Skip to content
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
25 changes: 7 additions & 18 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,7 +342,6 @@ class Symbol(Atom, NumericOperators, EvalMixin):

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

# Dictionary of Symbols defined so far.
Expand All @@ -355,7 +354,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, sympy_dummy=None):
def __new__(cls, name: str):
"""
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 @@ -383,20 +382,7 @@ def __new__(cls, name: str, sympy_dummy=None):
# Python objects, so we include the class in the
# event that different objects have the same Python value.
# 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 @@ -405,7 +391,7 @@ def __eq__(self, other) -> bool:
return self is other

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

def __hash__(self) -> int:
"""
Expand Down Expand Up @@ -703,6 +689,9 @@ 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 @@ -761,7 +750,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
Loading
Loading