Skip to content

Commit 814f03f

Browse files
authored
Merge branch 'master' into good_bye_sympy_dummy_attribute
2 parents 9329419 + 1f5dbb2 commit 814f03f

File tree

9 files changed

+249
-169
lines changed

9 files changed

+249
-169
lines changed

mathics/builtin/arithmetic.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,11 @@ def to_sympy(self, expr, **kwargs):
373373
sympy_cond = False
374374
if sympy_cond is None:
375375
sympy_cond = cond.to_sympy(**kwargs)
376+
# See similar code and comment in mathics.builtin.procedural.If
377+
# TODO: consider adding .is_Symbol (as with If) so that this
378+
# can be used for compilation. I tried that but it invalidated
379+
# doctest 1876 in Simplify which depends on ConditionalExpression
380+
# not getting rewritten, so may need update that secion of doc?
376381
if not (sympy_cond.is_Relational or sympy_cond.is_Boolean):
377382
return
378383

mathics/builtin/intfns/divlike.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def eval(self, ns: List[Integer], evaluation: Evaluation):
160160
return Integer(result)
161161

162162

163-
class Mod(Builtin):
163+
class Mod(SympyFunction):
164164
"""
165165
<url>:WMA link:https://reference.wolfram.com/language/ref/Mod.html</url>
166166
@@ -183,6 +183,8 @@ class Mod(Builtin):
183183
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
184184
summary_text = "the remainder in an integer division"
185185

186+
sympy_name = "Mod"
187+
186188
def eval(self, n: Integer, m: Integer, evaluation: Evaluation):
187189
"Mod[n_Integer, m_Integer]"
188190

mathics/builtin/numbers/numbertheory.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ def _fractional_part(n, expr, evaluation: Evaluation):
350350
return expr
351351

352352

353-
class FractionalPart(Builtin):
353+
class FractionalPart(SympyFunction):
354354
"""
355355
<url>:WMA link:https://reference.wolfram.com/language/ref/FractionalPart.html</url>
356356
@@ -384,6 +384,11 @@ def eval_complex_n(self, n, evaluation: Evaluation):
384384
image_fractional_part = _fractional_part(n_image, expr, evaluation)
385385
return Expression(SymbolComplex, real_fractional_part, image_fractional_part)
386386

387+
def to_sympy(self, expr, **kwargs):
388+
if len(expr.elements) > 0:
389+
x_sympy = expr.elements[0].to_sympy(**kwargs)
390+
return sympy.sign(x_sympy) * sympy.frac(abs(x_sympy))
391+
387392

388393
class FromContinuedFraction(SympyFunction):
389394
"""
@@ -415,7 +420,7 @@ def eval(self, expr, evaluation: Evaluation):
415420
return from_sympy(sympy.continued_fraction_reduce(nums))
416421

417422

418-
class IntegerPart(Builtin):
423+
class IntegerPart(SympyFunction):
419424
"""
420425
<url>:WMA link:https://reference.wolfram.com/language/ref/IntegerPart.html</url>
421426
@@ -449,6 +454,11 @@ def eval_complex_n(self, n, evaluation: Evaluation):
449454
image_integer_part = _integer_part(n_image, expr, evaluation)
450455
return Expression(SymbolComplex, real_integer_part, image_integer_part)
451456

457+
def to_sympy(self, expr, **kwargs):
458+
if len(expr.elements) > 0:
459+
x_sympy = expr.elements[0].to_sympy(**kwargs)
460+
return sympy.sign(x_sympy) * sympy.floor(abs(x_sympy))
461+
452462

453463
class IntegerPartitions(Builtin):
454464
"""

mathics/builtin/procedural.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,20 @@
1616
environment.
1717
"""
1818

