-
-
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 all commits
45c302b
36837e4
969a232
abe1bbd
66c6c6a
8e8d5cd
d4696d0
bd58f4e
4c10bf9
0102928
64d7790
8f50b4e
dc91eed
af8fb0a
88d07da
a95b65f
2ebd8ef
1e1e02b
07fb550
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 Complex, Integer, Rational, Real, 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 ( | ||
|
|
@@ -73,9 +78,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 | ||
|
|
@@ -174,15 +181,23 @@ def to_sympy(self, *args, **kwargs): | |
| raise NotImplementedError | ||
|
|
||
| def __hash__(self): | ||
| cfunc = self.cfunc | ||
| if cfunc is None: | ||
| hash( | ||
| ( | ||
| "CompiledCode", | ||
| None, | ||
| ) | ||
| ) # XXX hack | ||
| try: | ||
| return hash(("CompiledCode", ctypes.addressof(self.cfunc))) # XXX hack | ||
| return hash(("CompiledCode", ctypes.addressof(cfunc))) # XXX hack | ||
| except TypeError: | ||
| return hash( | ||
| ( | ||
| "CompiledCode", | ||
| self.cfunc, | ||
| "Pythonized-function", | ||
| cfunc, | ||
| ) | ||
| ) # XXX hack | ||
| ) | ||
|
|
||
| def atom_to_boxes(self, f, evaluation: Evaluation): | ||
| return CompiledCodeBox(String(self.__str__()), evaluation=evaluation) | ||
|
|
@@ -198,14 +213,15 @@ class CompiledFunction(Builtin): | |
| </dl> | ||
|
|
||
| >> sqr = Compile[{x}, x x] | ||
| = CompiledFunction[{x}, x ^ 2, ...] | ||
| = CompiledFunction[{x}, x x, ...] | ||
| >> Head[sqr] | ||
| = CompiledFunction | ||
| >> sqr[2] | ||
| = 4. | ||
|
|
||
| """ | ||
|
|
||
| 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, Complex or boolean.", | ||
| "cfsa": "Argument `1` at position `2` should be a `3`.", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,10 +3,7 @@ | |
|
|
||
| import numpy | ||
|
|
||
| from mathics.core.convert.lambdify import ( | ||
| CompileError as LambdifyCompileError, | ||
| lambdify_compile, | ||
| ) | ||
| from mathics.core.definitions import SIDE_EFFECT_BUILTINS, Definition | ||
| from mathics.core.evaluation import Evaluation | ||
| from mathics.core.expression import Expression, from_python | ||
| from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue | ||
|
|
@@ -53,6 +50,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.
Member
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. The two obvious places to start alternate representations or approximate computations of an expression (where a symbol is considered to be an expression) are either inside the compound Expression object or inside a definitions entry. I suspect we'll need to make a pass over the definitions table at some point. As with so many other things, it is one of those dark areas where, I suspect, things are a bit unconventional from both the conventional compiler standpoint as well as how WMA thinks of scoping. |
||
| 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_llvm( | ||
| expr: Expression, | ||
| args: Optional[list] = None, | ||
|
|
@@ -64,10 +88,36 @@ def expression_to_llvm( | |
| 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: | ||
| return _compile(expr, args) if (USE_LLVM and args is not None) else None | ||
| except CompileError: | ||
| return None | ||
| cfunc = None | ||
|
|
||
| if cfunc is None: | ||
| if evaluation is None: | ||
| raise CompileError | ||
| try: | ||
|
|
||
| def _pythonized_mathics_expr(*x): | ||
| from mathics.eval.scoping import dynamic_scoping | ||
|
|
||
| inner_evaluation = Evaluation(definitions=evaluation.definitions) | ||
| 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 | ||
|
|
||
| # TODO: check if we can use numba to compile this... | ||
| cfunc = _pythonized_mathics_expr | ||
| except Exception: | ||
| cfunc = None | ||
| return cfunc | ||
|
|
||
|
|
||
| def expression_to_python_function( | ||
|
|
@@ -141,6 +191,11 @@ def expression_to_callable_and_args( | |
| expr: A Mathics Expression object | ||
| vars: a list of Symbols or Mathics Lists of the form {Symbol, Type} | ||
| """ | ||
| from mathics.core.convert.lambdify import ( | ||
| CompileError as LambdifyCompileError, | ||
| lambdify_compile, | ||
| ) | ||
|
|
||
| args = collect_args(vars) | ||
|
|
||
| # If vectorize is requested, first, try to lambdify the expression: | ||
|
|
||
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.