Skip to content
Open
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
19 changes: 14 additions & 5 deletions mathics/builtin/arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,8 +802,13 @@ def to_sympy(self, expr, **kwargs):
try:
e_kwargs = kwargs.copy()
e_kwargs["convert_all_global_functions"] = True
e_kwargs["dummies"] = e_kwargs.get("dummies", set()).union((index,))
e = expr.elements[0].to_sympy(**e_kwargs)
i = index.elements[0].to_sympy(**kwargs)
e_kwargs["convert_all_global_functions"] = kwargs.get(
"convert_all_global_functions", False
)

i = index.elements[0].to_sympy(**e_kwargs)
start = index.elements[1].to_sympy(**kwargs)
stop = index.elements[2].to_sympy(**kwargs)

Expand Down Expand Up @@ -1045,23 +1050,27 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]:
index = expr.elements[1]
arg_kwargs = kwargs.copy()
arg_kwargs["convert_all_global_functions"] = True
arg_kwargs["dummies"] = kwargs.get("dummies", set()).union((index,))
f_sympy = expr.elements[0].to_sympy(**arg_kwargs)
if f_sympy is None:
return

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

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

arg_kwargs["convert_all_global_functions"] = kwargs.get(
"convert_all_global_functions", False
)
var_min_max = index.elements[:3]
bounds = [expr.to_sympy(**arg_kwargs) for expr in var_min_max]
if evaluation:
# Min and max might be Mathics expressions. If so, evaluate them.
for i in (1, 2):
min_max_expr = var_min_max[i]
if not isinstance(expr, Symbol):
min_max_expr_eval = min_max_expr.evaluate(evaluation)
value = min_max_expr_eval.to_sympy(**kwargs)
value = min_max_expr_eval.to_sympy(**arg_kwargs)
bounds[i] = value

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

if all(
if evaluation and all(
(hasattr(i, "is_integer") and i.is_integer)
or (hasattr(i, "is_finite") and i.is_finite and i.is_constant())
for i in bounds[1:]
Expand Down
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
46 changes: 32 additions & 14 deletions mathics/core/convert/sympy.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Tuple, Union, cast

import sympy
from sympy import Symbol as Sympy_Symbol, false as SympyFalse, true as SympyTrue
from sympy import (
Dummy as Sympy_Dummy,
Symbol as Sympy_Symbol,
false as SympyFalse,
true as SympyTrue,
)
from sympy.core.singleton import S

from mathics.core.atoms import (
Expand Down Expand Up @@ -108,6 +113,16 @@
}


def sympy_decode_mathics_symbol_name(name: str) -> 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 @@ -152,7 +167,9 @@ def __new__(cls, *exprs, **kwargs):
if kwargs.get("convert_functions_for_polynomialq", False):
sympy_elements = []
else:
sympy_elements = [element.to_sympy() for element in expr.elements]
sympy_elements = [
element.to_sympy(**kwargs) for element in expr.elements
]
if sympy_head is None or None in sympy_elements:
return None
obj = super().__new__(cls, sympy_head, *sympy_elements)
Expand Down Expand Up @@ -229,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 @@ -243,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 All @@ -261,11 +278,10 @@ def symbol_to_sympy(symbol: Symbol, **kwargs) -> Sympy_Symbol:
if result is not None:
return result

if symbol.sympy_dummy is not None:
return symbol.sympy_dummy

builtin = mathics_to_sympy.get(symbol.name)
if builtin is None or not builtin.sympy_name or not builtin.is_constant(): # nopep8
if symbol in kwargs.get("dummies", {}):
return Sympy_Dummy(sympy_name(symbol))
return Sympy_Symbol(sympy_name(symbol))
return builtin.to_sympy(symbol, **kwargs)

Expand Down Expand Up @@ -342,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 @@ -366,13 +381,16 @@ def old_from_sympy(expr) -> BaseElement:
if expr.is_Symbol:
name = str(expr)
if isinstance(expr, sympy.Dummy):
name = f"sympy`dummy`Dummy${expr.dummy_index}" # type: ignore[attr-defined]
name = name[1:]
if "_" not in name:
name = f"sympy`dummy`Dummy${expr.dummy_index}" # type: ignore[attr-defined]
else:
name = sympy_decode_mathics_symbol_name(name)
# Probably, this should be the value attribute
return Symbol(name, sympy_dummy=expr)
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 @@ -411,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 @@ -537,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
4 changes: 2 additions & 2 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 @@ -761,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("`", "_")


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