19+
import sympy
20+
1921
from mathics.core.attributes import (
2022
A_HOLD_ALL,
2123
A_HOLD_REST,
2224
A_PROTECTED,
2325
A_READ_PROTECTED,
2426
)
25-
from mathics.core.builtin import Builtin, InfixOperator, IterationFunction
27+
from mathics.core.builtin import (
28+
Builtin,
29+
InfixOperator,
30+
IterationFunction,
31+
SympyFunction,
32+
)
2633
from mathics.core.evaluation import Evaluation
2734
from mathics.core.expression import Expression
2835
from mathics.core.interrupt import (
@@ -365,7 +372,7 @@ def eval(self, start, test, incr, body, evaluation):
365372
return SymbolNull
366373

367374

368-
class If(Builtin):
375+
class If(SympyFunction):
369376
"""
370377
<url>:WMA link:https://reference.wolfram.com/language/ref/If.html</url>
371378
@@ -443,6 +450,28 @@ def eval_with_false_and_other(self, condition, t, f, u, evaluation):
443450
else:
444451
return u.evaluate(evaluation)
445452

453+
def to_sympy(self, expr, **kwargs):
454+
if len(expr.elements) == 3:
455+
sympy_cond = expr.elements[0].to_sympy(**kwargs)
456+
if sympy_cond is None:
457+
return
458+
# sympy.Piecewise is is picky as to what type of conds it will accept,
459+
# allowing Boolean, Relational, and Symbol (as an honorary Boolean,
460+
# accompanied by a comment in the code that this isn't really correct).
461+
# Seems an attempt at early typechecking, but maybe too restrictive -
462+
# what about Exprs in general that might or might not evaluate to Boolean?
463+
# See similar code in mathics.builtin.arithmetic.ConditionalExpression.
464+
if not (
465+
sympy_cond.is_Boolean
466+
or sympy_cond.is_Relational
467+
or sympy_cond.is_Symbol
468+
):
469+
return
470+
sympy_true = expr.elements[1].to_sympy(**kwargs)
471+
sympy_false = expr.elements[2].to_sympy(**kwargs)
472+
473+
return sympy.Piecewise((sympy_true, sympy_cond), (sympy_false, True))
474+
446475

447476
class Interrupt(Builtin):
448477
r"""
@@ -648,7 +677,7 @@ def eval_with_tag(
648677
raise WLThrowInterrupt(value, tag)
649678

650679

651-
class Which(Builtin):
680+
class Which(SympyFunction):
652681
"""
653682
<url>
654683
:WMA link:
@@ -710,6 +739,15 @@ def eval(self, items, evaluation):
710739
items = items[2:]
711740
return SymbolNull
712741

742+
def to_sympy(self, expr, **kwargs):
743+
if len(expr.elements) % 2 == 0:
744+
args = []
745+
for cond, value in zip(expr.elements[::2], expr.elements[1::2]):
746+
sympy_cond = cond.to_sympy(**kwargs)
747+
sympy_value = value.to_sympy(**kwargs)
748+
args.append((sympy_value, sympy_cond))
749+
return sympy.Piecewise(*args)
750+
713751

714752
class While(Builtin):
715753
"""

mathics/builtin/testing_expressions/equality_inequality.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ def eval_other(self, args, evaluation: Evaluation):
269269
return SymbolTrue
270270

271271

272-
class _MinMax(Builtin):
272+
class _MinMax(SympyFunction):
273273
attributes = (
274274
A_FLAT | A_NUMERIC_FUNCTION | A_ONE_IDENTITY | A_ORDERLESS | A_PROTECTED
275275
)
@@ -729,6 +729,7 @@ class Max(_MinMax):
729729
"""
730730

731731
summary_text = "get maximum value"
732+
sympy_name = "Max"
732733

733734

734735
class Min(_MinMax):
@@ -763,6 +764,7 @@ class Min(_MinMax):
763764

764765
sense = -1
765766
summary_text = "get minimum value"
767+
sympy_name = "Min"
766768

767769

768770
class SameQ(_ComparisonOperator):

mathics/core/builtin.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,8 +713,11 @@ def to_sympy(self, expr, **kwargs):
713713
sympy_function = self.get_sympy_function(elements)
714714
if sympy_function is not None:
715715
return tracing.run_sympy(sympy_function, *sympy_args)
716-
except TypeError:
717-
pass
716+
elif exc := kwargs.get("raise_on_error", None):
717+
raise exc(f"{self.get_name()}.sympy_name is {repr(self.sympy_name)}")
718+
except TypeError as oops:
719+
if exc := kwargs.get("raise_on_error", None):
720+
raise exc(f"TypeError: {oops}")
718721

719722
def from_sympy(self, elements: Tuple[BaseElement, ...]) -> Expression:
720723
return Expression(Symbol(self.get_name()), *elements)

mathics/core/convert/sympy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ def expression_to_sympy(expr: Expression, **kwargs):
254254
sympy_expr = builtin.to_sympy(expr, **kwargs)
255255
if sympy_expr is not None:
256256
return sympy_expr
257+
elif exc := kwargs.get("raise_on_error", None):
258+
raise exc(f"{lookup_name} not registered in mathics_to_sympy")
257259
return SympyExpression(expr, **kwargs)
258260

259261

mathics/eval/drawing/plot_compile.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import scipy
1919
import sympy
2020

21-
from mathics.core.convert.sympy import SympyExpression, mathics_to_sympy
21+
from mathics.core.convert.sympy import SympyExpression
2222
from mathics.core.symbols import strip_context
2323
from mathics.core.util import print_expression_tree, print_sympy_tree
2424

@@ -63,21 +63,8 @@ def plot_compile(evaluation, expr, names, debug=0):
6363
print("post-eval", expr)
6464

6565
# Ask the expr Expression to generate a sympy expression and handle errors
66-
try:
67-
sympy_expr = expr.to_sympy()
68-
except Exception as oops:
69-
raise CompileError(f"{expr}.to_sympy() failed: {oops}")
66+
sympy_expr = expr.to_sympy(raise_on_error=CompileError)
7067
if isinstance(sympy_expr, SympyExpression):
71-
if debug:
72-
# duplicates lookup logic in mathics.core.convert.sympy
73-
lookup_name = expr.get_lookup_name()
74-
builtin = mathics_to_sympy.get(lookup_name)
75-
if builtin:
76-
sympy_name = getattr(builtin, "sympy_name", None) if builtin else None
77-
print(f"compile: Invalid sympy_expr {sympy_expr}")
78-
print(f"compile: {builtin}.sympy_name is {repr(sympy_name)}")
79-
else:
80-
print(f"compile: {lookup_name} not registered with mathics_to_sympy")
8168
raise CompileError(f"{expr.head}.to_sympy returns invalid sympy expr.")
8269

8370
# Strip symbols in sympy expression of context.

0 commit comments

Comments
 (0)