Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion mathics/builtin/numbers/calculus.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
SymbolPower,
SymbolTimes,
SymbolTrue,
sympy_name,
)
from mathics.core.systemsymbols import (
SymbolAnd,
Expand Down Expand Up @@ -528,7 +529,7 @@ def to_sympy(self, expr, **kwargs):
return

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

counts = [element.get_int_value() for element in exprs[2].elements]
if None in counts:
Expand Down
26 changes: 17 additions & 9 deletions mathics/core/convert/sympy.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@
}


def sympy_decode_mathics_symbol_name(name: str):
"""
Remove the Prefix for Mathics symbols
and restore the context separator character.
"""
if name.startswith(SYMPY_SYMBOL_PREFIX):
return name[len(SYMPY_SYMBOL_PREFIX) :].replace("_", "`")
return name


def is_Cn_expr(name: str) -> bool:
"""Check if name is of the form {prefix}Cnnn"""
if name.startswith(SYMPY_SYMBOL_PREFIX) or name.startswith(SYMPY_SLOT_PREFIX):
Expand Down Expand Up @@ -236,7 +246,6 @@ def expression_to_sympy(expr: Expression, **kwargs):
"""
Convert `expr` to its sympy form.
"""

if len(expr.elements) > 0:
head_name = expr.get_head_name()
if head_name.startswith("Global`"):
Expand All @@ -250,6 +259,7 @@ def expression_to_sympy(expr: Expression, **kwargs):

lookup_name = expr.get_lookup_name()
builtin = mathics_to_sympy.get(lookup_name)

if builtin is not None:
sympy_expr = builtin.to_sympy(expr, **kwargs)
if sympy_expr is not None:
Expand Down Expand Up @@ -348,7 +358,6 @@ def old_from_sympy(expr) -> BaseElement:
"""
converts a SymPy object to a Mathics3 element.
"""

if isinstance(expr, (tuple, list)):
return to_mathics_list(*expr, elements_conversion_fn=from_sympy)
if isinstance(expr, int):
Expand All @@ -373,16 +382,15 @@ def old_from_sympy(expr) -> BaseElement:
name = str(expr)
if isinstance(expr, sympy.Dummy):
name = name[1:]
if "`" not in name:
if "_" not in name:
name = f"sympy`dummy`Dummy${expr.dummy_index}" # type: ignore[attr-defined]
else:
name = name[len(SYMPY_SYMBOL_PREFIX) :]
name = sympy_decode_mathics_symbol_name(name)
# Probably, this should be the value attribute
return Symbol(name)
if is_Cn_expr(name):
return Expression(SymbolC, Integer(int(name[1:])))
if name.startswith(SYMPY_SYMBOL_PREFIX):
name = name[len(SYMPY_SYMBOL_PREFIX) :]
name = sympy_decode_mathics_symbol_name(name)
if name.startswith(SYMPY_SLOT_PREFIX):
index = int(name[len(SYMPY_SLOT_PREFIX) :])
return Expression(SymbolSlot, Integer(index))
Expand Down Expand Up @@ -421,7 +429,8 @@ def old_from_sympy(expr) -> BaseElement:
if isinstance(expr, sympy.core.numbers.NaN):
return SymbolIndeterminate
if isinstance(expr, sympy.core.function.FunctionClass):
return Symbol(str(expr))
name = str(expr).replace("_", "`")
return Symbol(name)
if expr is sympy.true:
return SymbolTrue
if expr is sympy.false:
Expand Down Expand Up @@ -547,8 +556,7 @@ def old_from_sympy(expr) -> BaseElement:
Expression(Symbol("C"), Integer(int(name[1:]))),
*[from_sympy(arg) for arg in expr.args],
)
if name.startswith(SYMPY_SYMBOL_PREFIX):
name = name[len(SYMPY_SYMBOL_PREFIX) :]
name = sympy_decode_mathics_symbol_name(name)
args = [from_sympy(arg) for arg in expr.args]
builtin = sympy_to_mathics.get(name)
if builtin is not None:
Expand Down
27 changes: 17 additions & 10 deletions mathics/core/symbols.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def sympy_strip_context(name) -> str:
produce invalid code. In a next round, we would like
to use another character for split contexts in sympy variables.
"""
return strip_context(name)
return name.split("_")[-1]


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

name: str
hash: int
sympy_dummy: Any
_short_name: str

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

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

self.hash = hash((cls, name))

# TODO: revise how we convert sympy.Dummy
# symbols.
#
# In some cases, SymPy returns a sympy.Dummy
# object. It is converted to Mathics as a
# Symbol. However, we probably should have
# a different class for this kind of symbols.
# Also, sympy_dummy should be stored as the
# value attribute.
self.sympy_dummy = sympy_dummy

self._short_name = strip_context(name)

return self
Expand All @@ -392,7 +405,7 @@ def __eq__(self, other) -> bool:
return self is other

def __getnewargs__(self):
return (self.name,)
return (self.name, self.sympy_dummy)

def __hash__(self) -> int:
"""
Expand Down Expand Up @@ -690,12 +703,6 @@ def __new__(cls, name, value):
self.hash = hash((cls, name))
return self

