Skip to content

Commit c71c4da

Browse files
bdlucas1mmatera
andauthored
New plot compile functions 2 (#1530)
* Added an optional keyword argument `raise_on_error` to `to_sympy` and `expression_to_sympy` that causes it to raise the specified exception in cases where it would otherwise silently have returned `None`. This is used by `plot_compile` to aid in identifying the cause of compilation failure, which will help to build out compilation functionality. That is the only place it is specified, and if it is not specified, behavior is as it was before. * Added compilation test cases in `test_plot_compile.py` for `If`, `Switch`, and `Which`. These are defined in `procedural.py`, but they are not really procedural. * Enabled `Mod`, `FractionalPart`, `IntegerPart`, `If`, `Which`, `Min`, and `Max` for compilation by sympy-enabling them: subclass from `SympyFunction` and implement `to_sympy`. Verified that this enables compilation using the test cases in `test_plot_compile.py`, and by doing some plots: <img width="726" height="983" alt="test-030" src="https://github.com/user-attachments/assets/05bd5aec-bf12-41bb-a021-d4b5c84491cf" /> --------- Co-authored-by: Juan Mauricio Matera <[email protected]>
1 parent afc3e4a commit c71c4da

File tree

9 files changed

+247
-169
lines changed

9 files changed

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

447474
class Interrupt(Builtin):
448475
r"""
@@ -648,7 +675,7 @@ def eval_with_tag(
648675
raise WLThrowInterrupt(value, tag)
649676

650677

651-
class Which(Builtin):
678+
class Which(SympyFunction):
652679
"""
653680
<url>
654681
:WMA link:
@@ -710,6 +737,15 @@ def eval(self, items, evaluation):
710737
items = items[2:]
711738
return SymbolNull
712739

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

714750
class While(Builtin):
715751
"""

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
@@ -247,6 +247,8 @@ def expression_to_sympy(expr: Expression, **kwargs):
247247
sympy_expr = builtin.to_sympy(expr, **kwargs)
248248
if sympy_expr is not None:
249249
return sympy_expr
250+
elif exc := kwargs.get("raise_on_error", None):
251+
raise exc(f"{lookup_name} not registered in mathics_to_sympy")
250252
return SympyExpression(expr, **kwargs)
251253

252254

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)