Skip to content

Commit d98cc2c

Browse files
authored
Improve sympy conversions (#1534)
To start addressing #1533, this PR DRY the existing code, to avoid inconsistencies when the separator character be changed. Also, some constant names are changed to fullfil the Python conventions.
1 parent 9481d26 commit d98cc2c

File tree

9 files changed

+95
-100
lines changed

9 files changed

+95
-100
lines changed

mathics/builtin/arithmetic.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
SympyObject,
4242
Test,
4343
)
44-
from mathics.core.convert.sympy import SympyExpression, from_sympy, sympy_symbol_prefix
44+
from mathics.core.convert.sympy import SympyExpression, from_sympy
4545
from mathics.core.element import BaseElement
4646
from mathics.core.evaluation import Evaluation
4747
from mathics.core.expression import Expression
@@ -62,6 +62,7 @@
6262
SymbolPlus,
6363
SymbolTimes,
6464
SymbolTrue,
65+
sympy_name,
6566
)
6667
from mathics.core.systemsymbols import (
6768
SymbolAnd,
@@ -377,7 +378,7 @@ def to_sympy(self, expr, **kwargs):
377378

378379
sympy_cases = (
379380
(expr.to_sympy(**kwargs), sympy_cond),
380-
(sympy.Symbol(sympy_symbol_prefix + "System`Undefined"), True),
381+
(sympy.Symbol(sympy_name(SymbolUndefined)), True),
381382
)
382383
return sympy.Piecewise(*sympy_cases)
383384

mathics/builtin/functional/application.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from mathics.core.convert.sympy import SymbolFunction
1515
from mathics.core.evaluation import Evaluation
1616
from mathics.core.expression import Expression
17-
from mathics.core.symbols import Symbol, sympy_slot_prefix
17+
from mathics.core.symbols import SYMPY_SLOT_PREFIX, Symbol
1818
from mathics.core.systemsymbols import SymbolSlot
1919

2020
# This tells documentation how to sort this module
@@ -206,7 +206,7 @@ class Slot(SympyFunction, PrefixOperator):
206206

207207
def to_sympy(self, expr: Expression, **kwargs):
208208
index: Integer = expr.elements[0]
209-
return sympy.Symbol(f"{sympy_slot_prefix}{index.get_int_value()}")
209+
return sympy.Symbol(f"{SYMPY_SLOT_PREFIX}{index.get_int_value()}")
210210

211211

212212
class SlotSequence(PrefixOperator, Builtin):

mathics/builtin/numbers/algebra.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from mathics.core.attributes import A_LISTABLE, A_PROTECTED
3232
from mathics.core.builtin import Builtin
3333
from mathics.core.convert.python import from_bool
34-
from mathics.core.convert.sympy import SympyExpression, from_sympy, sympy_symbol_prefix
34+
from mathics.core.convert.sympy import SympyExpression, from_sympy
3535
from mathics.core.element import BaseElement
3636
from mathics.core.evaluation import Evaluation
3737
from mathics.core.expression import Expression
@@ -42,6 +42,7 @@
4242
from mathics.core.list import ListExpression
4343
from mathics.core.rules import BasePattern
4444
from mathics.core.symbols import (
45+
SYMPY_SYMBOL_PREFIX,
4546
Atom,
4647
Symbol,
4748
SymbolFalse,
@@ -183,7 +184,7 @@ def _expand(expr):
183184

184185
def store_sub_expr(expr):
185186
sub_exprs.append(expr)
186-
result = sympy.Symbol(sympy_symbol_prefix + str(len(sub_exprs) - 1))
187+
result = sympy.Symbol(SYMPY_SYMBOL_PREFIX + str(len(sub_exprs) - 1))
187188
return result
188189

189190
def get_sub_expr(expr):

mathics/builtin/numbers/calculus.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,14 @@
4141
from mathics.core.convert.expression import to_expression, to_mathics_list
4242
from mathics.core.convert.function import expression_to_callable_and_args
4343
from mathics.core.convert.python import from_python
44-
from mathics.core.convert.sympy import (
45-
SymbolRootSum,
46-
SympyExpression,
47-
from_sympy,
48-
sympy_symbol_prefix,
49-
)
44+
from mathics.core.convert.sympy import SymbolRootSum, SympyExpression, from_sympy
5045
from mathics.core.evaluation import Evaluation
5146
from mathics.core.expression import Expression
5247
from mathics.core.list import ListExpression
5348
from mathics.core.number import MACHINE_EPSILON, dps
5449
from mathics.core.rules import BasePattern
5550
from mathics.core.symbols import (
51+
SYMPY_SYMBOL_PREFIX,
5652
BaseElement,
5753
Symbol,
5854
SymbolFalse,
@@ -532,7 +528,7 @@ def to_sympy(self, expr, **kwargs):
532528
return
533529

534530
func = exprs[1].elements[0]
535-
sym_func = sympy.Function(str(sympy_symbol_prefix + func.__str__()))(*sym_args)
531+
sym_func = sympy.Function(str(SYMPY_SYMBOL_PREFIX + func.__str__()))(*sym_args)
536532

537533
counts = [element.get_int_value() for element in exprs[2].elements]
538534
if None in counts:

mathics/builtin/recurrence.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,23 @@
33
"""
44
Solving Recurrence Equations
55
"""
6-
7-
# This tells documentation how to sort this module
8-
# Here we are also hiding "moments" since this erroneously appears at the
9-
# top level.
10-
sort_order = "mathics.builtin.solving-recurrence-equations"
11-
12-
136
import sympy
147

158
from mathics.core.atoms import IntegerM1
169
from mathics.core.attributes import A_CONSTANT
1710
from mathics.core.builtin import Builtin
18-
from mathics.core.convert.sympy import from_sympy, sympy_symbol_prefix
11+
from mathics.core.convert.sympy import from_sympy
1912
from mathics.core.evaluation import Evaluation
2013
from mathics.core.expression import Expression
2114
from mathics.core.list import ListExpression
2215
from mathics.core.symbols import Atom, Symbol, SymbolPlus, SymbolTimes
2316
from mathics.core.systemsymbols import SymbolFunction, SymbolRule
2417

18+
# This tells documentation how to sort this module
19+
# Here we are also hiding "moments" since this erroneously appears at the
20+
# top level.
21+
sort_order = "mathics.builtin.solving-recurrence-equations"
22+
2523

2624
class RSolve(Builtin):
2725
"""
@@ -121,7 +119,7 @@ def is_relation(eqn):
121119
r_sympy = ri.to_sympy()
122120
if r_sympy is None:
123121
raise ValueError
124-
conditions[le.elements[0].to_python()] = r_sympy
122+
conditions[le.elements[0]] = r_sympy
125123
return False
126124
return True
127125

@@ -137,18 +135,18 @@ def is_relation(eqn):
137135
SymbolPlus, left, Expression(SymbolTimes, IntegerM1, right)
138136
).evaluate(evaluation)
139137

140-
sym_eq = relation.to_sympy(converted_functions={func.get_head_name()})
138+
func_name = func.get_head_name()
139+
sym_eq = relation.to_sympy(converted_functions={func_name})
141140
if sym_eq is None:
142141
return
143-
sym_n = sympy.core.symbols(str(sympy_symbol_prefix + n.name))
144-
sym_func = sympy.Function(str(sympy_symbol_prefix + func.get_head_name()))(
145-
sym_n
146-
)
142+
sym_func = func._as_sympy_function(converted_functions={func_name})
147143

148144
sym_conds = {}
149145
for cond in conditions:
150146
sym_conds[
151-
sympy.Function(str(sympy_symbol_prefix + func.get_head_name()))(cond)
147+
Expression(func.head, cond)._as_sympy_function(
148+
converted_functions={func_name}
149+
)
152150
] = conditions[cond]
153151

154152
try:

mathics/core/convert/sympy.py

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,16 @@
3535
from mathics.core.list import ListExpression
3636
from mathics.core.number import FP_MANTISA_BINARY_DIGITS
3737
from mathics.core.symbols import (
38+
SYMPY_SLOT_PREFIX,
39+
SYMPY_SYMBOL_PREFIX,
3840
Symbol,
3941
SymbolFalse,
4042
SymbolNull,
4143
SymbolPlus,
4244
SymbolPower,
4345
SymbolTimes,
4446
SymbolTrue,
45-
sympy_slot_prefix,
46-
sympy_symbol_prefix,
47+
sympy_name,
4748
)
4849
from mathics.core.systemsymbols import (
4950
SymbolC,
@@ -109,7 +110,7 @@
109110

110111
def is_Cn_expr(name: str) -> bool:
111112
"""Check if name is of the form {prefix}Cnnn"""
112-
if name.startswith(sympy_symbol_prefix) or name.startswith(sympy_slot_prefix):
113+
if name.startswith(SYMPY_SYMBOL_PREFIX) or name.startswith(SYMPY_SLOT_PREFIX):
113114
return False
114115
if not name.startswith("C"):
115116
return False
@@ -238,13 +239,7 @@ def expression_to_sympy(expr: Expression, **kwargs):
238239

239240
functions = kwargs.get("converted_functions", [])
240241
if head_name in functions:
241-
sym_args = [element.to_sympy() for element in expr._elements]
242-
if None in sym_args:
243-
return None
244-
func = sympy.Function(str(sympy_symbol_prefix + expr.get_head_name()))(
245-
*sym_args
246-
)
247-
return func
242+
return expr._as_sympy_function(**kwargs)
248243

249244
lookup_name = expr.get_lookup_name()
250245
builtin = mathics_to_sympy.get(lookup_name)
@@ -269,7 +264,7 @@ def symbol_to_sympy(symbol: Symbol, **kwargs) -> Sympy_Symbol:
269264

270265
builtin = mathics_to_sympy.get(symbol.name)
271266
if builtin is None or not builtin.sympy_name or not builtin.is_constant(): # nopep8
272-
return Sympy_Symbol(sympy_symbol_prefix + symbol.name)
267+
return Sympy_Symbol(sympy_name(symbol))
273268
return builtin.to_sympy(symbol, **kwargs)
274269

275270

@@ -369,15 +364,15 @@ def old_from_sympy(expr) -> BaseElement:
369364
if expr.is_Symbol:
370365
name = str(expr)
371366
if isinstance(expr, sympy.Dummy):
372-
name = name + (f"__Dummy_{expr.dummy_index}") # type: ignore[attr-defined]
367+
name = f"sympy`dummy`Dummy${expr.dummy_index}" # type: ignore[attr-defined]
373368
# Probably, this should be the value attribute
374369
return Symbol(name, sympy_dummy=expr)
375370
if is_Cn_expr(name):
376371
return Expression(SymbolC, Integer(int(name[1:])))
377-
if name.startswith(sympy_symbol_prefix):
378-
name = name[len(sympy_symbol_prefix) :]
379-
if name.startswith(sympy_slot_prefix):
380-
index = int(name[len(sympy_slot_prefix) :])
372+
if name.startswith(SYMPY_SYMBOL_PREFIX):
373+
name = name[len(SYMPY_SYMBOL_PREFIX) :]
374+
if name.startswith(SYMPY_SLOT_PREFIX):
375+
index = int(name[len(SYMPY_SLOT_PREFIX) :])
381376
return Expression(SymbolSlot, Integer(index))
382377
elif expr.is_NumberSymbol:
383378
name = str(expr)
@@ -497,7 +492,7 @@ def old_from_sympy(expr) -> BaseElement:
497492
return Expression(SymbolRoot, from_sympy(e_root), Integer(indx + 1))
498493
if isinstance(expr, sympy.Lambda):
499494
variables = [
500-
sympy.Symbol(f"{sympy_slot_prefix}{index + 1}")
495+
sympy.Symbol(f"{SYMPY_SLOT_PREFIX}{index + 1}")
501496
for index in range(len(expr.variables))
502497
]
503498
return Expression(SymbolFunction, from_sympy(expr(*variables)))
@@ -540,8 +535,8 @@ def old_from_sympy(expr) -> BaseElement:
540535
Expression(Symbol("C"), Integer(int(name[1:]))),
541536
*[from_sympy(arg) for arg in expr.args],
542537
)
543-
if name.startswith(sympy_symbol_prefix):
544-
name = name[len(sympy_symbol_prefix) :]
538+
if name.startswith(SYMPY_SYMBOL_PREFIX):
539+
name = name[len(SYMPY_SYMBOL_PREFIX) :]
545540
args = [from_sympy(arg) for arg in expr.args]
546541
builtin = sympy_to_mathics.get(name)
547542
if builtin is not None:

mathics/core/expression.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,30 +60,21 @@
6060
SymbolTimes,
6161
SymbolTrue,
6262
symbol_set,
63+
sympy_name,
6364
)
6465
from mathics.core.systemsymbols import (
6566
SymbolAborted,
66-
SymbolAlternatives,
67-
SymbolBlank,
68-
SymbolBlankNullSequence,
69-
SymbolBlankSequence,
70-
SymbolCondition,
7167
SymbolDirectedInfinity,
7268
SymbolFunction,
7369
SymbolMinus,
74-
SymbolOptional,
75-
SymbolOptionsPattern,
7670
SymbolOverflow,
77-
SymbolPattern,
78-
SymbolPatternTest,
7971
SymbolPower,
8072
SymbolSequence,
8173
SymbolSin,
8274
SymbolSlot,
8375
SymbolSqrt,
8476
SymbolSubtract,
8577
SymbolUnevaluated,
86-
SymbolVerbatim,
8778
)
8879
from mathics.eval.tracing import trace_evaluate
8980

@@ -331,9 +322,7 @@ def __str__(self) -> str:
331322
)
332323

333324
def _as_sympy_function(self, **kwargs):
334-
from mathics.core.convert.sympy import sympy_symbol_prefix
335-
336-
function_name = str(sympy_symbol_prefix + self.get_head_name())
325+
function_name = sympy_name(self.head)
337326
f = sympy.Function(function_name)
338327

339328
if kwargs.get("convert_functions_for_polynomial", False):

mathics/core/symbols.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
# keep variable names short. In tracing values, long names makes
3030
# output messy and harder to follow, since it detracts from the
3131
# important information
32-
sympy_symbol_prefix = "_u"
33-
sympy_slot_prefix = "_#"
32+
SYMPY_SYMBOL_PREFIX = "_u"
33+
SYMPY_SLOT_PREFIX = "_#"
3434

3535

3636
class NumericOperators:
@@ -109,6 +109,26 @@ def round_to_float(
109109
return None
110110

111111

112+
def strip_context(name) -> str:
113+
"""strip context from a symbol name"""
114+
if "`" in name:
115+
return name[name.rindex("`") + 1 :]
116+
return name
117+
118+
119+
def sympy_strip_context(name) -> str:
120+
"""
121+
Strip context from sympy names.
122+
Currenty we use the same context marks for
123+
mathics and sympy symbols. However,
124+
when using sympy to compile code,
125+
having '`' in symbol names
126+
produce invalid code. In a next round, we would like
127+
to use another character for split contexts in sympy variables.
128+
"""
129+
return strip_context(name)
130+
131+
112132
# system_symbols_dict({'SomeSymbol': ...}) -> {Symbol('System`SomeSymbol'): ...}
113133
def system_symbols_dict(d):
114134
return {Symbol(k): v for k, v in d.items()}
@@ -123,12 +143,6 @@ def valid_context_name(ctx, allow_initial_backquote=False) -> bool:
123143
)
124144

125145

126-
def strip_context(name) -> str:
127-
if "`" in name:
128-
return name[name.rindex("`") + 1 :]
129-
return name
130-
131-
132146
class Atom(BaseElement):
133147
"""
134148
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]:
745759
return frozenset(symbols)
746760

747761

762+
def sympy_name(mathics_symbol: Symbol):
763+
"""Convert a mathics symbol name into a sympy symbol name"""
764+
return SYMPY_SYMBOL_PREFIX + mathics_symbol.name
765+
766+
748767
# Symbols used in this module.
749768

750769
# Note, below we are only setting SymbolConstant for Symbols which

0 commit comments

Comments
 (0)