def __getnewargs__(self):
return (
self.name,
self._value,
)

@property
def is_literal(self) -> bool:
"""
Expand Down Expand Up @@ -754,7 +761,7 @@ def symbol_set(*symbols: Symbol) -> FrozenSet[Symbol]:

def sympy_name(mathics_symbol: Symbol):
"""Convert a mathics symbol name into a sympy symbol name"""
return SYMPY_SYMBOL_PREFIX + mathics_symbol.name
return SYMPY_SYMBOL_PREFIX + mathics_symbol.name.replace("`", "_")
Copy link
Member

@rocky rocky Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SYMPY_SYMBOL_PREFIX + mathics.symbol.name.replace("``'", "_") seems like an idiom. Maybe it could be turned into a function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, is already a function. What is not a function yet is the conversion in the opposite direction.



# Symbols used in this module.
Expand Down
7 changes: 4 additions & 3 deletions mathics/eval/drawing/plot_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import scipy
import sympy

from mathics.core.convert.sympy import SympyExpression
from mathics.core.symbols import strip_context
from mathics.core.convert.sympy import SympyExpression, mathics_to_sympy
from mathics.core.symbols import sympy_strip_context
from mathics.core.util import print_expression_tree, print_sympy_tree


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

# Strip symbols in sympy expression of context.
subs = {
sym: sympy.Symbol(strip_context(str(sym))) for sym in sympy_expr.free_symbols
sym: sympy.Symbol(sympy_strip_context(str(sym)))
for sym in sympy_expr.free_symbols
}
sympy_expr = sympy_expr.subs(subs)

Expand Down
81 changes: 45 additions & 36 deletions test/core/test_sympy_python_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
SYMPY_SYMBOL_PREFIX,
Symbol,
SymbolPlus,
sympy_name,
)
from mathics.core.systemsymbols import (
SymbolD,
Expand All @@ -36,6 +37,12 @@
SymbolSlot,
)

Symbol_f = Symbol("Global`f")
Symbol_x = Symbol("Global`x")
Symbol_y = Symbol("Global`y")
Symbol_z = Symbol("Global`z")
Symbol_Mathics_User_x = Symbol("Mathics`User`x")


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

def testSymbol(self):
self.compare(Symbol("Global`x"), sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"))
self.compare(Symbol_x, sympy.Symbol(sympy_name(Symbol_x)))
self.compare(
Symbol("_Mathics_User_x"),
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}System`_Mathics_User_x"),
Symbol_Mathics_User_x,
sympy.Symbol(sympy_name(Symbol_Mathics_User_x)),
)
# Sympy symbols without prefix are mapped to symbols in
# System` context:
self.compare_to_mathics(Symbol("x"), sympy.Symbol("x"))
# Notice that a sympy Symbol named "x" is converted
# to the Mathics symbol "System`x", and then, when converted
# back to sympy, goes to sympy.Symbol("_uSystem_x").

def testReal(self):
self.compare(Real("1.0"), sympy.Float("1.0"))
Expand Down Expand Up @@ -87,25 +100,25 @@ def testString(self):

def testAdd(self):
self.compare(
Expression(SymbolPlus, Integer1, Symbol("Global`x")),
sympy.Add(sympy.Integer(1), sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")),
Expression(SymbolPlus, Integer1, Symbol_x),
sympy.Add(sympy.Integer(1), sympy.Symbol(sympy_name(Symbol_x))),
)

def testIntegrate(self):
self.compare(
Expression(SymbolIntegrate, Symbol("Global`x"), Symbol("Global`y")),
Expression(SymbolIntegrate, Symbol_x, Symbol_y),
sympy.Integral(
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"),
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`y"),
sympy.Symbol(sympy_name(Symbol_x)),
sympy.Symbol(sympy_name(Symbol_y)),
),
)

def testDerivative(self):
self.compare(
Expression(SymbolD, Symbol("Global`x"), Symbol("Global`y")),
Expression(SymbolD, Symbol_x, Symbol_y),
sympy.Derivative(
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"),
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`y"),
sympy.Symbol(sympy_name(Symbol_x)),
sympy.Symbol(sympy_name(Symbol_y)),
),
)

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

head = Expression(
Expression(SymbolDerivative, Integer1, Integer0),
Symbol("Global`f"),
Symbol_f,
)
expr = Expression(head, Symbol("Global`x"), Symbol("Global`y"))
expr = Expression(head, Symbol_x, Symbol_y)

sfxy = sympy.Function(str(f"{SYMPY_SYMBOL_PREFIX}Global`f"))(
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"),
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`y"),
)
sym_expr = sympy.Derivative(
sfxy, sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")
sfxy = sympy.Function(sympy_name(Symbol_f))(
sympy.Symbol(sympy_name(Symbol_x)),
sympy.Symbol(sympy_name(Symbol_y)),
)
sym_expr = sympy.Derivative(sfxy, sympy.Symbol(sympy_name(Symbol_x)))

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

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

marg1 = Expression(Symbol("Global`f"), Symbol("Global`x"))
sarg1 = sympy.Function(str(f"{SYMPY_SYMBOL_PREFIX}Global`f"))(
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")
)
marg1 = Expression(Symbol_f, Symbol_x)
sarg1 = sympy.Function(sympy_name(Symbol_f))(sympy.Symbol(sympy_name(Symbol_x)))
self.compare(marg1, sarg1, **kwargs)

marg2 = Expression(Symbol("Global`f"), Symbol("Global`x"), Symbol("Global`y"))
sarg2 = sympy.Function(str(f"{SYMPY_SYMBOL_PREFIX}Global`f"))(
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"),
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`y"),
marg2 = Expression(Symbol_f, Symbol_x, Symbol_y)
sarg2 = sympy.Function(sympy_name(Symbol_f))(
sympy.Symbol(sympy_name(Symbol_x)),
sympy.Symbol(sympy_name(Symbol_y)),
)
self.compare(marg2, sarg2, **kwargs)

self.compare(
Expression(SymbolD, marg2, Symbol("Global`x")),
sympy.Derivative(sarg2, sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")),
Expression(SymbolD, marg2, Symbol_x),
sympy.Derivative(sarg2, sympy.Symbol(sympy_name(Symbol_x))),
**kwargs,
)

def testExpression(self):
self.compare(
Expression(SymbolSin, Symbol("Global`x")),
sympy.sin(sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")),
Expression(SymbolSin, Symbol_x),
sympy.sin(sympy.Symbol(sympy_name(Symbol_x))),
)

def testConstant(self):
Expand All @@ -163,14 +172,14 @@ def testConstant(self):

def testGamma(self):
self.compare(
Expression(SymbolGamma, Symbol("Global`z")),
sympy.gamma(sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`z")),
Expression(SymbolGamma, Symbol_z),
sympy.gamma(sympy.Symbol(sympy_name(Symbol_z))),
)
self.compare(
Expression(SymbolGamma, Symbol("Global`z"), Symbol("Global`x")),
Expression(SymbolGamma, Symbol_z, Symbol_x),
sympy.uppergamma(
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`z"),
sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"),
sympy.Symbol(sympy_name(Symbol_z)),
sympy.Symbol(sympy_name(Symbol_x)),
),
)

Expand Down