-
-
Notifications
You must be signed in to change notification settings - Fork 61
adding evaluate_without_side_effects for helping compile expressions.
#1539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 4 commits
45c302b
36837e4
969a232
abe1bbd
66c6c6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,12 @@ | |
|
|
||
| from mathics.builtin.box.compilation import CompiledCodeBox | ||
| from mathics.core.atoms import Integer, String | ||
| from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED | ||
| from mathics.core.attributes import ( | ||
| A_HOLD_ALL, | ||
| A_N_HOLD_ALL, | ||
| A_PROTECTED, | ||
| A_READ_PROTECTED, | ||
| ) | ||
| from mathics.core.builtin import Builtin | ||
| from mathics.core.convert.expression import to_mathics_list | ||
| from mathics.core.convert.function import ( | ||
|
|
@@ -65,9 +70,11 @@ class Compile(Builtin): | |
| >> cf[3.5, 2] | ||
| = 2.18888 | ||
|
|
||
| Loops and variable assignments are supported usinv Python builtin "compile" function: | ||
| >> Compile[{{a, _Integer}, {b, _Integer}}, While[b != 0, {a, b} = {b, Mod[a, b]}]; a] (* GCD of a, b *) | ||
| = CompiledFunction[{a, b}, a, -PythonizedCode-] | ||
| Loops and variable assignments are supported using Python builtin "compile" function: | ||
| >> gdc = Compile[{{a, _Integer}, {b, _Integer}}, Module[{x=a, y=b}, While[y != 0, {x, y} = {y, Mod[x, y]}]; x]] (* GCD of a, b *) | ||
| = CompiledFunction[{a, b}, Module[{x = a, y = b}, While[y != 0, {x, y} = {y, Mod[x, y]}] ; x], -PythonizedCode-] | ||
| >> gdc[18, 81] | ||
| = 9. | ||
| """ | ||
|
|
||
| attributes = A_HOLD_ALL | A_PROTECTED | ||
|
|
@@ -167,7 +174,23 @@ def to_sympy(self, *args, **kwargs): | |
| raise NotImplementedError | ||
|
|
||
| def __hash__(self): | ||
| return hash(("CompiledCode", ctypes.addressof(self.cfunc))) # XXX hack | ||
| cfunc = self.cfunc | ||
| if cfunc is None: | ||
| hash( | ||
| ( | ||
| "CompiledCode", | ||
| None, | ||
| ) | ||
| ) # XXX hack | ||
| try: | ||
| return hash(("CompiledCode", ctypes.addressof(cfunc))) # XXX hack | ||
| except TypeError: | ||
| return hash( | ||
| ( | ||
| "Pythonized-function", | ||
| cfunc, | ||
| ) | ||
| ) | ||
|
|
||
| def atom_to_boxes(self, f, evaluation: Evaluation): | ||
| return CompiledCodeBox(String(self.__str__()), evaluation=evaluation) | ||
|
|
@@ -191,6 +214,7 @@ class CompiledFunction(Builtin): | |
|
|
||
| """ | ||
|
|
||
| attributes = A_HOLD_ALL | A_PROTECTED | A_N_HOLD_ALL | A_READ_PROTECTED | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These attributes are very important: they prevent the evaluation of the original compiled expression. |
||
| messages = {"argerr": "Invalid argument `1` should be Integer, Real or boolean."} | ||
| summary_text = "A CompiledFunction object." | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,8 @@ | |
|
|
||
| from typing import Callable, Optional, Tuple | ||
|
|
||
| from mathics.core.definitions import SIDE_EFFECT_BUILTINS, Definition | ||
| from mathics.core.element import BaseElement, EvalMixin | ||
| from mathics.core.evaluation import Evaluation | ||
| from mathics.core.expression import Expression, from_python | ||
| from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue | ||
|
|
@@ -44,6 +46,33 @@ def __init__(self, var): | |
| self.var = var | ||
|
|
||
|
|
||
| def evaluate_without_side_effects( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this is the right place for this function. |
||
| expr: Expression, evaluation: Evaluation | ||
| ) -> Expression: | ||
| """ | ||
| Evaluate an expression leaving unevaluated subexpressions | ||
| related with side-effects (assignments, loops). | ||
| """ | ||
| definitions = evaluation.definitions | ||
| # Temporarily remove the builtin definitions | ||
| # of symbols with side effects | ||
| for name, defin in SIDE_EFFECT_BUILTINS.items(): | ||
| # Change the definition by a temporal definition setting | ||
| # just the name and the attributes. | ||
| definitions.builtin[name] = Definition( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe these definitions can be stored in some place, and then each time we compile something, bring them up, instead of constructing them each time. |
||
| name, attributes=defin.attributes, builtin=defin.builtin | ||
| ) | ||
| definitions.clear_cache(name) | ||
| try: | ||
| result = expr.evaluate(evaluation) | ||
| finally: | ||
| # Restore the definitions | ||
| for name, defin in SIDE_EFFECT_BUILTINS.items(): | ||
| definitions.builtin[name] = defin | ||
| definitions.clear_cache(name) | ||
| return result if result is not None else expr | ||
|
|
||
|
|
||
| def expression_to_callable( | ||
| expr: Expression, | ||
| args: Optional[list] = None, | ||
|
|
@@ -56,6 +85,9 @@ def expression_to_callable( | |
| args: a list of CompileArg elements | ||
| evaluation: an Evaluation object used if the llvm compilation fails | ||
| """ | ||
| if evaluation is not None: | ||
| expr = evaluate_without_side_effects(expr, evaluation) | ||
|
|
||
| try: | ||
| cfunc = _compile(expr, args) if (use_llvm and args is not None) else None | ||
| except CompileError: | ||
|
|
@@ -67,10 +99,13 @@ def expression_to_callable( | |
| try: | ||
|
|
||
| def _pythonized_mathics_expr(*x): | ||
| from mathics.eval.scoping import dynamic_scoping | ||
|
|
||
| inner_evaluation = Evaluation(definitions=evaluation.definitions) | ||
| x_mathics = (from_python(u) for u in x[: len(args)]) | ||
| vars = dict(list(zip([a.name for a in args], x_mathics))) | ||
| pyexpr = expr.replace_vars(vars) | ||
| vars = {a.name: from_python(u) for a, u in zip(args, x[: len(args)])} | ||
| pyexpr = dynamic_scoping( | ||
| lambda ev: expr.evaluate(ev), vars, inner_evaluation | ||
| ) | ||
| pyexpr = eval_N(pyexpr, inner_evaluation) | ||
| res = pyexpr.to_python() | ||
| return res | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test was wrong in many ways. On the one hand, this expression shows the following warning in WMA:
Then, if we try to evaluate the compiled function, we get more errors, and an unfinished loop.
The expected result is also wrong in another way: it expects the wrong behaviour, where the expression is fully evaluated before compiling, to get
a. The new test is something that actually works in WMA, and produces the expected behavior.