From 434f61816e8bd5261c6a6d9c513e6a280f91c0df Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 30 Nov 2025 20:28:20 -0300 Subject: [PATCH 01/13] rename plot_compile as lambdify_compile, and move it close to other code conversion functions --- .../drawing/plot_compile.py => core/convert/lambdify.py} | 2 +- mathics/eval/drawing/plot3d_vectorized.py | 2 +- test/builtin/drawing/test_plot_compile.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename mathics/{eval/drawing/plot_compile.py => core/convert/lambdify.py} (98%) diff --git a/mathics/eval/drawing/plot_compile.py b/mathics/core/convert/lambdify.py similarity index 98% rename from mathics/eval/drawing/plot_compile.py rename to mathics/core/convert/lambdify.py index 1cfaf2066..19a7c0f4c 100644 --- a/mathics/eval/drawing/plot_compile.py +++ b/mathics/core/convert/lambdify.py @@ -43,7 +43,7 @@ class CompileError(Exception): pass -def plot_compile(evaluation, expr, names, debug=0): +def lambdify_compile(evaluation, expr, names, debug=0): """Compile the specified expression as a function of the given names""" if debug >= 2: diff --git a/mathics/eval/drawing/plot3d_vectorized.py b/mathics/eval/drawing/plot3d_vectorized.py index d08c50f3d..05e1fa6be 100644 --- a/mathics/eval/drawing/plot3d_vectorized.py +++ b/mathics/eval/drawing/plot3d_vectorized.py @@ -13,7 +13,7 @@ from mathics.core.systemsymbols import SymbolNone, SymbolRGBColor from mathics.timing import Timer -from .plot_compile import plot_compile +from mathics.core.convert.lambdify import lambdify_compile as plot_compile from .util import GraphicsGenerator diff --git a/test/builtin/drawing/test_plot_compile.py b/test/builtin/drawing/test_plot_compile.py index e9ae911b8..83fac2c16 100644 --- a/test/builtin/drawing/test_plot_compile.py +++ b/test/builtin/drawing/test_plot_compile.py @@ -19,7 +19,7 @@ from mathics.core.convert.python import from_python from mathics.core.expression import Expression -from mathics.eval.drawing.plot_compile import plot_compile +from mathics.core.convert.lambdify import lambdify_compile # # Each test specifies: @@ -36,7 +36,7 @@ # not registered Bultin is not registered # Try making it a subclass of SympyFunction. # '...' is not defined Sympy expects function '...' to be defined. -# Look into adding it in plot_compile.py +# Look into adding it in lambdify_compile.py # TypeError Look into builtin.to_sympy where it catches TypeError # common test case for Round, Floor, Ceiling, IntegerPart, FractionalPart @@ -421,7 +421,7 @@ def failure(name, msg): # compile function try: expr = session.parse(def_expr) - fun = plot_compile(session.evaluation, expr, parms, debug) + fun = lambdify_compile(session.evaluation, expr, parms, debug) except Exception as oops: failure(name, f"compilaton failed: {oops}") From 9912b72b718d7322b643f6c2e8e41ff9b2885ebf Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 30 Nov 2025 20:29:12 -0300 Subject: [PATCH 02/13] rename tests --- .../convert/test_lambdify_compile.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{builtin/drawing/test_plot_compile.py => core/convert/test_lambdify_compile.py} (100%) diff --git a/test/builtin/drawing/test_plot_compile.py b/test/core/convert/test_lambdify_compile.py similarity index 100% rename from test/builtin/drawing/test_plot_compile.py rename to test/core/convert/test_lambdify_compile.py From 26bcaf15e01c5bbaaf18c2b6372bd898dfec16dd Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 1 Dec 2025 00:39:45 -0300 Subject: [PATCH 03/13] using sympy lambdify for compilation --- mathics/builtin/compilation.py | 36 ++++--- mathics/compile/base.py | 3 + mathics/core/convert/function.py | 160 +++++++++++++++++++------------ mathics/core/convert/lambdify.py | 2 +- 4 files changed, 129 insertions(+), 72 deletions(-) diff --git a/mathics/builtin/compilation.py b/mathics/builtin/compilation.py index 09b56fa3e..70e053237 100644 --- a/mathics/builtin/compilation.py +++ b/mathics/builtin/compilation.py @@ -11,7 +11,7 @@ from types import FunctionType from mathics.builtin.box.compilation import CompiledCodeBox -from mathics.core.atoms import Integer, String +from mathics.core.atoms import Complex, Integer, Real, String from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED from mathics.core.builtin import Builtin from mathics.core.convert.expression import to_mathics_list @@ -83,7 +83,6 @@ class Compile(Builtin): def eval(self, vars, expr, evaluation: Evaluation): "Compile[vars_, expr_]" - if not vars.has_form("List", None): evaluation.message("Compile", "invars") return @@ -167,7 +166,11 @@ def to_sympy(self, *args, **kwargs): raise NotImplementedError def __hash__(self): - return hash(("CompiledCode", ctypes.addressof(self.cfunc))) # XXX hack + try: + return hash(("CompiledCode", ctypes.addressof(self.cfunc))) # XXX hack + except TypeError: + return hash(("CompiledCode", self.cfunc,)) # XXX hack + def atom_to_boxes(self, f, evaluation: Evaluation): return CompiledCodeBox(String(self.__str__()), evaluation=evaluation) @@ -191,27 +194,38 @@ class CompiledFunction(Builtin): """ - messages = {"argerr": "Invalid argument `1` should be Integer, Real or boolean."} + messages = {"argerr": "Invalid argument `1` should be Integer, Real, Complex or boolean."} summary_text = "A CompiledFunction object." def eval(self, argnames, expr, code, args, evaluation: Evaluation): "CompiledFunction[argnames_, expr_, code_CompiledCode][args__]" - argseq = args.get_sequence() if len(argseq) != len(code.args): return py_args = [] - for arg in argseq: - if isinstance(arg, Integer): - py_args.append(arg.get_int_value()) + args_spec = code.args or [] + if len(args_spec)!= len(argseq): + evaluation.mesage("CompiledFunction","cfct", Integer(len(argseq)), Integer(len(args_spec))) + return + for arg, spec in zip(argseq, args_spec): + # TODO: check if the types are consistent. + # If not, show a message. + if isinstance(arg, (Integer, Real, Complex)): + val = arg.value elif arg.sameQ(SymbolTrue): - py_args.append(True) + val = True elif arg.sameQ(SymbolFalse): - py_args.append(False) + val = False else: - py_args.append(arg.round_to_float(evaluation)) + val = arg.to_python() + try: + val = spec.type(val) + except TypeError: + # Fallback by replace values in expr? + return + py_args.append(val) try: result = code.cfunc(*py_args) except (TypeError, ctypes.ArgumentError): diff --git a/mathics/compile/base.py b/mathics/compile/base.py index d9b213242..d3650a837 100644 --- a/mathics/compile/base.py +++ b/mathics/compile/base.py @@ -10,3 +10,6 @@ class CompileArg: def __init__(self, name, type): self.name = name self.type = type + + def __repr__(self): + return f"{self.name}:{self.type}" diff --git a/mathics/core/convert/function.py b/mathics/core/convert/function.py index 0250b04c6..b3acdb5f2 100644 --- a/mathics/core/convert/function.py +++ b/mathics/core/convert/function.py @@ -1,37 +1,39 @@ # -*- coding: utf-8 -*- +import numpy +from typing import Callable, List, Optional, Tuple -from typing import Callable, Optional, Tuple from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression, from_python from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue -from mathics.core.systemsymbols import SymbolBlank, SymbolInteger, SymbolReal +from mathics.core.systemsymbols import SymbolBlank, SymbolInteger, SymbolReal, SymbolComplex, SymbolOr from mathics.eval.nevaluator import eval_N +from mathics.core.convert.lambdify import lambdify_compile, CompileError as LambdifyCompileError + + +PERMITTED_TYPES = { + Expression(SymbolBlank, SymbolInteger): int, + Expression(SymbolBlank, SymbolReal): float, + Expression(SymbolBlank, SymbolComplex): complex, + Expression(SymbolOr, SymbolTrue, SymbolFalse): bool, + Expression(SymbolOr, SymbolFalse, SymbolTrue): bool, +} + try: from mathics.compile import CompileArg, CompileError, _compile from mathics.compile.types import bool_type, int_type, real_type - use_llvm = True + USE_LLVM = True # _Complex not implemented - permitted_types = { - Expression(SymbolBlank, SymbolInteger): int_type, - Expression(SymbolBlank, SymbolReal): real_type, - SymbolTrue: bool_type, - SymbolFalse: bool_type, + LLVM_TYPE_TRANSLATION = { + int: int_type, + float: real_type, + bool: bool_type, } except ImportError: - use_llvm = False - bool_type = bool - int_type = int - real_type = float + USE_LLVM = False - permitted_types = { - Expression(SymbolBlank, SymbolInteger): int, - Expression(SymbolBlank, SymbolReal): float, - SymbolTrue: bool, - SymbolFalse: bool, - } class CompileDuplicateArgName(Exception): @@ -44,56 +46,53 @@ def __init__(self, var): self.var = var -def expression_to_callable( - expr: Expression, - args: Optional[list] = None, - evaluation: Optional[Evaluation] = None, -) -> Optional[Callable]: + +def expression_to_llvm(expr: Expression, args:Optional[list]=None, evaluation: Optional[Evaluation]=None): """ - Return a Python callable from an expression. If llvm is available, - tries to produce llvm code. Otherwise, returns a Python function. + Convert an expression to LLVM code. None if it fails. expr: Expression args: a list of CompileArg elements evaluation: an Evaluation object used if the llvm compilation fails """ try: - cfunc = _compile(expr, args) if (use_llvm and args is not None) else None + return _compile(expr, args) if (USE_LLVM and args is not None) else None except CompileError: - cfunc = None - - if cfunc is None: - if evaluation is None: - raise CompileError - try: - - def _pythonized_mathics_expr(*x): - 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) - pyexpr = eval_N(pyexpr, inner_evaluation) - res = pyexpr.to_python() - return res + return None - # TODO: check if we can use numba to compile this... - cfunc = _pythonized_mathics_expr - except Exception: - cfunc = None - return cfunc - -def expression_to_callable_and_args( - expr: Expression, - vars: Optional[list] = None, - evaluation: Optional[Evaluation] = None, -) -> Tuple[Optional[Callable], Optional[list]]: +def expression_to_python_function( + expr: Expression, + args: Optional[list] = None, + evaluation: Optional[Evaluation] = None, +) -> Optional[Callable]: """ - Return a tuple of Python callable and a list of CompileArgs. - expr: A Mathics Expression object - vars: a list of Symbols or Mathics Lists of the form {Symbol, Type} + Return a Python function from an expression. + expr: Expression + args: a list of CompileArg elements + evaluation: an Evaluation object used if the llvm compilation fails + """ + try: + def _pythonized_mathics_expr(*x): + 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) + pyexpr = eval_N(pyexpr, inner_evaluation) + res = pyexpr.to_python() + return res + + # TODO: check if we can use numba to compile this... + return _pythonized_mathics_expr + except Exception: + return None + + +def collect_args(vars)->Optional[List[CompileArg]]: + """ + Convert a List expression into a list of CompileArg objects. """ if vars is None: - args = None + return None else: args = [] names = [] @@ -101,12 +100,12 @@ def expression_to_callable_and_args( if isinstance(var, Symbol): symb = var name = symb.get_name() - typ = real_type + typ = float elif var.has_form("List", 2): symb, typ = var.elements - if isinstance(symb, Symbol) and typ in permitted_types: + if isinstance(symb, Symbol) and typ in PERMITTED_TYPES: name = symb.get_name() - typ = permitted_types[typ] + typ = PERMITTED_TYPES[typ] else: raise CompileWrongArgType(var) else: @@ -116,5 +115,46 @@ def expression_to_callable_and_args( raise CompileDuplicateArgName(symb) names.append(name) args.append(CompileArg(name, typ)) + return args + + + +def expression_to_callable_and_args( + expr: Expression, + vars: Optional[list] = None, + evaluation: Optional[Evaluation] = None, + debug:int = 0, + vectorize=False +) -> Tuple[Callable, Optional[list]]: + """ + Return a tuple of Python callable and a list of CompileArgs. + expr: A Mathics Expression object + vars: a list of Symbols or Mathics Lists of the form {Symbol, Type} + """ + args = collect_args(vars) + + # First, try to lambdify the expression: + try: + cfunc = lambdify_compile(evaluation, expr, [arg.name for arg in args], debug) + # lambdify_compile returns an already vectorized expression. + return cfunc, args + except LambdifyCompileError: + pass + + # Then, try with llvm if available + if USE_LLVM: + try: + llvm_args = None if args is None else [CompileArg(compile_arg.name, LLVM_TYPE_TRANSLATION[compile_arg.typ]) for compile_arg in args] + cfunc = expression_to_llvm(expr, llvm_args, evaluation) + if vectorize: + cfunc = numpy.vectorize(cfunc) + return cfunc, llvm_args + except KeyError: + pass + + # Last resource + cfunc = expression_to_python_function(expr, args, evaluation) + if vectorize: + cfunc = numpy.vectorize(cfunc) + return cfunc, args - return expression_to_callable(expr, args, evaluation), args diff --git a/mathics/core/convert/lambdify.py b/mathics/core/convert/lambdify.py index 19a7c0f4c..e97c2c672 100644 --- a/mathics/core/convert/lambdify.py +++ b/mathics/core/convert/lambdify.py @@ -81,7 +81,7 @@ def lambdify_compile(evaluation, expr, names, debug=0): # Use numpy and scipy to do the evaluation so that operations are vectorized. # Augment the default numpy mappings with some additional ones not handled by default. try: - symbols = sympy.symbols(names) + symbols = sympy.symbols(tuple(strip_context(name) for name in names)) # compiled_function = sympy.lambdify(symbols, sympy_expr, mappings) compiled_function = sympy.lambdify( symbols, sympy_expr, modules=["numpy", "scipy"] From a462ad7e73d9694eea040ca2836888a6b89c7d82 Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 1 Dec 2025 00:41:42 -0300 Subject: [PATCH 04/13] black --- mathics/core/convert/function.py | 66 ++++++++++++++-------- mathics/eval/drawing/plot3d_vectorized.py | 2 +- test/core/convert/test_lambdify_compile.py | 2 +- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/mathics/core/convert/function.py b/mathics/core/convert/function.py index b3acdb5f2..06285c456 100644 --- a/mathics/core/convert/function.py +++ b/mathics/core/convert/function.py @@ -1,22 +1,30 @@ # -*- coding: utf-8 -*- -import numpy from typing import Callable, List, Optional, Tuple +import numpy +from mathics.core.convert.lambdify import ( + CompileError as LambdifyCompileError, + lambdify_compile, +) from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression, from_python from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue -from mathics.core.systemsymbols import SymbolBlank, SymbolInteger, SymbolReal, SymbolComplex, SymbolOr +from mathics.core.systemsymbols import ( + SymbolBlank, + SymbolComplex, + SymbolInteger, + SymbolOr, + SymbolReal, +) from mathics.eval.nevaluator import eval_N -from mathics.core.convert.lambdify import lambdify_compile, CompileError as LambdifyCompileError - PERMITTED_TYPES = { - Expression(SymbolBlank, SymbolInteger): int, - Expression(SymbolBlank, SymbolReal): float, - Expression(SymbolBlank, SymbolComplex): complex, - Expression(SymbolOr, SymbolTrue, SymbolFalse): bool, - Expression(SymbolOr, SymbolFalse, SymbolTrue): bool, + Expression(SymbolBlank, SymbolInteger): int, + Expression(SymbolBlank, SymbolReal): float, + Expression(SymbolBlank, SymbolComplex): complex, + Expression(SymbolOr, SymbolTrue, SymbolFalse): bool, + Expression(SymbolOr, SymbolFalse, SymbolTrue): bool, } @@ -35,7 +43,6 @@ USE_LLVM = False - class CompileDuplicateArgName(Exception): def __init__(self, symb): self.symb = symb @@ -46,8 +53,11 @@ def __init__(self, var): self.var = var - -def expression_to_llvm(expr: Expression, args:Optional[list]=None, evaluation: Optional[Evaluation]=None): +def expression_to_llvm( + expr: Expression, + args: Optional[list] = None, + evaluation: Optional[Evaluation] = None, +): """ Convert an expression to LLVM code. None if it fails. expr: Expression @@ -61,17 +71,18 @@ def expression_to_llvm(expr: Expression, args:Optional[list]=None, evaluation: O def expression_to_python_function( - expr: Expression, - args: Optional[list] = None, - evaluation: Optional[Evaluation] = None, + expr: Expression, + args: Optional[list] = None, + evaluation: Optional[Evaluation] = None, ) -> Optional[Callable]: """ - Return a Python function from an expression. + Return a Python function from an expression. expr: Expression args: a list of CompileArg elements evaluation: an Evaluation object used if the llvm compilation fails """ try: + def _pythonized_mathics_expr(*x): inner_evaluation = Evaluation(definitions=evaluation.definitions) x_mathics = (from_python(u) for u in x[: len(args)]) @@ -87,7 +98,7 @@ def _pythonized_mathics_expr(*x): return None -def collect_args(vars)->Optional[List[CompileArg]]: +def collect_args(vars) -> Optional[List[CompileArg]]: """ Convert a List expression into a list of CompileArg objects. """ @@ -116,15 +127,14 @@ def collect_args(vars)->Optional[List[CompileArg]]: names.append(name) args.append(CompileArg(name, typ)) return args - def expression_to_callable_and_args( expr: Expression, vars: Optional[list] = None, evaluation: Optional[Evaluation] = None, - debug:int = 0, - vectorize=False + debug: int = 0, + vectorize=False, ) -> Tuple[Callable, Optional[list]]: """ Return a tuple of Python callable and a list of CompileArgs. @@ -143,18 +153,24 @@ def expression_to_callable_and_args( # Then, try with llvm if available if USE_LLVM: - try: - llvm_args = None if args is None else [CompileArg(compile_arg.name, LLVM_TYPE_TRANSLATION[compile_arg.typ]) for compile_arg in args] + try: + llvm_args = ( + None + if args is None + else [ + CompileArg(compile_arg.name, LLVM_TYPE_TRANSLATION[compile_arg.typ]) + for compile_arg in args + ] + ) cfunc = expression_to_llvm(expr, llvm_args, evaluation) if vectorize: cfunc = numpy.vectorize(cfunc) return cfunc, llvm_args except KeyError: pass - + # Last resource cfunc = expression_to_python_function(expr, args, evaluation) if vectorize: cfunc = numpy.vectorize(cfunc) - return cfunc, args - + return cfunc, args diff --git a/mathics/eval/drawing/plot3d_vectorized.py b/mathics/eval/drawing/plot3d_vectorized.py index 05e1fa6be..3d7cb6d06 100644 --- a/mathics/eval/drawing/plot3d_vectorized.py +++ b/mathics/eval/drawing/plot3d_vectorized.py @@ -8,12 +8,12 @@ import numpy as np from mathics.builtin.colors.color_internals import convert_color +from mathics.core.convert.lambdify import lambdify_compile as plot_compile from mathics.core.evaluation import Evaluation from mathics.core.symbols import strip_context from mathics.core.systemsymbols import SymbolNone, SymbolRGBColor from mathics.timing import Timer -from mathics.core.convert.lambdify import lambdify_compile as plot_compile from .util import GraphicsGenerator diff --git a/test/core/convert/test_lambdify_compile.py b/test/core/convert/test_lambdify_compile.py index 83fac2c16..a8bd8285e 100644 --- a/test/core/convert/test_lambdify_compile.py +++ b/test/core/convert/test_lambdify_compile.py @@ -17,9 +17,9 @@ import numpy as np +from mathics.core.convert.lambdify import lambdify_compile from mathics.core.convert.python import from_python from mathics.core.expression import Expression -from mathics.core.convert.lambdify import lambdify_compile # # Each test specifies: From c4cc3968e27f195c239e831770a747433a1097a0 Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 1 Dec 2025 00:42:54 -0300 Subject: [PATCH 05/13] black --- mathics/builtin/compilation.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/mathics/builtin/compilation.py b/mathics/builtin/compilation.py index 70e053237..a271b8a15 100644 --- a/mathics/builtin/compilation.py +++ b/mathics/builtin/compilation.py @@ -169,8 +169,12 @@ def __hash__(self): try: return hash(("CompiledCode", ctypes.addressof(self.cfunc))) # XXX hack except TypeError: - return hash(("CompiledCode", self.cfunc,)) # XXX hack - + return hash( + ( + "CompiledCode", + self.cfunc, + ) + ) # XXX hack def atom_to_boxes(self, f, evaluation: Evaluation): return CompiledCodeBox(String(self.__str__()), evaluation=evaluation) @@ -194,7 +198,9 @@ class CompiledFunction(Builtin): """ - messages = {"argerr": "Invalid argument `1` should be Integer, Real, Complex or boolean."} + messages = { + "argerr": "Invalid argument `1` should be Integer, Real, Complex or boolean." + } summary_text = "A CompiledFunction object." def eval(self, argnames, expr, code, args, evaluation: Evaluation): @@ -206,8 +212,13 @@ def eval(self, argnames, expr, code, args, evaluation: Evaluation): py_args = [] args_spec = code.args or [] - if len(args_spec)!= len(argseq): - evaluation.mesage("CompiledFunction","cfct", Integer(len(argseq)), Integer(len(args_spec))) + if len(args_spec) != len(argseq): + evaluation.mesage( + "CompiledFunction", + "cfct", + Integer(len(argseq)), + Integer(len(args_spec)), + ) return for arg, spec in zip(argseq, args_spec): # TODO: check if the types are consistent. @@ -225,7 +236,7 @@ def eval(self, argnames, expr, code, args, evaluation: Evaluation): except TypeError: # Fallback by replace values in expr? return - py_args.append(val) + py_args.append(val) try: result = code.cfunc(*py_args) except (TypeError, ctypes.ArgumentError): From ada6e01b6292007d389c5f9b869c701019b0f929 Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 1 Dec 2025 06:46:05 -0300 Subject: [PATCH 06/13] fix typo --- mathics/core/convert/function.py | 2 +- mathics/eval/drawing/plot3d_vectorized.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mathics/core/convert/function.py b/mathics/core/convert/function.py index 06285c456..e3c1a24ce 100644 --- a/mathics/core/convert/function.py +++ b/mathics/core/convert/function.py @@ -158,7 +158,7 @@ def expression_to_callable_and_args( None if args is None else [ - CompileArg(compile_arg.name, LLVM_TYPE_TRANSLATION[compile_arg.typ]) + CompileArg(compile_arg.name, LLVM_TYPE_TRANSLATION[compile_arg.type]) for compile_arg in args ] ) diff --git a/mathics/eval/drawing/plot3d_vectorized.py b/mathics/eval/drawing/plot3d_vectorized.py index 3d7cb6d06..cc89f7d83 100644 --- a/mathics/eval/drawing/plot3d_vectorized.py +++ b/mathics/eval/drawing/plot3d_vectorized.py @@ -8,7 +8,7 @@ import numpy as np from mathics.builtin.colors.color_internals import convert_color -from mathics.core.convert.lambdify import lambdify_compile as plot_compile +from mathics.core.convert.lambdify import lambdify_compile from mathics.core.evaluation import Evaluation from mathics.core.symbols import strip_context from mathics.core.systemsymbols import SymbolNone, SymbolRGBColor @@ -39,7 +39,7 @@ def make_plot(plot_options, evaluation: Evaluation, dim: int, is_complex: bool, # compile the functions with Timer("compile"): compiled_functions = [ - plot_compile(evaluation, function, names) + lambdify_compile(evaluation, function, names) for function in plot_options.functions ] From d6bb694b7381d94c74202f81698c0202290f024c Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 1 Dec 2025 07:02:56 -0300 Subject: [PATCH 07/13] fix typing --- mathics/core/convert/function.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/mathics/core/convert/function.py b/mathics/core/convert/function.py index e3c1a24ce..56e71e826 100644 --- a/mathics/core/convert/function.py +++ b/mathics/core/convert/function.py @@ -108,15 +108,17 @@ def collect_args(vars) -> Optional[List[CompileArg]]: args = [] names = [] for var in vars: + name: str + t_typ: type if isinstance(var, Symbol): symb = var name = symb.get_name() - typ = float + t_typ = float elif var.has_form("List", 2): symb, typ = var.elements if isinstance(symb, Symbol) and typ in PERMITTED_TYPES: name = symb.get_name() - typ = PERMITTED_TYPES[typ] + t_typ = PERMITTED_TYPES[typ] else: raise CompileWrongArgType(var) else: @@ -125,7 +127,7 @@ def collect_args(vars) -> Optional[List[CompileArg]]: if name in names: raise CompileDuplicateArgName(symb) names.append(name) - args.append(CompileArg(name, typ)) + args.append(CompileArg(name, t_typ)) return args @@ -145,7 +147,9 @@ def expression_to_callable_and_args( # First, try to lambdify the expression: try: - cfunc = lambdify_compile(evaluation, expr, [arg.name for arg in args], debug) + cfunc = lambdify_compile( + evaluation, expr, [] if args is None else [arg.name for arg in args], debug + ) # lambdify_compile returns an already vectorized expression. return cfunc, args except LambdifyCompileError: @@ -158,7 +162,9 @@ def expression_to_callable_and_args( None if args is None else [ - CompileArg(compile_arg.name, LLVM_TYPE_TRANSLATION[compile_arg.type]) + CompileArg( + compile_arg.name, LLVM_TYPE_TRANSLATION[compile_arg.type] + ) for compile_arg in args ] ) From ae4a3b270231db9ea2e799905c23525bb1e297ab Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 1 Dec 2025 08:56:37 -0300 Subject: [PATCH 08/13] Try to use llvm for non vectorized functions. Improve messages and error handling. --- mathics/builtin/compilation.py | 68 ++++++++++++++++++++++----- mathics/core/convert/function.py | 35 ++++++++------ test/builtin/test_comparison.py | 80 +++++++++++++++++--------------- test/builtin/test_compilation.py | 2 +- 4 files changed, 119 insertions(+), 66 deletions(-) diff --git a/mathics/builtin/compilation.py b/mathics/builtin/compilation.py index a271b8a15..b39e7c0bb 100644 --- a/mathics/builtin/compilation.py +++ b/mathics/builtin/compilation.py @@ -33,6 +33,14 @@ sort_order = "mathics.builtin.code-compilation" +NAME_OF_TYPE = { + bool: String("True of False"), + int: String("integer"), + float: String("machine-size real number"), + complex: String("machine-size complex number"), +} + + class Compile(Builtin): """ :WMA link:https://reference.wolfram.com/language/ref/Compile.html @@ -199,7 +207,8 @@ class CompiledFunction(Builtin): """ messages = { - "argerr": "Invalid argument `1` should be Integer, Real, Complex or boolean." + "argerr": "Invalid argument `1` should be Integer, Real, Complex or boolean.", + "cfsa": "Argument `1` at position `2` should be a `3`.", } summary_text = "A CompiledFunction object." @@ -220,21 +229,56 @@ def eval(self, argnames, expr, code, args, evaluation: Evaluation): Integer(len(args_spec)), ) return - for arg, spec in zip(argseq, args_spec): + for pos, (arg, spec) in enumerate(zip(argseq, args_spec)): # TODO: check if the types are consistent. # If not, show a message. - if isinstance(arg, (Integer, Real, Complex)): - val = arg.value - elif arg.sameQ(SymbolTrue): - val = True - elif arg.sameQ(SymbolFalse): - val = False - else: - val = arg.to_python() try: - val = spec.type(val) - except TypeError: + spec_type = spec.type + if spec_type is float: + if isinstance(arg, (Integer, Real)): + val = spec_type(arg.value) + else: + raise TypeError + elif spec_type is int: + if isinstance(arg, (Integer, Real)): + val = spec_type(arg.value) + # If arg.value was not an integer, show a message but accept it: + if val != arg.value: + evaluation.message( + "CompiledFunction", + "cfsa", + arg, + Integer(pos + 1), + NAME_OF_TYPE[spec_type], + ) + else: + raise TypeError + elif spec_type is bool: + if arg.sameQ(SymbolTrue): + val = True + elif arg.sameQ(SymbolFalse): + val = False + else: + raise TypeError + elif spec_type is complex: + if isinstance(arg, Complex): + value = arg.value + val = complex(value[0].value, value[1].value) + elif isinstance(arg, (Integer, Real)): + val = complex(arg.value) + else: + raise TypeError + else: + raise TypeError + except (ValueError, TypeError): # Fallback by replace values in expr? + evaluation.message( + "CompiledFunction", + "cfsa", + arg, + Integer(pos + 1), + NAME_OF_TYPE[spec.type], + ) return py_args.append(val) try: diff --git a/mathics/core/convert/function.py b/mathics/core/convert/function.py index 56e71e826..22fef462c 100644 --- a/mathics/core/convert/function.py +++ b/mathics/core/convert/function.py @@ -11,10 +11,10 @@ from mathics.core.expression import Expression, from_python from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue from mathics.core.systemsymbols import ( + SymbolAlternatives, SymbolBlank, SymbolComplex, SymbolInteger, - SymbolOr, SymbolReal, ) from mathics.eval.nevaluator import eval_N @@ -23,8 +23,8 @@ Expression(SymbolBlank, SymbolInteger): int, Expression(SymbolBlank, SymbolReal): float, Expression(SymbolBlank, SymbolComplex): complex, - Expression(SymbolOr, SymbolTrue, SymbolFalse): bool, - Expression(SymbolOr, SymbolFalse, SymbolTrue): bool, + Expression(SymbolAlternatives, SymbolTrue, SymbolFalse): bool, + Expression(SymbolAlternatives, SymbolFalse, SymbolTrue): bool, } @@ -120,6 +120,7 @@ def collect_args(vars) -> Optional[List[CompileArg]]: name = symb.get_name() t_typ = PERMITTED_TYPES[typ] else: + print(symb, typ, var) raise CompileWrongArgType(var) else: raise CompileWrongArgType(var) @@ -145,15 +146,18 @@ def expression_to_callable_and_args( """ args = collect_args(vars) - # First, try to lambdify the expression: - try: - cfunc = lambdify_compile( - evaluation, expr, [] if args is None else [arg.name for arg in args], debug - ) - # lambdify_compile returns an already vectorized expression. - return cfunc, args - except LambdifyCompileError: - pass + # If vectorize is requested, first, try to lambdify the expression: + if vectorize: + try: + cfunc = lambdify_compile( + evaluation, + expr, + [] if args is None else [arg.name for arg in args], + debug, + ) + return cfunc, args + except LambdifyCompileError: + pass # Then, try with llvm if available if USE_LLVM: @@ -169,9 +173,10 @@ def expression_to_callable_and_args( ] ) cfunc = expression_to_llvm(expr, llvm_args, evaluation) - if vectorize: - cfunc = numpy.vectorize(cfunc) - return cfunc, llvm_args + if cfunc is not None: + if vectorize: + cfunc = numpy.vectorize(cfunc) + return cfunc, args except KeyError: pass diff --git a/test/builtin/test_comparison.py b/test/builtin/test_comparison.py index fc1c4644c..dc40168e6 100644 --- a/test/builtin/test_comparison.py +++ b/test/builtin/test_comparison.py @@ -206,7 +206,7 @@ def test_unsameq(str_expr, str_expected): # # import subprocess # from time import sleep -# exprss = ['2 + 3*a', 'Infinity', '-Infinity', 'Sqrt[I] Infinity', 'a', '"a"', '"1 / 4"', "I", "0", '1 / 4','.25',"Sqrt[2]", "BesselJ[0, 2]", "3+2 I", "2.+ Pi I", "3+I Pi", 'TestFunction["Tengo una vaca lechera"]', "Compile[{x}, Sqrt[x]]", "Graphics[{Disk[{0,0},1]}]"] +# exprss = ['2 + 3*a', 'Infinity', '-Infinity', 'Sqrt[I] Infinity', 'a', '"a"', '"1 / 4"', "I", "0", '1 / 4','.25',"Sqrt[2]", "BesselJ[0, 2]", "3+2 I", "2.+ Pi I", "3+I Pi", 'TestFunction["Tengo una vaca lechera"]', "Compile[{x}, NonCompilableFunction[x]]", "Graphics[{Disk[{0,0},1]}]"] # pairs = sum([[(exprss[i], exprss[j]) for j in range(i+1)] for i in range(len(exprss))],[]) # tests = [] # @@ -257,8 +257,8 @@ def test_unsameq(str_expr, str_expected): ), ( "Graphics[{Disk[{0,0},1]}]", - "Compile[{x}, Sqrt[x]]", - '"-Graphics- == CompiledFunction[{x}, Sqrt[x], -PythonizedCode-]"', + "Compile[{x}, NonCompilableFunction[x]]", + '"-Graphics- == CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-]"', ), ('"1 / 4"', "2 + 3 a", '"1 / 4 == 2 + 3 a"'), ('"1 / 4"', "Infinity", '"1 / 4 == Infinity"'), @@ -349,89 +349,89 @@ def test_unsameq(str_expr, str_expected): '"TestFunction[Tengo una vaca lechera] == 3 + I Pi"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "2 + 3 a", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == 2 + 3 a"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == 2 + 3 a"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "Infinity", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == Infinity"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == Infinity"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "-Infinity", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == -Infinity"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == -Infinity"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "Sqrt[I] Infinity", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == (-1) ^ (1 / 4) Infinity"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == (-1) ^ (1 / 4) Infinity"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "a", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == a"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == a"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", '"a"', - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == a"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == a"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", '"1 / 4"', - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == 1 / 4"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == 1 / 4"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "I", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == I"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == I"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "0", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == 0"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == 0"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "1 / 4", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == 1 / 4"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == 1 / 4"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", ".25", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == 0.25"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == 0.25"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "Sqrt[2]", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == Sqrt[2]"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == Sqrt[2]"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "BesselJ[0, 2]", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == BesselJ[0, 2]"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == BesselJ[0, 2]"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "3+2 I", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == 3 + 2 I"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == 3 + 2 I"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "2.+ Pi I", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == 2. + 3.14159 I"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == 2. + 3.14159 I"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", "3+I Pi", - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == 3 + I Pi"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == 3 + I Pi"', ), ( - "Compile[{x}, Sqrt[x]]", + "Compile[{x}, NonCompilableFunction[x]]", 'TestFunction["Tengo una vaca lechera"]', - '"CompiledFunction[{x}, Sqrt[x], -PythonizedCode-] == TestFunction[Tengo una vaca lechera]"', + '"CompiledFunction[{x}, NonCompilableFunction[x], -PythonizedCode-] == TestFunction[Tengo una vaca lechera]"', ), ] @@ -560,7 +560,11 @@ def test_unsameq(str_expr, str_expected): 'TestFunction["Tengo una vaca lechera"]', "True", ), - ("Compile[{x}, Sqrt[x]]", "Compile[{x}, Sqrt[x]]", "True"), + ( + "Compile[{x}, NonCompilableFunction[x]]", + "Compile[{x}, NonCompilableFunction[x]]", + "True", + ), ("Graphics[{Disk[{0,0},1]}]", "Graphics[{Disk[{0,0},1]}]", "True"), ("3+I Pi", '"a"', '3 + I Pi == "a"'), ("2.+ Pi I", "a", "2. + 3.14159265358979 I == a"), diff --git a/test/builtin/test_compilation.py b/test/builtin/test_compilation.py index 631a4b24d..ac57b40ce 100644 --- a/test/builtin/test_compilation.py +++ b/test/builtin/test_compilation.py @@ -27,7 +27,7 @@ ("cf[4]", None, "-0.756802", None), ( "cf[x]", - ("Invalid argument x should be Integer, Real or boolean.",), + ("Argument x at position 1 should be a machine-size real number.",), "CompiledFunction[{x}, Sin[x], -CompiledCode-][x]", None, ), From db5e13db7a00364c9d3ec6e048ef175bc5f04aa8 Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 1 Dec 2025 09:00:38 -0300 Subject: [PATCH 09/13] mypy --- mathics/core/atoms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index 4884eaaf1..6647ef069 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -628,7 +628,7 @@ def element_order(self) -> tuple: of an expression. The tuple is ultimately compared lexicographically. """ - value = self._value + value: sympy.Float = self._value value, prec = float(value), value._prec # For large values, use the sympy.Float value... if math.isinf(value): From 5d225dc964506c7e16ab4df031bdcfa3304ee02a Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 1 Dec 2025 09:06:22 -0300 Subject: [PATCH 10/13] more mypy --- mathics/core/convert/function.py | 28 ++++++++++++---------------- mathics/core/expression.py | 4 ++-- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/mathics/core/convert/function.py b/mathics/core/convert/function.py index 22fef462c..f8407e229 100644 --- a/mathics/core/convert/function.py +++ b/mathics/core/convert/function.py @@ -74,28 +74,24 @@ def expression_to_python_function( expr: Expression, args: Optional[list] = None, evaluation: Optional[Evaluation] = None, -) -> Optional[Callable]: +) -> Callable: """ Return a Python function from an expression. expr: Expression args: a list of CompileArg elements evaluation: an Evaluation object used if the llvm compilation fails """ - try: - - def _pythonized_mathics_expr(*x): - 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) - pyexpr = eval_N(pyexpr, inner_evaluation) - res = pyexpr.to_python() - return res - - # TODO: check if we can use numba to compile this... - return _pythonized_mathics_expr - except Exception: - return None + def _pythonized_mathics_expr(*x): + 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) + pyexpr = eval_N(pyexpr, inner_evaluation) + res = pyexpr.to_python() + return res + + # TODO: check if we can use numba to compile this... + return _pythonized_mathics_expr def collect_args(vars) -> Optional[List[CompileArg]]: diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 0b75e6011..b527c636c 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -1789,12 +1789,12 @@ def thread(self, evaluation, head=None) -> Tuple[bool, "Expression"]: (prefix + [innerelement]) for innerelement in element.get_elements() ] - elif len(element._elements) != dim: + elif len(element.get_elements()) != dim: evaluation.message("Thread", "tdlen") return True, self else: for index in range(dim): - items[index].append(element._elements[index]) + items[index].append(element.get_elements()[index]) else: if dim is None: prefix.append(element) From 3285f7dbba591df5559930359c556ad0dd51a3e5 Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 1 Dec 2025 09:18:24 -0300 Subject: [PATCH 11/13] improve Compile interface --- mathics/builtin/compilation.py | 10 ++++++---- mathics/core/convert/function.py | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mathics/builtin/compilation.py b/mathics/builtin/compilation.py index b39e7c0bb..b656fdf13 100644 --- a/mathics/builtin/compilation.py +++ b/mathics/builtin/compilation.py @@ -11,7 +11,7 @@ from types import FunctionType from mathics.builtin.box.compilation import CompiledCodeBox -from mathics.core.atoms import Complex, Integer, Real, String +from mathics.core.atoms import Complex, Integer, Rational, Real, String from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED from mathics.core.builtin import Builtin from mathics.core.convert.expression import to_mathics_list @@ -235,12 +235,12 @@ def eval(self, argnames, expr, code, args, evaluation: Evaluation): try: spec_type = spec.type if spec_type is float: - if isinstance(arg, (Integer, Real)): + if isinstance(arg, (Integer, Rational, Real)): val = spec_type(arg.value) else: raise TypeError elif spec_type is int: - if isinstance(arg, (Integer, Real)): + if isinstance(arg, (Integer, Rational, Real)): val = spec_type(arg.value) # If arg.value was not an integer, show a message but accept it: if val != arg.value: @@ -264,7 +264,7 @@ def eval(self, argnames, expr, code, args, evaluation: Evaluation): if isinstance(arg, Complex): value = arg.value val = complex(value[0].value, value[1].value) - elif isinstance(arg, (Integer, Real)): + elif isinstance(arg, (Integer, Rational, Real)): val = complex(arg.value) else: raise TypeError @@ -286,4 +286,6 @@ def eval(self, argnames, expr, code, args, evaluation: Evaluation): except (TypeError, ctypes.ArgumentError): evaluation.message("CompiledFunction", "argerr", args) return + except Exception: + return return from_python(result) diff --git a/mathics/core/convert/function.py b/mathics/core/convert/function.py index f8407e229..812913f65 100644 --- a/mathics/core/convert/function.py +++ b/mathics/core/convert/function.py @@ -175,6 +175,8 @@ def expression_to_callable_and_args( return cfunc, args except KeyError: pass + except RuntimeError: + pass # Last resource cfunc = expression_to_python_function(expr, args, evaluation) From 1830fbddd71de59e01d1a436c793f1a19204d805 Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 1 Dec 2025 09:19:39 -0300 Subject: [PATCH 12/13] black --- mathics/core/convert/function.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mathics/core/convert/function.py b/mathics/core/convert/function.py index 812913f65..0b233f0ff 100644 --- a/mathics/core/convert/function.py +++ b/mathics/core/convert/function.py @@ -81,6 +81,7 @@ def expression_to_python_function( args: a list of CompileArg elements evaluation: an Evaluation object used if the llvm compilation fails """ + def _pythonized_mathics_expr(*x): inner_evaluation = Evaluation(definitions=evaluation.definitions) x_mathics = (from_python(u) for u in x[: len(args)]) From ff52d8f97d7d83d530a7e505d847cf79afbabc44 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Thu, 4 Dec 2025 08:32:23 -0300 Subject: [PATCH 13/13] adding comment about lambdify compilation side-effects --- mathics/core/convert/function.py | 2 +- mathics/core/convert/lambdify.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/mathics/core/convert/function.py b/mathics/core/convert/function.py index 0b233f0ff..7c121dd88 100644 --- a/mathics/core/convert/function.py +++ b/mathics/core/convert/function.py @@ -134,7 +134,7 @@ def expression_to_callable_and_args( vars: Optional[list] = None, evaluation: Optional[Evaluation] = None, debug: int = 0, - vectorize=False, + vectorize: bool = False, # By now, just vectorize in drawing plots. ) -> Tuple[Callable, Optional[list]]: """ Return a tuple of Python callable and a list of CompileArgs. diff --git a/mathics/core/convert/lambdify.py b/mathics/core/convert/lambdify.py index e97c2c672..4e63c50f4 100644 --- a/mathics/core/convert/lambdify.py +++ b/mathics/core/convert/lambdify.py @@ -53,6 +53,25 @@ def lambdify_compile(evaluation, expr, names, debug=0): # Evaluate the expr first in case it hasn't been already, # because some functions are not themselves sympy-enabled # if they always get rewritten to one that is. + # Comment MM: + # + # Suppose we define `F[x_,y_]:=(a=x; Do[a=a+1,{y}];a)` + # which essentially is a convoluted way to say x+y. + # + # Now, what is expected from this function is to produce + # lambda function which implements the loop, and when + # evaluated on `(1,2)` produce `3`. However, with this implementation, + # what we obtain is a function that returns its first input: + # since the evaluation of the loop fails, `a` takes the value `x`, + # and `new_expression` results in `x`. + # + # Also, a side affect is produced, since after compiling the function, + # `a` now takes the value `x`. + # + # For this reason, I guess here we need an special way of evaluating + # expressions, which avoid rules like the one associated to `Do` + # or `*Set*`. + # try: new_expr = expr.evaluate(evaluation) if new_expr: