From 0026004547598fa01792c7f19b614f3b63525c82 Mon Sep 17 00:00:00 2001 From: mmatera Date: Fri, 9 Apr 2021 10:01:19 -0300 Subject: [PATCH 01/13] starting --- mathics/builtin/algebra.py | 115 +++++++++++++++++++++++++++++++++++++ mathics/builtin/system.py | 2 +- 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/mathics/builtin/algebra.py b/mathics/builtin/algebra.py index ac62505278..997835f370 100644 --- a/mathics/builtin/algebra.py +++ b/mathics/builtin/algebra.py @@ -1404,3 +1404,118 @@ def apply(self, expr, form, h, evaluation): return Expression( "List", *[Expression(h, *[i for i in s]) for s in exponents] ) + + +class CoefficientArrays(Builtin): + """ +
+
'CoefficientArrays[$polys$, $vars$]' +
returns a list of arrays of coefficients of the variables $vars$ in the polynomial $poly$. + + """ + + + options = {"Symmetric": "False", } + messages = {"poly": "`1` is not a polynomial", } + def apply_list(self, polys, varlist, expression, options): + """%(name)s[polys_list, varlist_, OptionsPattern[]]""" + return + if polys.has_form("List", None): + polys = polys.leaves + else: + polys = [polys] + + # Expand all the polynomials before start + polys = [Expression("ExpandAll", poly).evaluate(evaluation) for poly in polys] + + if varlist.has_form("List", None): + varpat = varlist.leaves + else: + varpat = [varlist] + + degree = 0 + + def isvar(var): + # TODO: check also patterns + if term.is_atom(): + return var in varpat + # if the expression do not match, and + # is not atomic, do not decide. + return + + def term_degree(term): + degree = 0 + linear = isvar(term): + if not (linear is None): + return linear + + if term.get_head_name()=="System`Times": + for factor in term.leaves: + q = factor_degree(factor) + if factor is None: + return None + degree += q + elif term.get_head_name()=="System`Power": + return power_degree(term) + + def factor_degree(factor): + linear = isvar(factor) + if not (islinear is None): + return linear + if factor.free_of + if factor.get_head_name() == "System`Power": + return power_degree(factor) + return None + + def power_degree(factor): + if not isvar(factor.leaves[0]): + return 0 + if not isinstance(factor.leaves[1], Integer): + return None + return factor.leaves[1].get_int_value() + + + + + + for poly in polys: + if poly.is_atom(): + if degree == 0 and poly in varpat: + degree = 1 + # TODO: handle patterns + continue + elif poly.get_head_name() == "System`Plus": + for term in poly.leaves: + curr_degree = 0 + if term.get_head_name() == "System`Times": + for factor in term.leaves: + if factor.get_head_name() == "System`Power": + if isinstance(factor.leaves[1], Integer): + curr_degree = factor.leaves[1].get_int_value() + else: + evaluation.message("CoefficientArrays", "poly", poly) + + else: + elif term.get_head_name() == "System`Power": + if term.leaves[0] in varpat: + if isinstance(term.leaves[1], Integer): + curr_degree = term.leaves[1].get_int_value() + else: + evaluation.message("CoefficientArrays", "poly", poly) + elif term in vars: + curr_degree = 1 + + + + + + elif poly.get_head_name() not in ("System`Plus", "System`Times", "System`Power")): + evaluation.message("CoefficientArrays", "poly", poly) + return + + + + + + + diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index 81826ac8d1..d7f335ac77 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -449,7 +449,7 @@ class VersionNumber(Predefined): """ name = "$VersionNumber" - value = 6.0 + value = 10.0 def evaluate(self, evaluation) -> Real: # Make this be whatever the latest Mathematica release is, From 1d6b4687f4cb6bf6875507a6b4d90fbde59c0deb Mon Sep 17 00:00:00 2001 From: mmatera Date: Fri, 9 Apr 2021 17:51:02 -0300 Subject: [PATCH 02/13] deactivate CoefficientArrays --- mathics/builtin/algebra.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/algebra.py b/mathics/builtin/algebra.py index 997835f370..0a637a6aea 100644 --- a/mathics/builtin/algebra.py +++ b/mathics/builtin/algebra.py @@ -1414,11 +1414,11 @@ class CoefficientArrays(Builtin): """ - +""" options = {"Symmetric": "False", } messages = {"poly": "`1` is not a polynomial", } def apply_list(self, polys, varlist, expression, options): - """%(name)s[polys_list, varlist_, OptionsPattern[]]""" + "%(name)s[polys_list, varlist_, OptionsPattern[]]" return if polys.has_form("List", None): polys = polys.leaves @@ -1519,3 +1519,4 @@ def power_degree(factor): +""" From 3a785008c65e84570745bfc61e84a2356b4705fa Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 25 Apr 2021 22:59:32 -0300 Subject: [PATCH 03/13] Implement Collect --- CHANGES.rst | 2 +- mathics/builtin/algebra.py | 205 +++++++++++++++++++++++++++++++++++- mathics/builtin/patterns.py | 5 +- mathics/core/expression.py | 4 +- 4 files changed, 209 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 126cdb7c54..cc22d9c867 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,7 +14,7 @@ New builtins * ``Series``, ``O`` and ``SeriesData`` * ``StringReverse`` * Add all of the named colors, e.g. ``Brown`` or ``LighterMagenta``. - +* ``Collect`` Enhancements diff --git a/mathics/builtin/algebra.py b/mathics/builtin/algebra.py index 81929074dc..72d0c6af9f 100644 --- a/mathics/builtin/algebra.py +++ b/mathics/builtin/algebra.py @@ -10,7 +10,9 @@ Atom, Expression, Integer, + Integer0, Integer1, + RationalOneHalf, Number, Symbol, SymbolFalse, @@ -18,6 +20,7 @@ SymbolTrue, ) from mathics.core.convert import from_sympy, sympy_symbol_prefix +from mathics.core.rules import Pattern import sympy @@ -62,7 +65,6 @@ def _expand(expr): if kwargs["modulus"] is not None and kwargs["modulus"] <= 0: return Integer(0) - # A special case for trigonometric functions if "trig" in kwargs and kwargs["trig"]: if expr.has_form("Sin", 1): @@ -149,7 +151,6 @@ def unconvert_subexprs(expr): ) sympy_expr = convert_sympy(expr) - if deep: # thread over everything for (i, sub_expr,) in enumerate(sub_exprs): @@ -192,7 +193,6 @@ def unconvert_subexprs(expr): sympy_expr = sympy_expr.expand(**hints) result = from_sympy(sympy_expr) result = unconvert_subexprs(result) - return result @@ -1413,3 +1413,202 @@ def apply(self, expr, form, h, evaluation): return Expression( "List", *[Expression(h, *[i for i in s]) for s in exponents] ) + + +class Collect(Builtin): + """ +
+
'Collect[$expr$, $x$]' +
Expands $expr$ and collect together terms having the same power of $x$. +
'Collect[$expr$, {$x_1$, $x_2$, ...}]' +
Expands $expr$ and collect together terms having the same powers of + $x_1$, $x_2$, .... +
'Collect[$expr$, {$x_1$, $x_2$, ...}, $filter$]' +
After collect the terms, applies $filter$ to each coefficient. +
+ + >> Collect[(x+y)^3, y] + = x ^ 3 + 3 x ^ 2 y + 3 x y ^ 2 + y ^ 3 + >> Collect[2 Sin[x z] (x+2 y^2 + Sin[y] x), y] + = 2 x Sin[x z] + 2 x Sin[x z] Sin[y] + 4 y ^ 2 Sin[x z] + >> Collect[3 x y+2 Sin[x z] (x+2 y^2 + x) + (x+y)^3, y] + = 4 x Sin[x z] + x ^ 3 + y (3 x + 3 x ^ 2) + y ^ 2 (3 x + 4 Sin[x z]) + y ^ 3 + >> Collect[3 x y+2 Sin[x z] (x+2 y^2 + x) + (x+y)^3, {x,y}] + = 4 x Sin[x z] + x ^ 3 + 3 x y + 3 x ^ 2 y + 4 y ^ 2 Sin[x z] + 3 x y ^ 2 + y ^ 3 + >> Collect[3 x y+2 Sin[x z] (x+2 y^2 + x) + (x+y)^3, {x,y}, h] + = x h[4 Sin[x z]] + x ^ 3 h[1] + x y h[3] + x ^ 2 y h[3] + y ^ 2 h[4 Sin[x z]] + x y ^ 2 h[3] + y ^ 3 h[1] + """ + + rules = { + "Collect[expr_, varlst_]": "Collect[expr, varlst, Identity]", + } + + def apply_var_filter(self, expr, varlst, filt, evaluation): + """Collect[expr_, varlst_, filt_]""" + from mathics.builtin.patterns import match + + if varlst.is_symbol(): + var_exprs = [varlst] + elif varlst.has_form("List", None): + var_exprs = varlst.get_leaves() + else: + var_exprs = [varlst] + + if len(var_exprs) > 1: + target_pat = Pattern.create(Expression("Alternatives", *var_exprs)) + var_pats = [Pattern.create(var) for var in var_exprs] + else: + target_pat = Pattern.create(varlst) + var_pats = [target_pat] + + expr = expand( + expr, + numer=True, + denom=False, + deep=False, + trig=False, + modulus=None, + target_pat=target_pat, + ) + if filt == Symbol("Identity"): + filt = None + + def key_powers(lst): + key = Expression("Plus", *lst) + key = key.evaluate(evaluation) + if key.is_numeric(): + return key.to_python() + return 0 + + def powers_list(pf): + powers = [Integer0 for i, p in enumerate(var_pats)] + if pf is None: + return powers + if pf.is_symbol(): + for i, pat in enumerate(var_pats): + if match(pf, pat, evaluation): + powers[i] = Integer(1) + return powers + if pf.has_form("Sqrt", 1): + for i, pat in enumerate(var_pats): + if match(pf._leaves[0], pat, evaluation): + powers[i] = RationalOneHalf + return powers + if pf.has_form("Power", 2): + for i, pat in enumerate(var_pats): + matchval = match(pf._leaves[0], pat, evaluation) + if matchval: + powers[i] = pf._leaves[1] + return powers + if pf.has_form("Times", None): + contrib = [powers_list(factor) for factor in pf._leaves] + for i in range(len(var_pats)): + powers[i] = Expression("Plus", *[c[i] for c in contrib]).evaluate( + evaluation + ) + return powers + return powers + + def split_coeff_pow(term: Expression): + """ + This function factorizes term in a coefficent free + of powers of the target variables, and a factor with + that powers. + """ + coeffs = [] + powers = [] + # First, split factors on those which are powers of the variables + # and the rest. + if term.is_free(target_pat, evaluation): + coeffs.append(term) + elif ( + term.is_symbol() + or term.has_form("Power", 2) + or term.has_form("Sqrt", 1) + ): + powers.append(term) + elif term.has_form("Times", None): + for factor in term.leaves: + if factor.is_free(target_pat, evaluation): + coeffs.append(factor) + elif match(factor, target_pat, evaluation): + powers.append(factor) + elif ( + factor.has_form("Power", 2) or factor.has_form("Sqrt", 1) + ) and match(factor._leaves[0], target_pat, evaluation): + powers.append(factor) + else: + coeffs.append(factor) + else: + coeffs.append(term) + # Now, rebuild both factors + if len(coeffs) == 0: + coeffs = None + elif len(coeffs) == 1: + coeffs = coeffs[0] + else: + coeffs = Expression("Times", *coeffs) + if len(powers) == 0: + powers = None + elif len(powers) == 1: + powers = powers[0] + else: + powers = Expression("Times", *sorted(powers)) + return coeffs, powers + + if expr.is_free(target_pat, evaluation): + if filt: + return Expression(filt, expr).evaluate(evaluation) + else: + return expr + elif expr.is_symbol() or expr.has_form("Power", 2) or expr.has_form("Sqrt", 1): + if filt: + return Expression( + "Times", Expression(filt, Integer1).evaluate(evaluation), expr + ) + else: + return expr + elif expr.has_form("Plus", None): + coeff_dict = {} + powers_dict = {} + powers_order = {} + for term in expr._leaves: + coeff, powers = split_coeff_pow(term) + pl = powers_list(powers) + key = str(pl) + if not key in powers_dict: + powers_dict[key] = powers + coeff_dict[key] = [] + powers_order[key] = key_powers(pl) + + coeff_dict[key].append(Integer1 if coeff is None else coeff) + + terms = [] + for key in sorted( + coeff_dict, key=lambda kv: powers_order[kv], reverse=False + ): + val = coeff_dict[key] + if len(val) == 0: + continue + elif len(val) == 1: + coeff = val[0] + else: + coeff = Expression("Plus", *val) + if filt: + coeff = Expression(filt, coeff).evaluate(evaluation) + + powerfactor = powers_dict[key] + if powerfactor: + terms.append(Expression("Times", coeff, powerfactor)) + else: + terms.append(coeff) + + return Expression("Plus", *terms) + else: + if filt: + return Expression(filt, expr).evaluate(evaluation) + else: + return expr + + +# tejimeto diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 92e7a8e76f..d1fa852eba 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -630,7 +630,10 @@ class _StopGeneratorMatchQ(StopGenerator): class Matcher(object): def __init__(self, form): - self.form = Pattern.create(form) + if isinstance(form, Pattern): + self.form = form + else: + self.form = Pattern.create(form) def match(self, expr, evaluation): def yield_func(vars, rest): diff --git a/mathics/core/expression.py b/mathics/core/expression.py index b93373a801..95007440fc 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -2258,10 +2258,9 @@ def __neg__(self) -> "Integer": def is_zero(self) -> bool: return self.value == 0 - +Integer0 = Integer(0) Integer1 = Integer(1) - class Rational(Number): @lru_cache() def __new__(cls, numerator, denominator=1) -> "Rational": @@ -2355,6 +2354,7 @@ def is_zero(self) -> bool: self.numerator().is_zero ) # (implicit) and not (self.denominator().is_zero) +RationalOneHalf = Rational(1, 2) class Real(Number): def __new__(cls, value, p=None) -> "Real": From 1ab29a8c6f4ba6f028fe055cd4e66b649d6bb07d Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 26 Apr 2021 20:58:58 -0300 Subject: [PATCH 04/13] removing output type --- mathics/builtin/algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/algebra.py b/mathics/builtin/algebra.py index 120ec8a31c..6dbc90b35a 100644 --- a/mathics/builtin/algebra.py +++ b/mathics/builtin/algebra.py @@ -1469,7 +1469,7 @@ def powers_list(pf): return powers return powers - def split_coeff_pow(term: Expression) -> tuple[Expression, Expression]: + def split_coeff_pow(term): """ This function factorizes term in a coefficent free of powers of the target variables, and a factor with From 1712ce96141a4d491ef62a23490a6513915c84d0 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 26 Apr 2021 16:11:46 -0400 Subject: [PATCH 05/13] Add drawing and numbers builtin submodules Move corresponding files in builtins under these submodules. Adjust lots of imports to include the new submodule --- mathics/builtin/__init__.py | 2 +- mathics/builtin/combinatorial.py | 2 +- mathics/builtin/comparison.py | 2 +- mathics/builtin/drawing/__init__.py | 3 +++ mathics/builtin/{ => drawing}/colors.py | 0 mathics/builtin/{ => drawing}/graphics.py | 6 +++--- mathics/builtin/{ => drawing}/graphics3d.py | 0 mathics/builtin/{ => drawing}/image.py | 6 +++--- mathics/builtin/{ => drawing}/plot.py | 11 ++++++----- mathics/builtin/{ => drawing}/rgbcolor.py | 0 mathics/builtin/files.py | 3 --- mathics/builtin/filesystem.py | 9 ++++++--- mathics/builtin/lists.py | 2 +- mathics/builtin/numbers/__init__.py | 3 +++ mathics/builtin/{ => numbers}/algebra.py | 0 mathics/builtin/{ => numbers}/arithmetic.py | 0 mathics/builtin/{ => numbers}/calculus.py | 0 mathics/builtin/{ => numbers}/constants.py | 0 mathics/builtin/{ => numbers}/diffeqns.py | 0 mathics/builtin/{ => numbers}/exptrig.py | 4 ++-- mathics/builtin/{ => numbers}/integer.py | 0 mathics/builtin/{ => numbers}/linalg.py | 0 mathics/builtin/{ => numbers}/numbertheory.py | 0 mathics/builtin/{ => numbers}/numeric.py | 0 mathics/builtin/{ => numbers}/randomnumbers.py | 0 mathics/builtin/options.py | 4 ++-- mathics/builtin/specialfns/bessel.py | 2 +- mathics/builtin/specialfns/erf.py | 2 +- mathics/builtin/specialfns/expintegral.py | 3 ++- mathics/builtin/specialfns/zeta.py | 2 +- mathics/builtin/tensors.py | 2 +- test/test_color.py | 2 +- 32 files changed, 39 insertions(+), 31 deletions(-) create mode 100644 mathics/builtin/drawing/__init__.py rename mathics/builtin/{ => drawing}/colors.py (100%) rename mathics/builtin/{ => drawing}/graphics.py (99%) rename mathics/builtin/{ => drawing}/graphics3d.py (100%) rename mathics/builtin/{ => drawing}/image.py (99%) rename mathics/builtin/{ => drawing}/plot.py (99%) rename mathics/builtin/{ => drawing}/rgbcolor.py (100%) create mode 100644 mathics/builtin/numbers/__init__.py rename mathics/builtin/{ => numbers}/algebra.py (100%) rename mathics/builtin/{ => numbers}/arithmetic.py (100%) rename mathics/builtin/{ => numbers}/calculus.py (100%) rename mathics/builtin/{ => numbers}/constants.py (100%) rename mathics/builtin/{ => numbers}/diffeqns.py (100%) rename mathics/builtin/{ => numbers}/exptrig.py (99%) rename mathics/builtin/{ => numbers}/integer.py (100%) rename mathics/builtin/{ => numbers}/linalg.py (100%) rename mathics/builtin/{ => numbers}/numbertheory.py (100%) rename mathics/builtin/{ => numbers}/numeric.py (100%) rename mathics/builtin/{ => numbers}/randomnumbers.py (100%) diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index 155e8ee11d..f57406299e 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -130,7 +130,7 @@ def is_builtin(var): _builtins = [] builtins_by_module = {} -for subdir in ("specialfns",): +for subdir in ("drawing", "numbers", "specialfns",): import_name = f"{__name__}.{subdir}" builtin_module = importlib.import_module(import_name) submodule_names = [ diff --git a/mathics/builtin/combinatorial.py b/mathics/builtin/combinatorial.py index 2b31aded96..b48e0ff959 100644 --- a/mathics/builtin/combinatorial.py +++ b/mathics/builtin/combinatorial.py @@ -9,7 +9,7 @@ from mathics.builtin.base import Builtin from mathics.core.expression import Expression, Integer, Symbol, SymbolTrue, SymbolFalse -from mathics.builtin.arithmetic import _MPMathFunction +from mathics.builtin.numbers.arithmetic import _MPMathFunction from itertools import combinations diff --git a/mathics/builtin/comparison.py b/mathics/builtin/comparison.py index 04a3aa9933..e083bf48fb 100644 --- a/mathics/builtin/comparison.py +++ b/mathics/builtin/comparison.py @@ -13,7 +13,7 @@ SympyFunction, ) -from mathics.builtin.constants import mp_convert_constant +from mathics.builtin.numbers.constants import mp_convert_constant from mathics.core.expression import ( COMPARE_PREC, diff --git a/mathics/builtin/drawing/__init__.py b/mathics/builtin/drawing/__init__.py new file mode 100644 index 0000000000..bbbc3fd78b --- /dev/null +++ b/mathics/builtin/drawing/__init__.py @@ -0,0 +1,3 @@ +""" +Graphics, Drawing, and Images +""" diff --git a/mathics/builtin/colors.py b/mathics/builtin/drawing/colors.py similarity index 100% rename from mathics/builtin/colors.py rename to mathics/builtin/drawing/colors.py diff --git a/mathics/builtin/graphics.py b/mathics/builtin/drawing/graphics.py similarity index 99% rename from mathics/builtin/graphics.py rename to mathics/builtin/drawing/graphics.py index 6f146ad437..6b37573d1c 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/drawing/graphics.py @@ -34,7 +34,7 @@ system_symbols_dict, from_python, ) -from mathics.builtin.colors import convert as convert_color +from mathics.builtin.drawing.colors import convert as convert_color from mathics.core.numbers import machine_epsilon GRAPHICS_OPTIONS = { @@ -60,7 +60,7 @@ class ColorError(BoxConstructError): def get_class(name): - from mathics.builtin.graphics3d import GLOBALS3D + from mathics.builtin.drawing.graphics3d import GLOBALS3D c = GLOBALS.get(name) if c is None: @@ -524,7 +524,7 @@ def convert(content): options[option] = Expression(SymbolN, options[option]).evaluate( evaluation ) - from mathics.builtin.graphics3d import Graphics3DBox, Graphics3D + from mathics.builtin.drawing.graphics3d import Graphics3DBox, Graphics3D if type(self) is Graphics: return GraphicsBox( diff --git a/mathics/builtin/graphics3d.py b/mathics/builtin/drawing/graphics3d.py similarity index 100% rename from mathics/builtin/graphics3d.py rename to mathics/builtin/drawing/graphics3d.py diff --git a/mathics/builtin/image.py b/mathics/builtin/drawing/image.py similarity index 99% rename from mathics/builtin/image.py rename to mathics/builtin/drawing/image.py index db5f3a3e27..a32d3a37e3 100644 --- a/mathics/builtin/image.py +++ b/mathics/builtin/drawing/image.py @@ -21,7 +21,7 @@ SymbolRule, from_python, ) -from mathics.builtin.colors import ( +from mathics.builtin.drawing.colors import ( convert as convert_color, colorspaces as known_colorspaces, ) @@ -1294,7 +1294,7 @@ def apply(self, input, colorspace, evaluation): if isinstance(input, Image): return input.color_convert(colorspace.get_string_value()) else: - from mathics.builtin.graphics import ( + from mathics.builtin.drawing.graphics import ( expression_to_color, color_to_expression, ) @@ -1599,7 +1599,7 @@ def apply(self, values, evaluation, options): ): color_function = String("LakeColors") - from mathics.builtin.plot import gradient_palette + from mathics.builtin.drawing.plot import gradient_palette cmap = gradient_palette(color_function, n, evaluation) if not cmap: diff --git a/mathics/builtin/plot.py b/mathics/builtin/drawing/plot.py similarity index 99% rename from mathics/builtin/plot.py rename to mathics/builtin/drawing/plot.py index 7167cbdcfe..5a99a9f14b 100644 --- a/mathics/builtin/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -24,12 +24,13 @@ SymbolN, SymbolRule, ) + from mathics.builtin.base import Builtin -from mathics.builtin.scoping import dynamic_scoping +from mathics.builtin.drawing.graphics import Graphics +from mathics.builtin.drawing.graphics3d import Graphics3D +from mathics.builtin.numbers.numeric import chop from mathics.builtin.options import options_to_rules -from mathics.builtin.numeric import chop -from mathics.builtin.graphics import Graphics -from mathics.builtin.graphics3d import Graphics3D +from mathics.builtin.scoping import dynamic_scoping try: @@ -64,7 +65,7 @@ def gradient_palette(color_function, n, evaluation): # always returns RGB value if len(colors.leaves) != n: return - from mathics.builtin.graphics import expression_to_color, ColorError + from mathics.builtin.drawing.graphics import expression_to_color, ColorError try: objects = [expression_to_color(x) for x in colors.leaves] diff --git a/mathics/builtin/rgbcolor.py b/mathics/builtin/drawing/rgbcolor.py similarity index 100% rename from mathics/builtin/rgbcolor.py rename to mathics/builtin/drawing/rgbcolor.py diff --git a/mathics/builtin/files.py b/mathics/builtin/files.py index fd873865e6..7f6919e854 100644 --- a/mathics/builtin/files.py +++ b/mathics/builtin/files.py @@ -35,7 +35,6 @@ String, Symbol, SymbolFailed, - SymbolFalse, SymbolNull, SymbolTrue, from_mpmath, @@ -46,10 +45,8 @@ Stream, path_search, stream_manager, - urlsave_tmp, ) from mathics.builtin.base import Builtin, Predefined, BinaryOperator, PrefixOperator -from mathics.builtin.numeric import Hash from mathics.builtin.strings import to_python_encoding from mathics.builtin.base import MessageException diff --git a/mathics/builtin/filesystem.py b/mathics/builtin/filesystem.py index 8a90b95397..f9384a1f7d 100644 --- a/mathics/builtin/filesystem.py +++ b/mathics/builtin/filesystem.py @@ -15,7 +15,6 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.core.expression import ( - BaseExpression, Expression, String, Symbol, @@ -38,8 +37,12 @@ ) from mathics.builtin.base import Builtin, Predefined -from mathics.builtin.files import DIRECTORY_STACK, INITIAL_DIR, mathics_open -from mathics.builtin.numeric import Hash +from mathics.builtin.files import ( + DIRECTORY_STACK, + INITIAL_DIR, # noqa is used via global + mathics_open + ) +from mathics.builtin.numbers.numeric import Hash from mathics.builtin.strings import to_regex from mathics.builtin.base import MessageException import re diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index 054dced5a9..dde8f65b9c 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -52,7 +52,7 @@ from mathics.core.evaluation import BreakInterrupt, ContinueInterrupt, ReturnInterrupt from mathics.core.rules import Pattern from mathics.core.convert import from_sympy -from mathics.builtin.algebra import cancel +from mathics.builtin.numbers.algebra import cancel from mathics.algorithm.introselect import introselect from mathics.algorithm.clusters import ( optimize, diff --git a/mathics/builtin/numbers/__init__.py b/mathics/builtin/numbers/__init__.py new file mode 100644 index 0000000000..2555e6b8dd --- /dev/null +++ b/mathics/builtin/numbers/__init__.py @@ -0,0 +1,3 @@ +""" +Integer and Number-Theoretical Functions +""" diff --git a/mathics/builtin/algebra.py b/mathics/builtin/numbers/algebra.py similarity index 100% rename from mathics/builtin/algebra.py rename to mathics/builtin/numbers/algebra.py diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/numbers/arithmetic.py similarity index 100% rename from mathics/builtin/arithmetic.py rename to mathics/builtin/numbers/arithmetic.py diff --git a/mathics/builtin/calculus.py b/mathics/builtin/numbers/calculus.py similarity index 100% rename from mathics/builtin/calculus.py rename to mathics/builtin/numbers/calculus.py diff --git a/mathics/builtin/constants.py b/mathics/builtin/numbers/constants.py similarity index 100% rename from mathics/builtin/constants.py rename to mathics/builtin/numbers/constants.py diff --git a/mathics/builtin/diffeqns.py b/mathics/builtin/numbers/diffeqns.py similarity index 100% rename from mathics/builtin/diffeqns.py rename to mathics/builtin/numbers/diffeqns.py diff --git a/mathics/builtin/exptrig.py b/mathics/builtin/numbers/exptrig.py similarity index 99% rename from mathics/builtin/exptrig.py rename to mathics/builtin/numbers/exptrig.py index d25e736ce8..48c7631150 100644 --- a/mathics/builtin/exptrig.py +++ b/mathics/builtin/numbers/exptrig.py @@ -21,8 +21,8 @@ Symbol, ) -from mathics.builtin.numeric import Fold -from mathics.builtin.arithmetic import _MPMathFunction +from mathics.builtin.numbers.numeric import Fold +from mathics.builtin.numbers.arithmetic import _MPMathFunction class AnglePath(Builtin): diff --git a/mathics/builtin/integer.py b/mathics/builtin/numbers/integer.py similarity index 100% rename from mathics/builtin/integer.py rename to mathics/builtin/numbers/integer.py diff --git a/mathics/builtin/linalg.py b/mathics/builtin/numbers/linalg.py similarity index 100% rename from mathics/builtin/linalg.py rename to mathics/builtin/numbers/linalg.py diff --git a/mathics/builtin/numbertheory.py b/mathics/builtin/numbers/numbertheory.py similarity index 100% rename from mathics/builtin/numbertheory.py rename to mathics/builtin/numbers/numbertheory.py diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numbers/numeric.py similarity index 100% rename from mathics/builtin/numeric.py rename to mathics/builtin/numbers/numeric.py diff --git a/mathics/builtin/randomnumbers.py b/mathics/builtin/numbers/randomnumbers.py similarity index 100% rename from mathics/builtin/randomnumbers.py rename to mathics/builtin/numbers/randomnumbers.py diff --git a/mathics/builtin/options.py b/mathics/builtin/options.py index a857db24e3..9cddae70c7 100644 --- a/mathics/builtin/options.py +++ b/mathics/builtin/options.py @@ -5,6 +5,7 @@ """ from mathics.version import __version__ # noqa used in loading to check consistency. + from mathics.builtin.base import Builtin, Test, get_option from mathics.core.expression import ( Symbol, @@ -14,8 +15,7 @@ ensure_context, strip_context, ) -from mathics.builtin.image import Image -from mathics.core.expression import strip_context +from mathics.builtin.drawing.image import Image class Options(Builtin): diff --git a/mathics/builtin/specialfns/bessel.py b/mathics/builtin/specialfns/bessel.py index b92330c04a..db56647531 100644 --- a/mathics/builtin/specialfns/bessel.py +++ b/mathics/builtin/specialfns/bessel.py @@ -7,7 +7,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.arithmetic import _MPMathFunction +from mathics.builtin.numbers.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin from mathics.core.expression import from_mpmath from mathics.core.numbers import machine_precision, get_precision, PrecisionValueError diff --git a/mathics/builtin/specialfns/erf.py b/mathics/builtin/specialfns/erf.py index 0b0dbc62f2..2670c51759 100644 --- a/mathics/builtin/specialfns/erf.py +++ b/mathics/builtin/specialfns/erf.py @@ -6,7 +6,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.arithmetic import _MPMathFunction, _MPMathMultiFunction +from mathics.builtin.numbers.arithmetic import _MPMathFunction, _MPMathMultiFunction class Erf(_MPMathMultiFunction): """ diff --git a/mathics/builtin/specialfns/expintegral.py b/mathics/builtin/specialfns/expintegral.py index 89cc79d076..def704e9c6 100644 --- a/mathics/builtin/specialfns/expintegral.py +++ b/mathics/builtin/specialfns/expintegral.py @@ -6,7 +6,8 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.arithmetic import _MPMathFunction +from mathics.builtin.numbers.arithmetic import _MPMathFunction +from mathics.core.expression import from_mpmath class ExpIntegralE(_MPMathFunction): """ diff --git a/mathics/builtin/specialfns/zeta.py b/mathics/builtin/specialfns/zeta.py index 72f026cc2d..15b857c62c 100644 --- a/mathics/builtin/specialfns/zeta.py +++ b/mathics/builtin/specialfns/zeta.py @@ -8,7 +8,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.arithmetic import _MPMathFunction +from mathics.builtin.numbers.arithmetic import _MPMathFunction from mathics.core.expression import from_mpmath class LerchPhi(_MPMathFunction): diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index 771222c3ff..f78ed5c5d4 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -480,7 +480,7 @@ def is_boolean(x): elif all(isinstance(q, String) for q in p): return "EditDistance" else: - from mathics.builtin.graphics import expression_to_color + from mathics.builtin.drawing.graphics import expression_to_color if all(expression_to_color(q) is not None for q in p): return "ColorDistance" diff --git a/test/test_color.py b/test/test_color.py index 946dff3aec..116f67af5f 100755 --- a/test/test_color.py +++ b/test/test_color.py @@ -6,7 +6,7 @@ from random import random -import mathics.builtin.colors as colors +import mathics.builtin.drawing.colors as colors from mathics.builtin.numpy_utils import array, stacked, vectorize from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation From 2ae551963f2426b5f9433267a34e2b8703ff44c5 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 26 Apr 2021 16:41:44 -0400 Subject: [PATCH 06/13] Disable cythonization of submodule files --- mathics/builtin/assignment.py | 6 +++--- setup.py | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/mathics/builtin/assignment.py b/mathics/builtin/assignment.py index c50be16328..8a0ebda339 100644 --- a/mathics/builtin/assignment.py +++ b/mathics/builtin/assignment.py @@ -1922,11 +1922,11 @@ class LoadModule(Builtin): def apply(self, module, evaluation): "LoadModule[module_String]" try: - module_loaded = evaluation.definitions.load_pymathics_module(module.value) - except PyMathicsLoadException as e: + evaluation.definitions.load_pymathics_module(module.value) + except PyMathicsLoadException: evaluation.message(self.name, "notmathicslib", module) return SymbolFailed - except ImportError as e: + except ImportError: evaluation.message(self.get_name(), "notfound", module) return SymbolFailed else: diff --git a/setup.py b/setup.py index da2af5109d..045dc0f202 100644 --- a/setup.py +++ b/setup.py @@ -64,17 +64,27 @@ def read(*rnames): EXTENSIONS = [] CMDCLASS = {} else: - EXTENSIONS = { - "core": ["expression", "numbers", "rules", "pattern"], - "builtin": ["arithmetic", "numeric", "patterns", "graphics"], + EXTENSIONS_DICT = { + "core": ("expression", "numbers", "rules", "pattern"), + "builtin": ("patterns",), } EXTENSIONS = [ Extension( "mathics.%s.%s" % (parent, module), ["mathics/%s/%s.py" % (parent, module)] ) - for parent, modules in EXTENSIONS.items() + for parent, modules in EXTENSIONS_DICT.items() for module in modules ] + # EXTENSIONS_SUBDIR_DICT = { + # "builtin": [("numbers", "arithmetic"), ("numbers", "numeric"), ("drawing", "graphics")], + # } + # EXTENSIONS.append( + # Extension( + # "mathics.%s.%s.%s" % (parent, module[0], module[1]), ["mathics/%s/%s/%s.py" % (parent, module[0], module[1])] + # ) + # for parent, modules in EXTENSIONS_SUBDIR_DICT.items() + # for module in modules + # ) CMDCLASS = {"build_ext": build_ext} INSTALL_REQUIRES += ["cython>=0.15.1"] From 4e1bcdb36b7304db40f25f450a2f25f679317a88 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 27 Apr 2021 11:36:37 -0400 Subject: [PATCH 07/13] Add more builtin modules --- mathics/builtin/{numbers => }/arithmetic.py | 0 mathics/builtin/combinatorial.py | 2 +- mathics/builtin/drawing/graphics3d.py | 2 +- mathics/builtin/drawing/image.py | 2 +- mathics/builtin/drawing/plot.py | 18 ++----------- mathics/builtin/{drawing => }/graphics.py | 29 ++++++++++++++------- mathics/builtin/numbers/exptrig.py | 2 +- mathics/builtin/{numbers => }/numeric.py | 0 mathics/builtin/specialfns/bessel.py | 2 +- mathics/builtin/specialfns/erf.py | 2 +- mathics/builtin/specialfns/expintegral.py | 2 +- mathics/builtin/specialfns/zeta.py | 2 +- mathics/builtin/tensors.py | 2 +- setup.py | 4 ++- 14 files changed, 34 insertions(+), 35 deletions(-) rename mathics/builtin/{numbers => }/arithmetic.py (100%) rename mathics/builtin/{drawing => }/graphics.py (99%) rename mathics/builtin/{numbers => }/numeric.py (100%) diff --git a/mathics/builtin/numbers/arithmetic.py b/mathics/builtin/arithmetic.py similarity index 100% rename from mathics/builtin/numbers/arithmetic.py rename to mathics/builtin/arithmetic.py diff --git a/mathics/builtin/combinatorial.py b/mathics/builtin/combinatorial.py index b48e0ff959..2b31aded96 100644 --- a/mathics/builtin/combinatorial.py +++ b/mathics/builtin/combinatorial.py @@ -9,7 +9,7 @@ from mathics.builtin.base import Builtin from mathics.core.expression import Expression, Integer, Symbol, SymbolTrue, SymbolFalse -from mathics.builtin.numbers.arithmetic import _MPMathFunction +from mathics.builtin.arithmetic import _MPMathFunction from itertools import combinations diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 59527a76d8..f7e9df4477 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -14,7 +14,7 @@ SymbolList, ) from mathics.builtin.base import BoxConstructError, Builtin, InstanceableBuiltin -from .graphics import ( +from mathics.builtin.graphics import ( Graphics, GraphicsBox, PolygonBox, diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index a32d3a37e3..861156c409 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -1294,7 +1294,7 @@ def apply(self, input, colorspace, evaluation): if isinstance(input, Image): return input.color_convert(colorspace.get_string_value()) else: - from mathics.builtin.drawing.graphics import ( + from mathics.builtin.graphics import ( expression_to_color, color_to_expression, ) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 5a99a9f14b..7ba0983322 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -26,7 +26,7 @@ ) from mathics.builtin.base import Builtin -from mathics.builtin.drawing.graphics import Graphics +from mathics.builtin.graphics import Graphics from mathics.builtin.drawing.graphics3d import Graphics3D from mathics.builtin.numbers.numeric import chop from mathics.builtin.options import options_to_rules @@ -40,7 +40,6 @@ except ImportError: has_compile = False - def gradient_palette(color_function, n, evaluation): # always returns RGB values if isinstance(color_function, String): color_data = Expression("ColorData", color_function).evaluate(evaluation) @@ -65,7 +64,7 @@ def gradient_palette(color_function, n, evaluation): # always returns RGB value if len(colors.leaves) != n: return - from mathics.builtin.drawing.graphics import expression_to_color, ColorError + from mathics.builtin.graphics import expression_to_color, ColorError try: objects = [expression_to_color(x) for x in colors.leaves] @@ -341,7 +340,6 @@ def get_plot_range(values, all_values, option): class _Plot(Builtin): - from .graphics import Graphics attributes = ("HoldAll",) @@ -706,8 +704,6 @@ def find_excl(excl): class _Chart(Builtin): attributes = ("HoldAll",) - from .graphics import Graphics - options = Graphics.options.copy() options.update( { @@ -1136,8 +1132,6 @@ class Histogram(Builtin): = -Graphics- """ - from .graphics import Graphics - attributes = ("HoldAll",) options = Graphics.options.copy() @@ -2225,8 +2219,6 @@ class ListPlot(_ListPlot): = -Graphics- """ - from .graphics import Graphics - attributes = ("HoldAll",) options = Graphics.options.copy() @@ -2265,8 +2257,6 @@ class ListLinePlot(_ListPlot): = -Graphics- """ - from .graphics import Graphics - attributes = ("HoldAll",) options = Graphics.options.copy() @@ -2345,8 +2335,6 @@ class Plot3D(_Plot3D): #> Plot3D[x + 2y, {x, -2, 2}, {y, -2, 2}] // TeXForm """ - from .graphics import Graphics - attributes = ("HoldAll",) options = Graphics.options.copy() @@ -2429,8 +2417,6 @@ class DensityPlot(_Plot3D): = -Graphics- """ - from .graphics import Graphics - attributes = ("HoldAll",) options = Graphics.options.copy() diff --git a/mathics/builtin/drawing/graphics.py b/mathics/builtin/graphics.py similarity index 99% rename from mathics/builtin/drawing/graphics.py rename to mathics/builtin/graphics.py index 6b37573d1c..6e26b51ee5 100644 --- a/mathics/builtin/drawing/graphics.py +++ b/mathics/builtin/graphics.py @@ -29,7 +29,6 @@ SymbolList, SymbolN, SymbolMakeBoxes, - strip_context, system_symbols, system_symbols_dict, from_python, @@ -119,7 +118,9 @@ def cut(value): return value -def create_css(edge_color=None, face_color=None, stroke_width=None, font_color=None, opacity=1.0): +def create_css( + edge_color=None, face_color=None, stroke_width=None, font_color=None, opacity=1.0 +): css = [] if edge_color is not None: color, stroke_opacity = edge_color.to_css() @@ -1004,7 +1005,7 @@ def distance(a, b): else: return Expression( "List", - *[distance(a, b) for a, b in zip(c1.leaves, c2.leaves)] + *[distance(a, b) for a, b in zip(c1.leaves, c2.leaves)], ) else: return Expression(SymbolList, *[distance(c, c2) for c in c1.leaves]) @@ -1805,7 +1806,7 @@ def parse_component(segments): k = spline_degree.get_int_value() elif head == "System`BSplineCurve": raise NotImplementedError # FIXME convert bspline to bezier here - parts = segment.leaves + # parts = segment.leaves else: raise BoxConstructError @@ -2569,7 +2570,16 @@ def default_arrow(px, py, vx, vy, t1, s): class InsetBox(_GraphicsElement): - def init(self, graphics, style, item=None, content=None, pos=None, opos=(0, 0), opacity=1.0): + def init( + self, + graphics, + style, + item=None, + content=None, + pos=None, + opos=(0, 0), + opacity=1.0, + ): super(InsetBox, self).init(graphics, item, style) self.color = self.style.get_option("System`FontColor") @@ -2618,15 +2628,16 @@ def to_svg(self, offset=None): svg = "\n" + content + "\n" else: css_style = create_css( - font_color=self.color, edge_color=self.color, face_color=self.color, opacity=self.opacity + font_color=self.color, + edge_color=self.color, + face_color=self.color, + opacity=self.opacity, ) text_pos_opts = f'x="{x}" y="{y}" ox="{self.opos[0]}" oy="{self.opos[1]}"' # FIXME: don't hard code text_style_opts, but allow these to be adjustable. text_style_opts = "text-anchor:middle; dominant-baseline:middle;" content = self.content.boxes_to_text(evaluation=self.graphics.evaluation) - svg = ( - f'{content}' - ) + svg = f'{content}' # content = self.content.boxes_to_mathml(evaluation=self.graphics.evaluation) # style = create_css(font_color=self.color) diff --git a/mathics/builtin/numbers/exptrig.py b/mathics/builtin/numbers/exptrig.py index 48c7631150..27422202be 100644 --- a/mathics/builtin/numbers/exptrig.py +++ b/mathics/builtin/numbers/exptrig.py @@ -22,7 +22,7 @@ ) from mathics.builtin.numbers.numeric import Fold -from mathics.builtin.numbers.arithmetic import _MPMathFunction +from mathics.builtin.arithmetic import _MPMathFunction class AnglePath(Builtin): diff --git a/mathics/builtin/numbers/numeric.py b/mathics/builtin/numeric.py similarity index 100% rename from mathics/builtin/numbers/numeric.py rename to mathics/builtin/numeric.py diff --git a/mathics/builtin/specialfns/bessel.py b/mathics/builtin/specialfns/bessel.py index db56647531..b92330c04a 100644 --- a/mathics/builtin/specialfns/bessel.py +++ b/mathics/builtin/specialfns/bessel.py @@ -7,7 +7,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.numbers.arithmetic import _MPMathFunction +from mathics.builtin.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin from mathics.core.expression import from_mpmath from mathics.core.numbers import machine_precision, get_precision, PrecisionValueError diff --git a/mathics/builtin/specialfns/erf.py b/mathics/builtin/specialfns/erf.py index 2670c51759..0b0dbc62f2 100644 --- a/mathics/builtin/specialfns/erf.py +++ b/mathics/builtin/specialfns/erf.py @@ -6,7 +6,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.numbers.arithmetic import _MPMathFunction, _MPMathMultiFunction +from mathics.builtin.arithmetic import _MPMathFunction, _MPMathMultiFunction class Erf(_MPMathMultiFunction): """ diff --git a/mathics/builtin/specialfns/expintegral.py b/mathics/builtin/specialfns/expintegral.py index def704e9c6..53401c80a8 100644 --- a/mathics/builtin/specialfns/expintegral.py +++ b/mathics/builtin/specialfns/expintegral.py @@ -6,7 +6,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.numbers.arithmetic import _MPMathFunction +from mathics.builtin.arithmetic import _MPMathFunction from mathics.core.expression import from_mpmath class ExpIntegralE(_MPMathFunction): diff --git a/mathics/builtin/specialfns/zeta.py b/mathics/builtin/specialfns/zeta.py index 15b857c62c..72f026cc2d 100644 --- a/mathics/builtin/specialfns/zeta.py +++ b/mathics/builtin/specialfns/zeta.py @@ -8,7 +8,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.numbers.arithmetic import _MPMathFunction +from mathics.builtin.arithmetic import _MPMathFunction from mathics.core.expression import from_mpmath class LerchPhi(_MPMathFunction): diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index f78ed5c5d4..771222c3ff 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -480,7 +480,7 @@ def is_boolean(x): elif all(isinstance(q, String) for q in p): return "EditDistance" else: - from mathics.builtin.drawing.graphics import expression_to_color + from mathics.builtin.graphics import expression_to_color if all(expression_to_color(q) is not None for q in p): return "ColorDistance" diff --git a/setup.py b/setup.py index 045dc0f202..ce1c389de7 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,7 @@ def read(*rnames): else: EXTENSIONS_DICT = { "core": ("expression", "numbers", "rules", "pattern"), - "builtin": ("patterns",), + "builtin": ["arithmetic", "numeric", "patterns", "graphics"], } EXTENSIONS = [ Extension( @@ -123,6 +123,8 @@ def subdirs(root, file="*.*", depth=10): "mathics.core.parser", "mathics.builtin", "mathics.builtin.compile", + "mathics.builtin.drawing", + "mathics.builtin.numbers", "mathics.builtin.numpy_utils", "mathics.builtin.pymimesniffer", "mathics.builtin.pympler", From f7d4cfa86b52202f4e9a904900f21752429725b1 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 29 Apr 2021 08:09:57 -0400 Subject: [PATCH 08/13] Correct builtin numeric imports --- mathics/builtin/drawing/plot.py | 2 +- mathics/builtin/filesystem.py | 2 +- mathics/builtin/numbers/exptrig.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 7ba0983322..65be21f606 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -28,7 +28,7 @@ from mathics.builtin.base import Builtin from mathics.builtin.graphics import Graphics from mathics.builtin.drawing.graphics3d import Graphics3D -from mathics.builtin.numbers.numeric import chop +from mathics.builtin.numeric import chop from mathics.builtin.options import options_to_rules from mathics.builtin.scoping import dynamic_scoping diff --git a/mathics/builtin/filesystem.py b/mathics/builtin/filesystem.py index f9384a1f7d..b01472e478 100644 --- a/mathics/builtin/filesystem.py +++ b/mathics/builtin/filesystem.py @@ -42,7 +42,7 @@ INITIAL_DIR, # noqa is used via global mathics_open ) -from mathics.builtin.numbers.numeric import Hash +from mathics.builtin.numeric import Hash from mathics.builtin.strings import to_regex from mathics.builtin.base import MessageException import re diff --git a/mathics/builtin/numbers/exptrig.py b/mathics/builtin/numbers/exptrig.py index 27422202be..d25e736ce8 100644 --- a/mathics/builtin/numbers/exptrig.py +++ b/mathics/builtin/numbers/exptrig.py @@ -21,7 +21,7 @@ Symbol, ) -from mathics.builtin.numbers.numeric import Fold +from mathics.builtin.numeric import Fold from mathics.builtin.arithmetic import _MPMathFunction From b828610b8a18ab37d8dd5553690f9ee03df92cb3 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 29 Apr 2021 08:17:14 -0400 Subject: [PATCH 09/13] disable test_get_and_put() on win32 --- test/test_files.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/test_files.py b/test/test_files.py index 673cd1f3c6..014a6c2220 100644 --- a/test/test_files.py +++ b/test/test_files.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import sys from .helper import check_evaluation, evaluate def test_compress(): @@ -18,12 +19,13 @@ def test_unprotected(): check_evaluation(str_expr, str_expected, message) -def test_get_and_put(): - temp_filename = evaluate('$TemporaryDirectory<>"/testfile"').to_python() - temp_filename_strip = temp_filename[1:-1] - check_evaluation(f"40! >> {temp_filename_strip}", "Null") - check_evaluation(f"<< {temp_filename_strip}", "40!") - check_evaluation(f"DeleteFile[{temp_filename}]", "Null") +if sys.platform not in ("win32",): + def test_get_and_put(): + temp_filename = evaluate('$TemporaryDirectory<>"/testfile"').to_python() + temp_filename_strip = temp_filename[1:-1] + check_evaluation(f"40! >> {temp_filename_strip}", "Null") + check_evaluation(f"<< {temp_filename_strip}", "40!") + check_evaluation(f"DeleteFile[{temp_filename}]", "Null") # I do not know what is it supposed to test with this... From 0a6fabacdf734b0f2fc7c0ed2a4a54f40ae3ed2e Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 12 Apr 2021 14:47:39 -0300 Subject: [PATCH 10/13] adding tests new inference module. Many Improvings on inference --- mathics/builtin/inference.py | 358 +++++++++++++++++++++++++++++++++-- test/test_assumptions.py | 58 ++++++ 2 files changed, 400 insertions(+), 16 deletions(-) create mode 100644 test/test_assumptions.py diff --git a/mathics/builtin/inference.py b/mathics/builtin/inference.py index 5dda95db1c..d05ffbc7d4 100644 --- a/mathics/builtin/inference.py +++ b/mathics/builtin/inference.py @@ -1,7 +1,9 @@ +# -*- coding: utf-8 -*- + from mathics.version import __version__ # noqa used in loading to check consistency. + from mathics.core.expression import ( Expression, - Symbol, SymbolTrue, SymbolFalse, ) @@ -14,8 +16,103 @@ # TODO: Extend these rules? +def debug_logical_expr(pref, expr, evaluation): + pass + # return + # print(pref , expr) #expr.format(evaluation,"OutputForm").boxes_to_text(evaluation=evaluation)) + + +logical_algebraic_rules_spec = { + # Inequality rules + "Unequal[a_, b_]": "Not[Equal[a, b]]", + "Greater[a_, b_]": "Less[b, a]", + "GreaterEqual[a_, b_]": "Not[Less[a, b]]", + "LessEqual[a_, b_]": "Not[Less[b, a]]", + "PositiveQ[a_]": "Less[0, a]", + "NegativeQ[a_]": "Less[a, 0]", + # Logical basic reductions + "Or[q_, Not[q_]]": "True", + "Or[q_,]": "q", + "Or[q_, q_]": "q", + "Or[pred1___, q_, pred2___, q_, pred3___]": "Or[pred1, q, pred2, pred3]", + # TODO: Logical operations should sort the leaves... + "Or[Not[q_], q_]": "True", + "Or[pred1___, q_, pred2___, Not[q_], pred3___]": "Or[pred1, pred2, pred3]", + "Or[pred1___, Not[q_], pred2___, q_, pred3___]": "Or[pred1, pred2, pred3]", + "And[q_,q_]": "q", + "And[q_, Not[q_]]": "False", + "And[Not[q_],q_]": "False", + "And[pred1___, q_, pred2___, Not[q_], pred3___]": "False", + "And[pred1___, Not[q_], pred2___, q_, pred3___]": "False", + # Logical reductions involving equalities + "Or[pred1___, a_==b_, pred2___ , b_==a_, pred3___]": "Or[pred1, a==b, pred2, pred3]", + "And[pred1___, a_==b_, pred2___ , b_==a_, pred3___]": "And[pred1, a==b, pred2, pred3]", + "Or[pred1___, a_==b_, pred2___ , Not[b_==a_], pred3___]": "Or[pred1, pred2, pred3]", + "And[pred1___, a_==b_, pred2___ , Not[b_==a_], pred3___]": "False", + "Xor[q_, Not[q_]]": "True", + "Xor[a_==b_, Not[b_==a_]]": "True", + # Logical reductions involving inequalities + "Or[a_b)", + "Or[b_==a_, a_b)", + "And[a_=b", + "Not[a_==b_]": "a!=b", +} + + +logical_algebraic_rules = None +remove_not_rules = None + + +def ensure_logical_algebraic_rules(): + global logical_algebraic_rules + global remove_not_rules + if logical_algebraic_rules is None: + logical_algebraic_rules = [] + for pattern, replace in logical_algebraic_rules_spec.items(): + pattern = parse_builtin_rule(pattern, SystemDefinitions()) + logical_algebraic_rules.append( + Rule(pattern, parse_builtin_rule(replace), system=True) + ) + remove_not_rules = [] + for pattern, replace in remove_not_rules_spec.items(): + pattern = parse_builtin_rule(pattern, SystemDefinitions()) + remove_not_rules.append( + Rule(pattern, parse_builtin_rule(replace), system=True) + ) + return + -def get_assumptions_list(evaluation): +def remove_nots_when_unnecesary(pred, evaluation): + global remove_not_rules + cc = True + while cc: + pred, cc = pred.apply_rules(remove_not_rules, evaluation) + debug_logical_expr("-> ", pred, evaluation) + if pred.is_true() or pred == SymbolFalse: + return pred + return pred + + +def get_assumptions_list(evaluation): assumptions = None assumptions_def = evaluation.definitions.get_definition( "System`$Assumptions", only_if_exists=True @@ -35,23 +132,252 @@ def get_assumptions_list(evaluation): return assumptions +def remove_duplicated_assumptions(assumptions_list, evaluation): + if len(assumptions_list) == 0: + return assumptions_list + assumptions_list = sorted(assumptions_list) + unique_assumptions = [assumptions_list[0]] + for i, assumption in enumerate(assumptions_list): + if not (assumption == unique_assumptions[-1]): + unique_assumptions.append(assumption) + return unique_assumptions + + +def logical_expand_assumptions(assumptions_list, evaluation): + new_assumptions_list = [] + changed = False + for assumption in assumptions_list: + if assumption.is_symbol(): + if assumption.is_true(): + changed = True + continue + if assumption == SymbolFalse: + evaluation.message("Assumption", "faas") + changed = True + continue + if assumption.is_numeric(): + evaluation.message("Assumption", "baas") + changed = True + continue + new_assumptions_list.append(assumption) + continue + if assumption.has_form("And", None): + changed = True + for leaf in assumption.leaves: + new_assumptions_list.append(leaf) + continue + if assumption.has_form("Not", 1): + sentence = assumption._leaves[0] + if sentence.has_form("Or", None): + changed = True + for leaf in sentence._leaves: + new_assumptions_list.append(Expression("Not", leaf)) + continue + if sentence.has_form("And", None): + leaves = (Expression("Not", leaf) for leaf in sentence._leaves) + new_assumptions_list.append(Expression("Or", *leaves)) + continue + if sentence.has_form("Implies", 2): + changed = True + new_assumptions_list.append(sentence._leaves[0]) + new_assumptions_list.append(Expression("Not", sentence._leaves[1])) + if assumption.has_form("Nor", None): + changed = True + for leaf in assumption.leaves: + new_assumptions_list.append(Expression("Not", leaf)) + continue + else: + new_assumptions_list.append(assumption) + + if changed: + new_assumptions_list = remove_duplicated_assumptions( + new_assumptions_list, evaluation + ) + + return new_assumptions_list, changed -def evaluate_predicate(pred, evaluation): - if pred.has_form(("List", "Sequence"), None): - return Expression(pred._head, *[evaluate_predicate(subp, evaluation) for subp in pred._leaves] ) - assumptions = get_assumptions_list(evaluation) +def algebraic_expand_assumptions(assumptions_list, evaluation): + global logical_algebraic_rules + ensure_logical_algebraic_rules() + new_assumptions_list = [] + changed = False + # First apply standard rules of reduction. + # These rules are generated the first time that are used. + for assumption in assumptions_list: + assumption, applied = assumption.apply_rules( + logical_algebraic_rules, evaluation + ) + changed = changed or applied + new_assumptions_list.append(assumption) + if changed: + return new_assumptions_list, changed + # If not changed, let's try with the next set of rules + for assumption in assumptions_list: + if assumption.has_form("Not", 1): + nas, local_change = algebraic_expand_assumptions( + [assumption._leaves[0]], evaluation + ) + if local_change: + changed = local_change + for na in nas: + if na.has_form("Not", 1): + new_assumptions_list.append(na._leaves[0]) + else: + new_assumptions_list.append(Expression("Not", na)) + else: + new_assumptions_list.append(assumption) + elif assumption.has_form(("Equal", "Unequal", "Equivalent"), (3, None)): + leaves = assumption.leaves() + head = assumption.get_head() + changed = True + for i in range(len(leaves)): + for j in range(i): + new_assumptions_list.append(Expression(head, leaves[i], leaves[j])) + new_assumptions_list.append(Expression(head, leaves[j], leaves[i])) + elif assumption.has_form( + ("Less", "Greater", "LessEqual", "GreaterEqual"), (3, None) + ): + leaves = assumption.leaves() + head = assumption.get_head() + changed = True + for i in range(len(leaves)): + for j in range(i): + new_assumptions_list.append(Expression(head, leaves[i], leaves[j])) + else: + new_assumptions_list.append(assumption) + + if changed: + assumptions_list = remove_duplicated_assumptions( + new_assumptions_list, evaluation + ) + new_assumptions_list = [] + for assumption in assumptions_list: + assumption, applied = assumption.apply_rules( + logical_algebraic_rules, evaluation + ) + new_assumptions_list.append(assumption) + return new_assumptions_list, changed + + +def get_assumption_rules_dispatch(evaluation): + # TODO: cache the generated rules... + assumptions_list = get_assumptions_list(evaluation) + if assumptions_list is None: + return None + + # check for consistency: + consistent_assumptions = Expression("And", *assumptions_list) + val_consistent_assumptions = consistent_assumptions.evaluate(evaluation) + if val_consistent_assumptions == SymbolFalse: + evaluation.message("Inconsistent assumptions") + + if assumptions_list is None: + return remove_nots_when_unnecesary(pred, evaluation).evaluate(evaluation) + + # Expands Logically + assumptions_list, cont = logical_expand_assumptions(assumptions_list, evaluation) + while cont: + assumptions_list, cont = logical_expand_assumptions( + assumptions_list, evaluation + ) + + # Expands algebraically + assumptions_list, cont = algebraic_expand_assumptions(assumptions_list, evaluation) + while cont: + assumptions_list, cont = algebraic_expand_assumptions( + assumptions_list, evaluation + ) assumption_rules = [] - for assumption in assumptions: - true_state = True - while assumption.has_form("Not",1): - true_state = False - assumption = assumption._leaves[0] - if true_state: - assumption_rules.append(Rule(assumption, SymbolTrue)) + for pat in assumptions_list: + value = True + while pat.has_form("Not", 1): + value = not value + pat = pat._leaves[0] + + if value: + symbol_value = SymbolTrue + symbol_negate_value = SymbolFalse else: - assumption_rules.append(Rule(assumption, SymbolFalse)) - - pred, changed = pred.apply_rules(assumption_rules, evaluation) + symbol_value = SymbolFalse + symbol_negate_value = SymbolTrue + + if pat.has_form("Equal", 2): + if value: + lhs, rhs = pat._leaves + if lhs.is_numeric(): + assumption_rules.append(Rule(rhs, lhs)) + else: + assumption_rules.append(Rule(lhs, rhs)) + else: + assumption_rules.append(Rule(pat, SymbolFalse)) + symm_pat = Expression(pat._head, pat._leaves[1], pat._leaves[0]) + assumption_rules.append(Rule(symm_pat, SymbolFalse)) + elif pat.has_form("Equivalent", 2): + assumption_rules.append(Rule(pat, symbol_value)) + symm_pat = Expression(pat._head, pat._leaves[1], pat._leaves[0]) + assumption_rules.append(Rule(symm_pat, symbol_value)) + elif pat.has_form("Less", 2): + if value: + assumption_rules.append(Rule(pat, SymbolTrue)) + assumption_rules.append( + Rule( + Expression(pat._head, pat._leaves[1], pat._leaves[0]), + SymbolFalse, + ) + ) + for head in ("Equal", "Equivalent"): + assumption_rules.append( + Rule( + Expression(head, pat._leaves[0], pat._leaves[1]), + SymbolFalse, + ) + ) + assumption_rules.append( + Rule( + Expression(head, pat._leaves[1], pat._leaves[0]), + SymbolFalse, + ) + ) + else: + assumption_rules.append(Rule(pat, SymbolFalse)) + else: + assumption_rules.append(Rule(pat, symbol_value)) + # TODO: expand the pred and assumptions into an standard, + # atomized form, and then apply the rules... + print("assumptions:") + for assump in assumption_rules: + print("* ", assump) + return assumption_rules + + +def evaluate_predicate(pred, evaluation): + global logical_algebraic_rules + global remove_not_rules + + if pred.has_form(("List", "Sequence"), None): + return Expression( + pred._head, *[evaluate_predicate(subp, evaluation) for subp in pred._leaves] + ) + + debug_logical_expr("reducing ", pred, evaluation) + ensure_logical_algebraic_rules() pred = pred.evaluate(evaluation) + debug_logical_expr("-> ", pred, evaluation) + cc = True + while cc: + pred, cc = pred.apply_rules(logical_algebraic_rules, evaluation) + debug_logical_expr("-> ", pred, evaluation) + if pred.is_true() or pred == SymbolFalse: + return pred + + assumption_rules = get_assumption_rules_dispatch(evaluation) + if assumption_rules is not None: + debug_logical_expr(" Now, using the assumptions over ", pred, evaluation) + changed = True + while changed: + pred, changed = pred.apply_rules(assumption_rules, evaluation) + debug_logical_expr(" -> ", pred, evaluation) + + pred = remove_nots_when_unnecesary(pred, evaluation).evaluate(evaluation) return pred diff --git a/test/test_assumptions.py b/test/test_assumptions.py new file mode 100644 index 0000000000..78cb2e20fc --- /dev/null +++ b/test/test_assumptions.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from .helper import check_evaluation +import pytest + +from mathics_scanner.errors import IncompleteSyntaxError + +list_test_assumptions_integrate = [ + ( + "Integrate[x^n, {x, 0, 1}]", + "Piecewise[{{1 / (1 + n), 1 + Re[n] > 0 && n > -Infinity && n < Infinity && n != -1}}, Infinity]", + "This is so complicated due the sympy result is wrong...", + ), + ( + "Assuming[0 < n < 1, Integrate[x^n, {x, 0, 1}]]", + "Piecewise[{{1 / (1 + n), 1 + Re[n] > 0 && n > -Infinity && n < Infinity && n != -1}}, Infinity]", + "", + ), + ( + "Assuming[0 < Re[n] + 1, Integrate[x^n, {x, 0, 1}]]", + "Piecewise[{{1 / (1 + n), n > -Infinity && n < Infinity && n != -1}}, Infinity]", + "", + ), + ("Assuming[n == 1, Integrate[x^n, {x, 0, 1}]]", "1 / 2", ""), + ("Assuming[n == 2, Integrate[x^n, {x, 0, 1}]]", "1 / 3", ""), + ("Assuming[n == -1, Integrate[x^n, {x, 0, 1}]]", "Infinity", ""), + # ("Assuming[12, n>=3], Integrate[x^n, {x, 0, 1}]]", "x^(n+1)/(n+1)", ""), +] + +list_test_assumptions_simplify = [ + ("Simplify[a==b || a!=b]", "True", "",), + ("Simplify[a==b && a!=b]", "False", "",), + ("Simplify[a<=b && a>b]", "False", "",), + ("Simplify[a==b, ! a!=b]", "True", "",), + ("Simplify[a==b, a!=b]", "False", "",), + ("Simplify[a > b, {a==4}]", "b < 4", "",), + ("Simplify[And[a>b, bb", "",), + ("Simplify[Or[a>b, ab, bb", "",), + ("Simplify[a>b, {b<=a}]", "a>b", "",), +] + + +@pytest.mark.parametrize( + ("str_expr", "str_expected", "message"), list_test_assumptions_integrate, +) +@pytest.mark.xfail +def test_assumptions_integrate(str_expr, str_expected, message): + check_evaluation(str_expr, str_expected) + + +@pytest.mark.parametrize( + ("str_expr", "str_expected", "message"), list_test_assumptions_simplify, +) +@pytest.mark.xfail +def test_assumptions_simplify(str_expr, str_expected, message): + check_evaluation(str_expr, str_expected) From dbace9b78d6562e900d52116d39d7e1551094da9 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sat, 1 May 2021 16:20:12 -0300 Subject: [PATCH 11/13] catch malformed Pattern --- mathics/builtin/base.py | 6 ++++++ mathics/builtin/patterns.py | 27 +++++++++++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index c74064ecdf..87b6b3cc79 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -721,11 +721,17 @@ def boxes_to_tex(self, leaves, **options) -> str: class PatternError(Exception): def __init__(self, name, tag, *args): super().__init__() + self.name = name + self.tag = tag + self.args = args class PatternArgumentError(PatternError): def __init__(self, name, count, expected): super().__init__(None, None) + self.name = name + self.count = count + self.expected = expected class PatternObject(InstanceableBuiltin, Pattern): diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 92e7a8e76f..e22181db19 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -45,7 +45,9 @@ Integer, Rational, Real, + SymbolFalse, SymbolList, + SymbolTrue, ) from mathics.core.rules import Rule from mathics.core.pattern import Pattern, StopGenerator @@ -451,6 +453,9 @@ class PatternTest(BinaryOperator, PatternObject): = True >> MatchQ[-3, _Integer?(#>0&)] = False + >> MatchQ[3, Pattern[3]] + : First element in pattern Pattern[3] is not a valid pattern name. + = False """ operator = "?" @@ -660,6 +665,9 @@ class MatchQ(Builtin): = False >> MatchQ[_Integer][123] = True + >> MatchQ[3, Pattern[3]] + : First element in pattern Pattern[3] is not a valid pattern name. + = False """ rules = {"MatchQ[form_][expr_]": "MatchQ[expr, form]"} @@ -667,9 +675,13 @@ class MatchQ(Builtin): def apply(self, expr, form, evaluation): "MatchQ[expr_, form_]" - if match(expr, form, evaluation): - return Symbol("True") - return Symbol("False") + try: + if match(expr, form, evaluation): + return SymbolTrue + return SymbolFalse + except PatternError as e: + evaluation.message(e.name, e.tag, *(e.args)) + return SymbolFalse class Verbatim(PatternObject): @@ -801,10 +813,13 @@ class Pattern_(PatternObject): } def init(self, expr): - super(Pattern_, self).init(expr) - self.varname = expr.leaves[0].get_name() - if self.varname is None: + if len(expr.leaves) != 2: + self.error("patvar", expr) + varname = expr.leaves[0].get_name() + if varname is None or varname == "": self.error("patvar", expr) + super(Pattern_, self).init(expr) + self.varname = varname self.pattern = Pattern.create(expr.leaves[1]) def __repr__(self): From d702b8caaeae6abf44ecf1defe0c640745abbcf4 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sat, 1 May 2021 17:28:13 -0300 Subject: [PATCH 12/13] fixing comments --- mathics/builtin/inference.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mathics/builtin/inference.py b/mathics/builtin/inference.py index d05ffbc7d4..94c296203d 100644 --- a/mathics/builtin/inference.py +++ b/mathics/builtin/inference.py @@ -272,9 +272,6 @@ def get_assumption_rules_dispatch(evaluation): if val_consistent_assumptions == SymbolFalse: evaluation.message("Inconsistent assumptions") - if assumptions_list is None: - return remove_nots_when_unnecesary(pred, evaluation).evaluate(evaluation) - # Expands Logically assumptions_list, cont = logical_expand_assumptions(assumptions_list, evaluation) while cont: @@ -345,9 +342,8 @@ def get_assumption_rules_dispatch(evaluation): assumption_rules.append(Rule(pat, symbol_value)) # TODO: expand the pred and assumptions into an standard, # atomized form, and then apply the rules... - print("assumptions:") - for assump in assumption_rules: - print("* ", assump) + if len(assumption_rules) == 0: + return None return assumption_rules @@ -372,6 +368,9 @@ def evaluate_predicate(pred, evaluation): return pred assumption_rules = get_assumption_rules_dispatch(evaluation) + if assumption_rules is None: + return remove_nots_when_unnecesary(pred, evaluation).evaluate(evaluation) + if assumption_rules is not None: debug_logical_expr(" Now, using the assumptions over ", pred, evaluation) changed = True From f0e9cb453e513ca8cc83dd0daa1306c473e4f664 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 2 May 2021 21:36:18 -0300 Subject: [PATCH 13/13] first try: implementing <> Dispatch tables --- mathics/builtin/patterns.py | 101 ++++++++++++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 92e7a8e76f..7f673af56a 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -34,11 +34,13 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.base import Builtin, BinaryOperator, PostfixOperator +from mathics.builtin.base import Builtin, BinaryOperator, PostfixOperator, AtomBuiltin from mathics.builtin.base import PatternObject, PatternError from mathics.builtin.lists import python_levelspec, InvalidLevelspecError from mathics.core.expression import ( + Atom, + String, Symbol, Expression, Number, @@ -101,13 +103,16 @@ class RuleDelayed(BinaryOperator): def create_rules(rules_expr, expr, name, evaluation, extra_args=[]): - if rules_expr.has_form("Dispatch", None): - rules_expr = rules_expr.leaves[0] + if isinstance(rules_expr, Dispatch): + return rules_expr.rules, False + elif rules_expr.has_form("Dispatch", None): + return Dispatch(rules_expr._leaves, evaluation) + if rules_expr.has_form("List", None): rules = rules_expr.leaves else: rules = [rules_expr] - any_lists = any(item.has_form("List", None) for item in rules) + any_lists = any(item.has_form(("List", "Dispatch"), None) for item in rules) if any_lists: all_lists = all(item.has_form("List", None) for item in rules) if all_lists: @@ -285,10 +290,8 @@ def apply(self, expr, rules, evaluation): "ReplaceAll[expr_, rules_]" try: rules, ret = create_rules(rules, expr, "ReplaceAll", evaluation) - if ret: return rules - result, applied = expr.apply_rules(rules, evaluation) return result except PatternError: @@ -1450,7 +1453,33 @@ def yield_match(vars, rest): ) -class Dispatch(Builtin): +class Dispatch(Atom): + def __init__(self, rulelist, evaluation): + self.src = Expression(SymbolList, *rulelist) + self.rules = [Rule(rule._leaves[0], rule._leaves[1]) for rule in rulelist] + self._leaves = None + self._head = Symbol("Dispatch") + + def get_sort_key(self): + return self.src.get_sort_key() + + def get_atom_name(self): + return "System`Dispatch" + + def __repr__(self): + return "dispatch" + + def atom_to_boxes(self, f, evaluation): + leaves = self.src.format(evaluation, f.get_name()) + return Expression( + "RowBox", + Expression( + SymbolList, String("Dispatch"), String("["), leaves, String("]") + ), + ) + + +class DispatchAtom(AtomBuiltin): """
'Dispatch[$rulelist$]' @@ -1458,10 +1487,23 @@ class Dispatch(Builtin): In the future, it should return an optimized DispatchRules atom, containing an optimized set of rules.
- + >> rules = {{a_,b_}->a^b, {1,2}->3., F[x_]->x^2}; + >> F[2] /. rules + = 4 + >> dispatchrules = Dispatch[rules] + = Dispatch[{{a_, b_} -> a ^ b, {1, 2} -> 3., F[x_] -> x ^ 2}] + >> F[2] /. dispatchrules + = 4 """ - def apply_stub(self, rules, evaluation): + messages = { + "invrpl": "`1` is not a valid rule or list of rules.", + } + + def __repr__(self): + return "dispatchatom" + + def apply_create(self, rules, evaluation): """Dispatch[rules_List]""" # TODO: # The next step would be to enlarge this method, in order to @@ -1471,4 +1513,43 @@ def apply_stub(self, rules, evaluation): # compiled patters, and modify Replace and ReplaceAll to handle this # kind of objects. # - return rules + if isinstance(rules, Dispatch): + return rules + if rules.is_symbol(): + rules = rules.evaluate(evaluation) + + if rules.has_form("List", None): + rules = rules._leaves + else: + rules = [rules] + + all_list = all(rule.has_form("List", None) for rule in rules) + if all_list: + leaves = [self.apply_create(rule, evaluation) for rule in rules] + return Expression(SymbolList, *leaves) + flatten_list = [] + for rule in rules: + if rule.is_symbol(): + rule = rule.evaluate(evaluation) + if rule.has_form("List", None): + flatten_list.extend(rule._leaves) + elif rule.has_form(("Rule", "RuleDelayed"), 2): + flatten_list.append(rule) + elif isinstance(rule, Dispatch): + flatten_list.extend(rule.src._leaves) + else: + # WMA does not raise this message: just leave it unevaluated, + # and raise an error when the dispatch rule is used. + evaluation.message("Dispatch", "invrpl", rule) + return + try: + return Dispatch(flatten_list, evaluation) + except: + return + + def apply_normal(self, dispatch, evaluation): + """Normal[dispatch_Dispatch]""" + if isinstance(dispatch, Dispatch): + return dispatch.src + else: + return dispatch._leaves[0]