From 5e64159937b741eaa346af344151bac8c54348af Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Thu, 27 Nov 2025 09:45:53 -0300 Subject: [PATCH 1/7] DRY conversions to sympy. constants names to uppercase --- mathics/builtin/arithmetic.py | 5 +- mathics/builtin/functional/application.py | 4 +- mathics/builtin/numbers/algebra.py | 5 +- mathics/builtin/numbers/calculus.py | 10 +--- mathics/builtin/recurrence.py | 28 +++++----- mathics/core/convert/sympy.py | 33 +++++------ mathics/core/expression.py | 15 +---- mathics/core/symbols.py | 35 +++++++++--- test/core/test_sympy_python_convert.py | 67 ++++++++++++----------- 9 files changed, 103 insertions(+), 99 deletions(-) diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 60022155e..46b0c73b7 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -41,7 +41,7 @@ SympyObject, Test, ) -from mathics.core.convert.sympy import SympyExpression, from_sympy, sympy_symbol_prefix +from mathics.core.convert.sympy import SympyExpression, from_sympy from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression @@ -62,6 +62,7 @@ SymbolPlus, SymbolTimes, SymbolTrue, + sympy_name, ) from mathics.core.systemsymbols import ( SymbolAnd, @@ -377,7 +378,7 @@ def to_sympy(self, expr, **kwargs): sympy_cases = ( (expr.to_sympy(**kwargs), sympy_cond), - (sympy.Symbol(sympy_symbol_prefix + "System`Undefined"), True), + (sympy.Symbol(sympy_name(SymbolUndefined)), True), ) return sympy.Piecewise(*sympy_cases) diff --git a/mathics/builtin/functional/application.py b/mathics/builtin/functional/application.py index 9be292fe0..3c5bebbcd 100644 --- a/mathics/builtin/functional/application.py +++ b/mathics/builtin/functional/application.py @@ -14,7 +14,7 @@ from mathics.core.convert.sympy import SymbolFunction from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression -from mathics.core.symbols import Symbol, sympy_slot_prefix +from mathics.core.symbols import SYMPY_SLOT_PREFIX, Symbol from mathics.core.systemsymbols import SymbolSlot # This tells documentation how to sort this module @@ -206,7 +206,7 @@ class Slot(SympyFunction, PrefixOperator): def to_sympy(self, expr: Expression, **kwargs): index: Integer = expr.elements[0] - return sympy.Symbol(f"{sympy_slot_prefix}{index.get_int_value()}") + return sympy.Symbol(f"{SYMPY_SLOT_PREFIX}{index.get_int_value()}") class SlotSequence(PrefixOperator, Builtin): diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index ebd5d78e6..be3288e59 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -31,7 +31,7 @@ from mathics.core.attributes import A_LISTABLE, A_PROTECTED from mathics.core.builtin import Builtin from mathics.core.convert.python import from_bool -from mathics.core.convert.sympy import SympyExpression, from_sympy, sympy_symbol_prefix +from mathics.core.convert.sympy import SympyExpression, from_sympy from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression @@ -42,6 +42,7 @@ from mathics.core.list import ListExpression from mathics.core.rules import BasePattern from mathics.core.symbols import ( + SYMPY_SYMBOL_PREFIX, Atom, Symbol, SymbolFalse, @@ -183,7 +184,7 @@ def _expand(expr): def store_sub_expr(expr): sub_exprs.append(expr) - result = sympy.Symbol(sympy_symbol_prefix + str(len(sub_exprs) - 1)) + result = sympy.Symbol(SYMPY_SYMBOL_PREFIX + str(len(sub_exprs) - 1)) return result def get_sub_expr(expr): diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index 3f6c9dbd7..a734822c3 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -41,18 +41,14 @@ from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.convert.function import expression_to_callable_and_args from mathics.core.convert.python import from_python -from mathics.core.convert.sympy import ( - SymbolRootSum, - SympyExpression, - from_sympy, - sympy_symbol_prefix, -) +from mathics.core.convert.sympy import SymbolRootSum, SympyExpression, from_sympy from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.number import MACHINE_EPSILON, dps from mathics.core.rules import BasePattern from mathics.core.symbols import ( + SYMPY_SYMBOL_PREFIX, BaseElement, Symbol, SymbolFalse, @@ -532,7 +528,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(str(SYMPY_SYMBOL_PREFIX + func.__str__()))(*sym_args) counts = [element.get_int_value() for element in exprs[2].elements] if None in counts: diff --git a/mathics/builtin/recurrence.py b/mathics/builtin/recurrence.py index ddf1a19ef..610119d03 100644 --- a/mathics/builtin/recurrence.py +++ b/mathics/builtin/recurrence.py @@ -3,25 +3,23 @@ """ Solving Recurrence Equations """ - -# This tells documentation how to sort this module -# Here we are also hiding "moments" since this erroneously appears at the -# top level. -sort_order = "mathics.builtin.solving-recurrence-equations" - - import sympy from mathics.core.atoms import IntegerM1 from mathics.core.attributes import A_CONSTANT from mathics.core.builtin import Builtin -from mathics.core.convert.sympy import from_sympy, sympy_symbol_prefix +from mathics.core.convert.sympy import from_sympy from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Atom, Symbol, SymbolPlus, SymbolTimes from mathics.core.systemsymbols import SymbolFunction, SymbolRule +# This tells documentation how to sort this module +# Here we are also hiding "moments" since this erroneously appears at the +# top level. +sort_order = "mathics.builtin.solving-recurrence-equations" + class RSolve(Builtin): """ @@ -121,7 +119,7 @@ def is_relation(eqn): r_sympy = ri.to_sympy() if r_sympy is None: raise ValueError - conditions[le.elements[0].to_python()] = r_sympy + conditions[le.elements[0]] = r_sympy return False return True @@ -137,18 +135,18 @@ def is_relation(eqn): SymbolPlus, left, Expression(SymbolTimes, IntegerM1, right) ).evaluate(evaluation) - sym_eq = relation.to_sympy(converted_functions={func.get_head_name()}) + func_name = func.get_head_name() + sym_eq = relation.to_sympy(converted_functions={func_name}) if sym_eq is None: return - sym_n = sympy.core.symbols(str(sympy_symbol_prefix + n.name)) - sym_func = sympy.Function(str(sympy_symbol_prefix + func.get_head_name()))( - sym_n - ) + sym_func = func._as_sympy_function(converted_functions={func_name}) sym_conds = {} for cond in conditions: sym_conds[ - sympy.Function(str(sympy_symbol_prefix + func.get_head_name()))(cond) + Expression(func.head, cond)._as_sympy_function( + converted_functions={func_name} + ) ] = conditions[cond] try: diff --git a/mathics/core/convert/sympy.py b/mathics/core/convert/sympy.py index df091e1c3..7edcea4a0 100644 --- a/mathics/core/convert/sympy.py +++ b/mathics/core/convert/sympy.py @@ -35,6 +35,8 @@ from mathics.core.list import ListExpression from mathics.core.number import FP_MANTISA_BINARY_DIGITS from mathics.core.symbols import ( + SYMPY_SLOT_PREFIX, + SYMPY_SYMBOL_PREFIX, Symbol, SymbolFalse, SymbolNull, @@ -42,8 +44,7 @@ SymbolPower, SymbolTimes, SymbolTrue, - sympy_slot_prefix, - sympy_symbol_prefix, + sympy_name, ) from mathics.core.systemsymbols import ( SymbolC, @@ -109,7 +110,7 @@ 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): + if name.startswith(SYMPY_SYMBOL_PREFIX) or name.startswith(SYMPY_SLOT_PREFIX): return False if not name.startswith("C"): return False @@ -238,13 +239,7 @@ def expression_to_sympy(expr: Expression, **kwargs): functions = kwargs.get("converted_functions", []) if head_name in functions: - sym_args = [element.to_sympy() for element in expr._elements] - if None in sym_args: - return None - func = sympy.Function(str(sympy_symbol_prefix + expr.get_head_name()))( - *sym_args - ) - return func + return expr._as_sympy_function(**kwargs) lookup_name = expr.get_lookup_name() builtin = mathics_to_sympy.get(lookup_name) @@ -269,7 +264,7 @@ def symbol_to_sympy(symbol: Symbol, **kwargs) -> Sympy_Symbol: builtin = mathics_to_sympy.get(symbol.name) if builtin is None or not builtin.sympy_name or not builtin.is_constant(): # nopep8 - return Sympy_Symbol(sympy_symbol_prefix + symbol.name) + return Sympy_Symbol(sympy_name(symbol)) return builtin.to_sympy(symbol, **kwargs) @@ -369,15 +364,15 @@ def old_from_sympy(expr) -> BaseElement: if expr.is_Symbol: name = str(expr) if isinstance(expr, sympy.Dummy): - name = name + (f"__Dummy_{expr.dummy_index}") # type: ignore[attr-defined] + name = f"sympy`dummy`Dummy${expr.dummy_index}" # type: ignore[attr-defined] # Probably, this should be the value attribute return Symbol(name, sympy_dummy=expr) if is_Cn_expr(name): return Expression(SymbolC, Integer(int(name[1:]))) - if name.startswith(sympy_symbol_prefix): - name = name[len(sympy_symbol_prefix) :] - if name.startswith(sympy_slot_prefix): - index = int(name[len(sympy_slot_prefix) :]) + if name.startswith(SYMPY_SYMBOL_PREFIX): + name = name[len(SYMPY_SYMBOL_PREFIX) :] + if name.startswith(SYMPY_SLOT_PREFIX): + index = int(name[len(SYMPY_SLOT_PREFIX) :]) return Expression(SymbolSlot, Integer(index)) elif expr.is_NumberSymbol: name = str(expr) @@ -497,7 +492,7 @@ def old_from_sympy(expr) -> BaseElement: return Expression(SymbolRoot, from_sympy(e_root), Integer(indx + 1)) if isinstance(expr, sympy.Lambda): variables = [ - sympy.Symbol(f"{sympy_slot_prefix}{index + 1}") + sympy.Symbol(f"{SYMPY_SLOT_PREFIX}{index + 1}") for index in range(len(expr.variables)) ] return Expression(SymbolFunction, from_sympy(expr(*variables))) @@ -540,8 +535,8 @@ 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) :] + if name.startswith(SYMPY_SYMBOL_PREFIX): + name = name[len(SYMPY_SYMBOL_PREFIX) :] args = [from_sympy(arg) for arg in expr.args] builtin = sympy_to_mathics.get(name) if builtin is not None: diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 2b5b73e89..ba0eba05a 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -60,22 +60,14 @@ SymbolTimes, SymbolTrue, symbol_set, + sympy_name, ) from mathics.core.systemsymbols import ( SymbolAborted, - SymbolAlternatives, - SymbolBlank, - SymbolBlankNullSequence, - SymbolBlankSequence, - SymbolCondition, SymbolDirectedInfinity, SymbolFunction, SymbolMinus, - SymbolOptional, - SymbolOptionsPattern, SymbolOverflow, - SymbolPattern, - SymbolPatternTest, SymbolPower, SymbolSequence, SymbolSin, @@ -83,7 +75,6 @@ SymbolSqrt, SymbolSubtract, SymbolUnevaluated, - SymbolVerbatim, ) from mathics.eval.tracing import trace_evaluate @@ -331,9 +322,7 @@ def __str__(self) -> str: ) def _as_sympy_function(self, **kwargs): - from mathics.core.convert.sympy import sympy_symbol_prefix - - function_name = str(sympy_symbol_prefix + self.get_head_name()) + function_name = sympy_name(self.head) f = sympy.Function(function_name) if kwargs.get("convert_functions_for_polynomial", False): diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 1394d06e8..db9135d87 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -29,8 +29,8 @@ # keep variable names short. In tracing values, long names makes # output messy and harder to follow, since it detracts from the # important information -sympy_symbol_prefix = "_u" -sympy_slot_prefix = "_#" +SYMPY_SYMBOL_PREFIX = "_u" +SYMPY_SLOT_PREFIX = "_#" class NumericOperators: @@ -109,6 +109,26 @@ def round_to_float( return None +def strip_context(name) -> str: + """strip context from a symbol name""" + if "`" in name: + return name[name.rindex("`") + 1 :] + return name + + +def sympy_strip_context(name) -> str: + """ + Strip context from sympy names. + Currenty we use the same context marks for + mathics and sympy symbols. However, + when using sympy to compile code, + having '`' in symbol names + produce invalid code. In a next round, we would like + to use another character for split contexts in sympy variables. + """ + return strip_context(name) + + # system_symbols_dict({'SomeSymbol': ...}) -> {Symbol('System`SomeSymbol'): ...} def system_symbols_dict(d): return {Symbol(k): v for k, v in d.items()} @@ -123,12 +143,6 @@ def valid_context_name(ctx, allow_initial_backquote=False) -> bool: ) -def strip_context(name) -> str: - if "`" in name: - return name[name.rindex("`") + 1 :] - return name - - class Atom(BaseElement): """ Atoms are the (some) leaves and the Heads of an S-Expression or an M-Expression. @@ -745,6 +759,11 @@ def symbol_set(*symbols: Symbol) -> FrozenSet[Symbol]: return frozenset(symbols) +def sympy_name(mathics_symbol: Symbol): + """Convert a mathics symbol name into a sympy symbol name""" + return SYMPY_SYMBOL_PREFIX + mathics_symbol.name + + # Symbols used in this module. # Note, below we are only setting SymbolConstant for Symbols which diff --git a/test/core/test_sympy_python_convert.py b/test/core/test_sympy_python_convert.py index cae80f38c..d5be71d9c 100644 --- a/test/core/test_sympy_python_convert.py +++ b/test/core/test_sympy_python_convert.py @@ -21,10 +21,10 @@ from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import ( + SYMPY_SLOT_PREFIX, + SYMPY_SYMBOL_PREFIX, Symbol, SymbolPlus, - sympy_slot_prefix, - sympy_symbol_prefix, ) from mathics.core.systemsymbols import ( SymbolD, @@ -49,10 +49,10 @@ 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("Global`x"), sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")) self.compare( Symbol("_Mathics_User_x"), - sympy.Symbol(f"{sympy_symbol_prefix}System`_Mathics_User_x"), + sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}System`_Mathics_User_x"), ) def testReal(self): @@ -88,15 +88,15 @@ 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")), + sympy.Add(sympy.Integer(1), sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")), ) def testIntegrate(self): self.compare( Expression(SymbolIntegrate, Symbol("Global`x"), Symbol("Global`y")), sympy.Integral( - sympy.Symbol(f"{sympy_symbol_prefix}Global`x"), - sympy.Symbol(f"{sympy_symbol_prefix}Global`y"), + sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"), + sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`y"), ), ) @@ -104,8 +104,8 @@ def testDerivative(self): self.compare( Expression(SymbolD, Symbol("Global`x"), Symbol("Global`y")), sympy.Derivative( - sympy.Symbol(f"{sympy_symbol_prefix}Global`x"), - sympy.Symbol(f"{sympy_symbol_prefix}Global`y"), + sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"), + sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`y"), ), ) @@ -118,12 +118,12 @@ def testDerivative2(self): ) expr = Expression(head, Symbol("Global`x"), Symbol("Global`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"), + 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.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x") ) self.compare_to_sympy(expr, sym_expr, **kwargs) @@ -133,28 +133,28 @@ 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") + sarg1 = sympy.Function(str(f"{SYMPY_SYMBOL_PREFIX}Global`f"))( + sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`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"), + 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"), ) self.compare(marg2, sarg2, **kwargs) self.compare( Expression(SymbolD, marg2, Symbol("Global`x")), - sympy.Derivative(sarg2, sympy.Symbol(f"{sympy_symbol_prefix}Global`x")), + sympy.Derivative(sarg2, sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")), **kwargs, ) def testExpression(self): self.compare( Expression(SymbolSin, Symbol("Global`x")), - sympy.sin(sympy.Symbol(f"{sympy_symbol_prefix}Global`x")), + sympy.sin(sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x")), ) def testConstant(self): @@ -164,29 +164,34 @@ def testConstant(self): def testGamma(self): self.compare( Expression(SymbolGamma, Symbol("Global`z")), - sympy.gamma(sympy.Symbol(f"{sympy_symbol_prefix}Global`z")), + sympy.gamma(sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`z")), ) self.compare( Expression(SymbolGamma, Symbol("Global`z"), Symbol("Global`x")), sympy.uppergamma( - sympy.Symbol(f"{sympy_symbol_prefix}Global`z"), - sympy.Symbol(f"{sympy_symbol_prefix}Global`x"), + sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`z"), + sympy.Symbol(f"{SYMPY_SYMBOL_PREFIX}Global`x"), ), ) def testSlots(self): """check the conversion of slots in anonymous functions.""" - sympy_symbol = sympy.Symbol("x") + sympy_symbol = sympy.Symbol(f"{SYMPY_SLOT_PREFIX}1") sympy_lambda_expr = sympy.Lambda(sympy_symbol, sympy_symbol + 1) + expr = Expression( + SymbolFunction, + Expression(SymbolPlus, Integer1, Expression(SymbolSlot, Integer1)), + ) + print(" * expr:", expr) + print(" * lambda:", sympy_lambda_expr) + self.compare(expr, sympy_lambda_expr) # compare_to_sympy does not pass because Slot[1] are translated as # functions - self.compare_to_mathics( - Expression( - SymbolFunction, - Expression(SymbolPlus, Integer1, Expression(SymbolSlot, Integer1)), - ), - sympy_lambda_expr, - ) + + # self.compare_to_mathics( + # expr, + # sympy_lambda_expr, + # ) class PythonConvert(unittest.TestCase): From 6859dccf1a29289f578f4b7e4e1ca1975be7f78d Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Thu, 27 Nov 2025 09:46:42 -0300 Subject: [PATCH 2/7] adjust test --- test/core/test_sympy_python_convert.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/core/test_sympy_python_convert.py b/test/core/test_sympy_python_convert.py index d5be71d9c..31f4fd2dc 100644 --- a/test/core/test_sympy_python_convert.py +++ b/test/core/test_sympy_python_convert.py @@ -182,16 +182,7 @@ def testSlots(self): SymbolFunction, Expression(SymbolPlus, Integer1, Expression(SymbolSlot, Integer1)), ) - print(" * expr:", expr) - print(" * lambda:", sympy_lambda_expr) self.compare(expr, sympy_lambda_expr) - # compare_to_sympy does not pass because Slot[1] are translated as - # functions - - # self.compare_to_mathics( - # expr, - # sympy_lambda_expr, - # ) class PythonConvert(unittest.TestCase): From 837b5e70138afb5f2ce0495351446e1ac14c3267 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Thu, 27 Nov 2025 10:52:02 -0300 Subject: [PATCH 3/7] remove sympy_dummy attribute to Symbols --- mathics/core/convert/sympy.py | 20 ++++++++++++++------ mathics/core/symbols.py | 23 ++++++++--------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/mathics/core/convert/sympy.py b/mathics/core/convert/sympy.py index 7edcea4a0..b23b8fbb0 100644 --- a/mathics/core/convert/sympy.py +++ b/mathics/core/convert/sympy.py @@ -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 ( @@ -259,11 +264,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) @@ -364,9 +368,13 @@ 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 = name[len(SYMPY_SYMBOL_PREFIX) :] # 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): diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index db9135d87..c5562f9f7 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -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. @@ -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. @@ -385,18 +384,6 @@ def __new__(cls, name: str, sympy_dummy=None): # 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 @@ -405,7 +392,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: """ @@ -703,6 +690,12 @@ 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: """ From a232e7983ff422ea1d1f8bcb9f4101b1b41710d6 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Thu, 27 Nov 2025 11:48:12 -0300 Subject: [PATCH 4/7] adding benchmarks. Using dummy variables in sums and products. --- mathics/builtin/arithmetic.py | 19 ++++++++++++++----- mathics/core/convert/sympy.py | 4 +++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 46b0c73b7..1d65e9327 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -793,8 +793,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) @@ -1032,6 +1037,7 @@ 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 @@ -1039,16 +1045,19 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]: 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 @@ -1062,7 +1071,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:] diff --git a/mathics/core/convert/sympy.py b/mathics/core/convert/sympy.py index b23b8fbb0..2b3badb9d 100644 --- a/mathics/core/convert/sympy.py +++ b/mathics/core/convert/sympy.py @@ -157,7 +157,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) From 5ea915ba094f33dee1725947db88313f58d2444e Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 1 Dec 2025 23:02:12 -0300 Subject: [PATCH 5/7] 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. --- mathics/builtin/numbers/calculus.py | 3 +- mathics/core/convert/sympy.py | 26 ++++++--- mathics/core/symbols.py | 27 +++++---- mathics/eval/drawing/plot_compile.py | 7 ++- test/core/test_sympy_python_convert.py | 81 ++++++++++++++------------ 5 files changed, 85 insertions(+), 59 deletions(-) diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index a734822c3..96f02123b 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -57,6 +57,7 @@ SymbolPower, SymbolTimes, SymbolTrue, + sympy_name, ) from mathics.core.systemsymbols import ( SymbolAnd, @@ -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: diff --git a/mathics/core/convert/sympy.py b/mathics/core/convert/sympy.py index 29efbdd7f..f0889936e 100644 --- a/mathics/core/convert/sympy.py +++ b/mathics/core/convert/sympy.py @@ -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): @@ -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`"): @@ -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: @@ -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): @@ -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)) @@ -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: @@ -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: diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index c5562f9f7..8b0b621ea 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -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'): ...} @@ -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. @@ -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. @@ -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 @@ -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: """ @@ -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: """ @@ -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("`", "_") # Symbols used in this module. diff --git a/mathics/eval/drawing/plot_compile.py b/mathics/eval/drawing/plot_compile.py index 1cfaf2066..04b92bca2 100644 --- a/mathics/eval/drawing/plot_compile.py +++ b/mathics/eval/drawing/plot_compile.py @@ -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 @@ -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) diff --git a/test/core/test_sympy_python_convert.py b/test/core/test_sympy_python_convert.py index 31f4fd2dc..e5f9fbb93 100644 --- a/test/core/test_sympy_python_convert.py +++ b/test/core/test_sympy_python_convert.py @@ -25,6 +25,7 @@ SYMPY_SYMBOL_PREFIX, Symbol, SymbolPlus, + sympy_name, ) from mathics.core.systemsymbols import ( SymbolD, @@ -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): @@ -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")) @@ -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)), ), ) @@ -114,17 +127,15 @@ 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) @@ -132,29 +143,27 @@ def testDerivative2(self): 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): @@ -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)), ), ) From a58b6cabc3fdec62859375ebca7987fc5abdcab6 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 1 Dec 2025 23:08:42 -0300 Subject: [PATCH 6/7] fix mypy --- mathics/core/convert/sympy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/core/convert/sympy.py b/mathics/core/convert/sympy.py index f0889936e..7fb96555c 100644 --- a/mathics/core/convert/sympy.py +++ b/mathics/core/convert/sympy.py @@ -113,7 +113,7 @@ } -def sympy_decode_mathics_symbol_name(name: str): +def sympy_decode_mathics_symbol_name(name: str) -> str: """ Remove the Prefix for Mathics symbols and restore the context separator character. From f952462f405f7472bc2698823877810ed0dcb582 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Tue, 2 Dec 2025 09:21:38 -0300 Subject: [PATCH 7/7] remove (again) sympy_dummy attribute from symbols --- mathics/core/symbols.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 8b0b621ea..2f85a22b1 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -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. @@ -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. @@ -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 @@ -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: """ @@ -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: """