From 58674ebdb19b3c3deedd3033a1beb38de9e68fa4 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jun 2022 08:22:58 -0400 Subject: [PATCH 01/13] About 20 more Expression conversions --- mathics/builtin/datentime.py | 2 +- mathics/builtin/distance/stringdata.py | 5 +- mathics/builtin/drawing/image.py | 50 ++++--- mathics/builtin/drawing/plot.py | 56 ++++---- mathics/builtin/inference.py | 4 +- mathics/builtin/intfns/combinatorial.py | 8 +- mathics/builtin/structure.py | 168 ++++++++++++++---------- mathics/core/systemsymbols.py | 1 + 8 files changed, 170 insertions(+), 124 deletions(-) diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index f7f760b34..a8d4bf2e8 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -678,7 +678,7 @@ def apply_makeboxes(self, datetime, gran, cal, tz, fmt, evaluation): fmtds = Expression(SymbolDateString, datetime, fmt).evaluate(evaluation) if fmtds is None: return - # tz = Expression("ToString", tz).evaluate(evaluation) + # tz_string = to_mathic_expression("ToString", tz).evaluate(evaluation) tz_string = String(str(int(tz.to_python()))) return to_expression( SymbolRowBox, to_mathics_list("[", fmtds, " GTM", tz_string, "]") diff --git a/mathics/builtin/distance/stringdata.py b/mathics/builtin/distance/stringdata.py index abdb82a31..216b769a5 100644 --- a/mathics/builtin/distance/stringdata.py +++ b/mathics/builtin/distance/stringdata.py @@ -10,10 +10,11 @@ from mathics.builtin.base import Builtin +from mathics.core.atoms import Integer, String, Symbol from mathics.core.expression import Expression -from mathics.core.atoms import Integer, String from mathics.core.symbols import SymbolTrue +SymbolEditDistance = Symbol("EditDistance") # Levenshtein's algorithm is defined by the following construction: # (adapted from https://de.wikipedia.org/wiki/Levenshtein-Distanz) @@ -139,7 +140,7 @@ def normalize(c): elif a.get_head_name() == "System`List" and b.get_head_name() == "System`List": return Integer(self._distance(a.leaves, b.leaves, lambda u, v: u.sameQ(v))) else: - return Expression("EditDistance", a, b) + return Expression(SymbolEditDistance, a, b) class DamerauLevenshteinDistance(_StringDistance): diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 1096f69b1..3ddeadccd 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -15,18 +15,6 @@ from mathics.builtin.base import Builtin, AtomBuiltin, Test, String from mathics.builtin.box.image import ImageBox -from mathics.core.atoms import ( - Atom, - Integer, - Integer0, - Integer1, - MachineReal, - Rational, - Real, - SymbolDivide, - from_python, -) -from mathics.core.expression import Expression from mathics.builtin.colors.color_internals import ( convert_color, colorspaces as known_colorspaces, @@ -41,10 +29,28 @@ numpy_flip, convolve, ) + +from mathics.core.atoms import ( + Atom, + Integer, + Integer0, + Integer1, + MachineReal, + Rational, + Real, + SymbolDivide, + from_python, +) +from mathics.core.expression import Expression from mathics.core.list import to_mathics_list, ListExpression from mathics.core.symbols import Symbol, SymbolNull, SymbolTrue from mathics.core.systemsymbols import SymbolRule, SymbolSimplify +SymbolColorQuantize = Symbol("ColorQuantize") +SymbolImage = Symbol("Image") +SymbolMatrixQ = Symbol("MatrixQ") +SymbolThreshold = Symbol("Threshold") + _image_requires = ("numpy", "PIL") @@ -1309,7 +1315,7 @@ def apply(self, image, n: Integer, evaluation): py_value = n.value if py_value <= 0: return evaluation.message( - "ColorQuantize", "intp", Expression("ColorQuantize", image, n), 2 + "ColorQuantize", "intp", Expression(SymbolColorQuantize, image, n), 2 ) converted = image.color_convert("RGB") if converted is None: @@ -1397,7 +1403,9 @@ class Binarize(_SkimageBuiltin): def apply(self, image, evaluation): "Binarize[image_Image]" image = image.grayscale() - thresh = Expression("Threshold", image).evaluate(evaluation).round_to_float() + thresh = ( + Expression(SymbolThreshold, image).evaluate(evaluation).round_to_float() + ) if thresh is not None: return Image(image.pixels > thresh, "Grayscale") @@ -1458,7 +1466,10 @@ def apply(self, channels, colorspace, evaluation): numpy_channels = [] for channel in channels.elements: - if not Expression("MatrixQ", channel).evaluate(evaluation) is SymbolTrue: + if ( + not Expression(SymbolMatrixQ, channel).evaluate(evaluation) + is SymbolTrue + ): return numpy_channels.append(matrix_to_numpy(channel)) @@ -1532,7 +1543,7 @@ def apply(self, values, evaluation, options): pixels = values.grayscale().pixels matrix = pixels_as_ubyte(pixels.reshape(pixels.shape[:2])) else: - if not Expression("MatrixQ", values).evaluate(evaluation) is SymbolTrue: + if not Expression(SymbolMatrixQ, values).evaluate(evaluation) is SymbolTrue: return matrix = matrix_to_numpy(values) @@ -1833,7 +1844,7 @@ def apply(self, image, evaluation): class ImageType(_ImageBuiltin): """
-
'ImageType[$image$]' +
'ImageType[$image$]'
gives the interval storage type of $image$, e.g. "Real", "Bit32", or "Bit".
@@ -2108,8 +2119,7 @@ def storage_type(self): return str(dtype) def options(self): - return Expression( - "List", + return ListExpression( Expression(SymbolRule, String("ColorSpace"), String(self.color_space)), Expression(SymbolRule, String("MetaInformation"), self.metadata), ) @@ -2140,7 +2150,7 @@ def apply_create(self, array, evaluation): is_rgb = len(shape) == 3 and shape[2] in (3, 4) return Image(pixels.clip(0, 1), "RGB" if is_rgb else "Grayscale") else: - return Expression("Image", array) + return Expression(SymbolImage, array) # complex operations diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 5c3652c71..33c8957ed 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -7,12 +7,11 @@ from math import sin, cos, pi, sqrt, isnan, isinf -import numbers import itertools +import numbers import palettable -from mathics.core.evaluators import apply_N from mathics.builtin.numeric import chop from mathics.builtin.base import Builtin from mathics.builtin.graphics import Graphics @@ -30,6 +29,7 @@ from_python, ) from mathics.core.attributes import hold_all, protected +from mathics.core.evaluators import apply_N from mathics.core.expression import Expression from mathics.core.list import ListExpression, to_mathics_list from mathics.core.symbols import Symbol, SymbolList, SymbolN, SymbolPower, SymbolTrue @@ -39,6 +39,7 @@ SymbolEdgeForm, SymbolFunction, SymbolGraphics, + SymbolGraphics3D, SymbolGrid, SymbolMessageName, SymbolMap, @@ -54,9 +55,14 @@ RealPoint6 = Real(0.6) RealPoint2 = Real(0.2) + +SymbolColorDataFunction = Symbol("ColorDataFunction") +SymbolDisk = Symbol("Disk") +SymbolFaceForm = Symbol("FaceForm") SymbolHue = Symbol("Hue") SymbolLine = Symbol("Line") SymbolRectangle = Symbol("Rectangle") +SymbolText = Symbol("Text") try: from mathics.builtin.compile import _compile, CompileArg, CompileError, real_type @@ -113,8 +119,8 @@ class ColorDataFunction(Builtin): class _GradientColorScheme(object): def color_data_function(self, name): - colors = Expression( - "List", *[Expression(SymbolRGBColor, *color) for color in self.colors()] + colors = ListExpression( + *[Expression(SymbolRGBColor, *color) for color in self.colors()] ) blend = Expression( SymbolFunction, @@ -126,7 +132,7 @@ def color_data_function(self, name): ListExpression(Integer0, Integer1), blend, ] - return Expression("ColorDataFunction", *arguments) + return Expression(SymbolColorDataFunction, *arguments) class _PredefinedGradient(_GradientColorScheme): @@ -821,7 +827,7 @@ def box(color): return Expression( SymbolGraphics, ListExpression( - Expression("FaceForm", color), Expression(SymbolRectangle) + Expression(SymbolFaceForm, color), Expression(SymbolRectangle) ), Expression( SymbolRule, @@ -974,7 +980,7 @@ def _draw(self, data, color, evaluation, options): segment_spacing = 0.0 # not yet implemented; needs real arc graphics radius_spacing = max(0.0, min(1.0, sector_spacing.leaves[1].round_to_float())) - def vector2(x, y): + def vector2(x, y) -> ListExpression: return ListExpression(Real(x), Real(y)) def radii(): @@ -1020,25 +1026,27 @@ def segments(): for k, (phi0, phi1) in enumerate(phis(values)): yield Expression( SymbolStyle, - Expression("Disk", origin, radius, vector2(phi0, phi1)), + Expression(SymbolDisk, origin, radius, vector2(phi0, phi1)), color(k + 1, n), ) if r1 > 0.0: yield Expression( SymbolStyle, - Expression("Disk", origin, vector2(r1, r1)), + Expression(SymbolDisk, origin, vector2(r1, r1)), Symbol("White"), ) def labels(names): - yield Expression("FaceForm", Symbol("Black")) + yield Expression(SymbolFaceForm, Symbol("Black")) for values, (r0, r1) in zip(data, radii()): for name, (phi0, phi1) in zip(names, phis(values)): r = (r0 + r1) / 2.0 phi = (phi0 + phi1) / 2.0 - yield Expression("Text", name, vector2(r * cos(phi), r * sin(phi))) + yield Expression( + SymbolText, name, vector2(r * cos(phi), r * sin(phi)) + ) graphics = list(segments()) @@ -1046,8 +1054,8 @@ def labels(names): if chart_labels.get_head_name() == "System`List": graphics.extend(list(labels(chart_labels.leaves))) - options["System`PlotRange"] = Expression( - "List", vector2(-2.0, 2.0), vector2(-2.0, 2.0) + options["System`PlotRange"] = ListExpression( + vector2(-2.0, 2.0), vector2(-2.0, 2.0) ) return Expression( @@ -1105,7 +1113,7 @@ class BarChart(_Chart): ) def _draw(self, data, color, evaluation, options): - def vector2(x, y): + def vector2(x, y) -> ListExpression: return to_mathics_list(x, y) def boxes(): @@ -1147,7 +1155,7 @@ def rectangles(): ) def axes(): - yield Expression("FaceForm", Symbol("Black")) + yield Expression(SymbolFaceForm, Symbol("Black")) def points(x): return ListExpression(vector2(x, 0), vector2(x, Real(-0.2))) @@ -1159,12 +1167,14 @@ def points(x): yield Expression(SymbolLine, points(x1)) def labels(names): - yield Expression("FaceForm", Symbol("Black")) + yield Expression(SymbolFaceForm, Symbol("Black")) for (k, n), x0, x1, y in boxes(): if k <= len(names): name = names[k - 1] - yield Expression("Text", name, vector2((x0 + x1) / 2, Real(-0.2))) + yield Expression( + SymbolText, name, vector2((x0 + x1) / 2, Real(-0.2)) + ) x_coords = list(itertools.chain(*[[x0, x1] for (k, n), x0, x1, y in boxes()])) y_coords = [0] + [y for (k, n), x0, x1, y in boxes()] @@ -2473,7 +2483,7 @@ def construct_graphics( for p1, p2, p3 in triangles: graphics.append( Expression( - "Polygon", + SymbolPolygon, ListExpression( to_mathics_list(*p1), to_mathics_list(*p2), @@ -2497,7 +2507,7 @@ def construct_graphics( def final_graphics(self, graphics, options): return Expression( - "Graphics3D", + SymbolGraphics3D, ListExpression(*graphics), *options_to_rules(options, Graphics3D.options) ) @@ -2559,9 +2569,9 @@ def construct_graphics( if color_function.get_name() == "System`Automatic": color_function = String("LakeColors") if color_function.get_string_value(): - func = Expression("ColorData", color_function.get_string_value()).evaluate( - evaluation - ) + func = Expression( + SymbolColorData, color_function.get_string_value() + ).evaluate(evaluation) if func.has_form("ColorDataFunction", 4): color_function_min = func.leaves[2].leaves[0].round_to_float() color_function_max = func.leaves[2].leaves[1].round_to_float() @@ -2625,7 +2635,7 @@ def eval_color(x, y, v): graphics.append( Expression( - "Polygon", + SymbolPolygon, ListExpression(*points), Expression( SymbolRule, diff --git a/mathics/builtin/inference.py b/mathics/builtin/inference.py index c3e024440..84eddd41c 100644 --- a/mathics/builtin/inference.py +++ b/mathics/builtin/inference.py @@ -176,7 +176,9 @@ def logical_expand_assumptions(assumptions_list, evaluation): new_assumptions_list.append(Expression(SymbolNot, element)) continue if sentence.has_form("And", None): - elements = (Expression("Not", element) for element in sentence.elements) + elements = ( + Expression(SymbolNot, element) for element in sentence.elements + ) new_assumptions_list.append(Expression(SymbolOr, *elements)) continue if sentence.has_form("Implies", 2): diff --git a/mathics/builtin/intfns/combinatorial.py b/mathics/builtin/intfns/combinatorial.py index cf8fda1fc..824b88504 100644 --- a/mathics/builtin/intfns/combinatorial.py +++ b/mathics/builtin/intfns/combinatorial.py @@ -8,9 +8,11 @@ """ +from mathics.builtin.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin -from mathics.core.expression import Expression from mathics.core.atoms import Integer +from mathics.core.attributes import listable, numeric_function, orderless, protected +from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import ( Atom, @@ -21,8 +23,6 @@ SymbolTimes, SymbolTrue, ) -from mathics.builtin.arithmetic import _MPMathFunction -from mathics.core.attributes import listable, numeric_function, orderless, protected from itertools import combinations SymbolBinomial = Symbol("Binomial") @@ -456,7 +456,7 @@ def apply_1(self, list, n, evaluation): def apply_2(self, list, n, evaluation): "Subsets[list_, Pattern[n,_List|All|DirectedInfinity[1]]]" - expr = Expression("Subsets", list, n) + expr = Expression(SymbolSubsets, list, n) if isinstance(list, Atom): return evaluation.message("Subsets", "normal", expr) diff --git a/mathics/builtin/structure.py b/mathics/builtin/structure.py index 77f11cb11..29fcda940 100644 --- a/mathics/builtin/structure.py +++ b/mathics/builtin/structure.py @@ -3,6 +3,7 @@ Structural Operations """ +from typing import Iterable from mathics.builtin.base import ( Builtin, @@ -18,7 +19,7 @@ Rational, ) from mathics.core.expression import Expression -from mathics.core.list import ListExpression +from mathics.core.list import ListExpression, to_mathics_list from mathics.core.rules import Pattern from mathics.core.symbols import ( Atom, @@ -28,7 +29,7 @@ SymbolTrue, ) -from mathics.core.systemsymbols import SymbolDirectedInfinity +from mathics.core.systemsymbols import SymbolDirectedInfinity, SymbolMap, SymbolRule from mathics.builtin.lists import ( python_levelspec, @@ -45,6 +46,10 @@ bytecount_support = True +SymbolMapThread = Symbol("MapThread") +SymbolOperate = Symbol("Operate") +SymbolSortBy = Symbol("SortBy") + class Sort(Builtin): """ @@ -126,26 +131,27 @@ class SortBy(Builtin): = {{10, -9}, {5, 1}} """ - summary_text = "sort by the values of a function applied to elements" - rules = { - "SortBy[f_]": "SortBy[#, f]&", - } - messages = { "list": "List expected at position `2` in `1`.", "func": "Function expected at position `2` in `1`.", } + rules = { + "SortBy[f_]": "SortBy[#, f]&", + } + + summary_text = "sort by the values of a function applied to elements" + def apply(self, li, f, evaluation): "SortBy[li_, f_]" if isinstance(li, Atom): return evaluation.message("Sort", "normal") elif li.get_head_name() != "System`List": - expr = Expression("SortBy", li, f) + expr = Expression(SymbolSortBy, li, f) return evaluation.message(self.get_name(), "list", expr, 1) else: - keys_expr = Expression("Map", f, li).evaluate(evaluation) # precompute: + keys_expr = Expression(SymbolMap, f, li).evaluate(evaluation) # precompute: # even though our sort function has only (n log n) comparisons, we should # compute f no more than n times. @@ -154,7 +160,7 @@ def apply(self, li, f, evaluation): or keys_expr.get_head_name() != "System`List" or len(keys_expr.elements) != len(li.elements) ): - expr = Expression("SortBy", li, f) + expr = Expression(SymbolSortBy, li, f) return evaluation.message("SortBy", "func", expr, 2) keys = keys_expr.elements @@ -212,13 +218,14 @@ class BinarySearch(Builtin): = 2 """ - summary_text = "search a sorted list for a key" context = "CombinatoricaOld`" rules = { "CombinatoricaOld`BinarySearch[l_List, k_] /; Length[l] > 0": "CombinatoricaOld`BinarySearch[l, k, Identity]" } + summary_text = "search a sorted list for a key" + def apply(self, l, k, f, evaluation): "CombinatoricaOld`BinarySearch[l_List, k_, f_] /; Length[l] > 0" @@ -380,33 +387,37 @@ def apply(self, expr, evaluation): class ApplyLevel(BinaryOperator): """
-
'ApplyLevel[$f$, $expr$]' -
'$f$ @@@ $expr$' -
is equivalent to 'Apply[$f$, $expr$, {1}]'. +
'ApplyLevel[$f$, $expr$]' + +
'$f$ @@@ $expr$' +
is equivalent to 'Apply[$f$, $expr$, {1}]'.
>> f @@@ {{a, b}, {c, d}} = {f[a, b], f[c, d]} """ - summary_text = "apply a function to a list, at the top level" + grouping = "Right" operator = "@@@" precedence = 620 - grouping = "Right" rules = { "ApplyLevel[f_, expr_]": "Apply[f, expr, {1}]", } + summary_text = "apply a function to a list, at the top level" + class Apply(BinaryOperator): """
-
'Apply[$f$, $expr$]' -
'$f$ @@ $expr$' -
replaces the head of $expr$ with $f$. -
'Apply[$f$, $expr$, $levelspec$]' -
applies $f$ on the parts specified by $levelspec$. +
'Apply[$f$, $expr$]' + +
'$f$ @@ $expr$' +
replaces the head of $expr$ with $f$. + +
'Apply[$f$, $expr$, $levelspec$]' +
applies $f$ on the parts specified by $levelspec$.
>> f @@ {1, 2, 3} @@ -477,10 +488,11 @@ def callback(level): class Map(BinaryOperator): """
-
'Map[$f$, $expr$]' or '$f$ /@ $expr$' -
applies $f$ to each part on the first level of $expr$. -
'Map[$f$, $expr$, $levelspec$]' -
applies $f$ to each level specified by $levelspec$ of $expr$. +
'Map[$f$, $expr$]' or '$f$ /@ $expr$' +
applies $f$ to each part on the first level of $expr$. + +
'Map[$f$, $expr$, $levelspec$]' +
applies $f$ to each level specified by $levelspec$ of $expr$.
>> f /@ {1, 2, 3} @@ -539,8 +551,10 @@ class MapAt(Builtin):
'MapAt[$f$, $expr$, $n$]'
applies $f$ to the element at position $n$ in $expr$. If $n$ is negative, the position is counted from the end. +
'MapAt[f, $exp$r, {$i$, $j$ ...}]'
applies $f$ to the part of $expr$ at position {$i$, $j$, ...}. +
'MapAt[$f$,$pos$]'
represents an operator form of MapAt that can be applied to an expression.
@@ -592,7 +606,7 @@ def map_at_one(i, elements): "System`Rule" ): new_elements[j] = Expression( - "System`Rule", + SymbolRule, replace_leaf.elements[0], Expression(f, replace_leaf.elements[1]), ) @@ -616,9 +630,10 @@ def map_at_one(i, elements): class Scan(Builtin): """
-
'Scan[$f$, $expr$]' +
'Scan[$f$, $expr$]'
applies $f$ to each element of $expr$ and returns 'Null'. -
'Scan[$f$, $expr$, $levelspec$] + +
'Scan[$f$, $expr$, $levelspec$]
applies $f$ to each level specified by $levelspec$ of $expr$.
@@ -676,11 +691,11 @@ def callback(level): class MapIndexed(Builtin): """
-
'MapIndexed[$f$, $expr$]' -
applies $f$ to each part on the first level of $expr$, including the part positions - in the call to $f$. -
'MapIndexed[$f$, $expr$, $levelspec$]' -
applies $f$ to each level specified by $levelspec$ of $expr$. +
'MapIndexed[$f$, $expr$]' +
applies $f$ to each part on the first level of $expr$, including the part positions in the call to $f$. + +
'MapIndexed[$f$, $expr$, $levelspec$]' +
applies $f$ to each level specified by $levelspec$ of $expr$.
>> MapIndexed[f, {a, b, c}] @@ -733,8 +748,10 @@ def apply_level(self, f, expr, ls, evaluation, options={}): evaluation.message("MapIndexed", "level", ls) return - def callback(level, pos): - return Expression(f, level, Expression("List", *[Integer(p) for p in pos])) + def callback(level, pos: Iterable): + return Expression( + f, level, to_mathics_list(*pos, elements_conversion_fn=Integer) + ) heads = self.get_option(options, "Heads", evaluation) is SymbolTrue result, depth = walk_levels( @@ -747,9 +764,10 @@ def callback(level, pos): class MapThread(Builtin): """
-
'MapThread[$f$, {{$a1$, $a2$, ...}, {$b1$, $b2$, ...}, ...}] +
'MapThread[$f$, {{$a1$, $a2$, ...}, {$b1$, $b2$, ...}, ...}]
returns '{$f$[$a1$, $b1$, ...], $f$[$a2$, $b2$, ...], ...}'. -
'MapThread[$f$, {$expr1$, $expr2$, ...}, $n$]' + +
'MapThread[$f$, {$expr1$, $expr2$, ...}, $n$]'
applies $f$ at level $n$.
@@ -803,9 +821,9 @@ def apply_n(self, f, expr, n, evaluation): if n is None: n = 1 - full_expr = Expression("MapThread", f, expr) + full_expr = Expression(SymbolMapThread, f, expr) else: - full_expr = Expression("MapThread", f, expr, n) + full_expr = Expression(SymbolMapThread, f, expr, n) n = n.get_int_value() if n is None or n < 0: @@ -841,8 +859,7 @@ def walk(args, depth=0): dim, len(arg.elements), ) - return Expression( - "List", + return ListExpression( *[ walk([arg.elements[i] for arg in args], depth + 1) for i in range(dim) @@ -858,10 +875,11 @@ def walk(args, depth=0): class Thread(Builtin): """
-
'Thread[$f$[$args$]]' -
threads $f$ over any lists that appear in $args$. -
'Thread[$f$[$args$], $h$]' -
threads over any parts with head $h$. +
'Thread[$f$[$args$]]' +
threads $f$ over any lists that appear in $args$. + +
'Thread[$f$[$args$], $h$]' +
threads over any parts with head $h$.
>> Thread[f[{a, b, c}]] @@ -876,7 +894,6 @@ class Thread(Builtin): = {a + d + g, b + e + g, c + f + g} """ - summary_text = '"thread" a function across lists that appear in its arguments' messages = { "tdlen": "Objects of unequal length cannot be combined.", } @@ -885,6 +902,8 @@ class Thread(Builtin): "Thread[f_[args___]]": "Thread[f[args], List]", } + summary_text = '"thread" a function across lists that appear in its arguments' + def apply(self, f, args, h, evaluation): "Thread[f_[args___], h_]" @@ -897,9 +916,8 @@ def apply(self, f, args, h, evaluation): class FreeQ(Builtin): """
-
'FreeQ[$expr$, $x$]' -
returns 'True' if $expr$ does not contain the expression - $x$. +
'FreeQ[$expr$, $x$]' +
returns 'True' if $expr$ does not contain the expression $x$.
>> FreeQ[y, x] @@ -916,13 +934,14 @@ class FreeQ(Builtin): = True """ - summary_text = ( - "test whether an expression is free of subexpressions matching a pattern" - ) rules = { "FreeQ[form_][expr_]": "FreeQ[expr, form]", } + summary_text = ( + "test whether an expression is free of subexpressions matching a pattern" + ) + def apply(self, expr, form, evaluation): "FreeQ[expr_, form_]" @@ -936,12 +955,14 @@ def apply(self, expr, form, evaluation): class Flatten(Builtin): """
-
'Flatten[$expr$]' -
flattens out nested lists in $expr$. -
'Flatten[$expr$, $n$]' -
stops flattening at level $n$. -
'Flatten[$expr$, $n$, $h$]' -
flattens expressions with head $h$ instead of 'List'. +
'Flatten[$expr$]' +
flattens out nested lists in $expr$. + +
'Flatten[$expr$, $n$]' +
stops flattening at level $n$. + +
'Flatten[$expr$, $n$, $h$]' +
flattens expressions with head $h$ instead of 'List'.
>> Flatten[{{a, b}, {c, {d}, e}, {f, {g, h}}}] @@ -994,12 +1015,6 @@ class Flatten(Builtin): = Flatten[{{1, 2}, {3, {4}}}, {{1, 2, 3}}, List] """ - summary_text = "flatten out any sequence of levels in a nested list" - rules = { - "Flatten[expr_]": "Flatten[expr, Infinity, Head[expr]]", - "Flatten[expr_, n_]": "Flatten[expr, n, Head[expr]]", - } - messages = { "flpi": ( "Levels to be flattened together in `1` " @@ -1012,6 +1027,13 @@ class Flatten(Builtin): ), } + rules = { + "Flatten[expr_]": "Flatten[expr, Infinity, Head[expr]]", + "Flatten[expr_, n_]": "Flatten[expr, n, Head[expr]]", + } + + summary_text = "flatten out any sequence of levels in a nested list" + def apply_list(self, expr, n, h, evaluation): "Flatten[expr_, n_List, h_]" @@ -1215,7 +1237,7 @@ def apply(self, p, expr, n, evaluation): head_depth = n.get_int_value() if head_depth is None or head_depth < 0: return evaluation.message( - "Operate", "intnn", Expression("Operate", p, expr, n), 3 + "Operate", "intnn", Expression(SymbolOperate, p, expr, n), 3 ) if head_depth == 0: @@ -1246,8 +1268,8 @@ def apply(self, p, expr, n, evaluation): class Through(Builtin): """
-
'Through[$p$[$f$][$x$]]' -
gives $p$[$f$[$x$]]. +
'Through[$p$[$f$][$x$]]' +
gives $p$[$f$[$x$]].
>> Through[f[g][x]] @@ -1261,17 +1283,17 @@ class Through(Builtin): def apply(self, p, args, x, evaluation): "Through[p_[args___][x___]]" - items = [] - for leaf in args.get_sequence(): - items.append(Expression(leaf, *x.get_sequence())) - return Expression(p, *items) + elements = [] + for element in args.get_sequence(): + elements.append(Expression(element, *x.get_sequence())) + return Expression(p, *elements) class ByteCount(Builtin): """
-
'ByteCount[$expr$]' -
gives the internal memory space used by $expr$, in bytes. +
'ByteCount[$expr$]' +
gives the internal memory space used by $expr$, in bytes.
The results may heavily depend on the Python implementation in use. diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 989a0941e..6f3547be7 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -52,6 +52,7 @@ SymbolGet = Symbol("Get") SymbolGoldenRatio = Symbol("GoldenRatio") SymbolGraphics = Symbol("Graphics") +SymbolGraphics3D = Symbol("Graphics3D") SymbolGreater = Symbol("Greater") SymbolGreaterEqual = Symbol("GreaterEqual") SymbolGrid = Symbol("Grid") From d349e7a6abcddd6d2a5a2cf81ddd8a12a23d9b20 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jun 2022 08:58:00 -0400 Subject: [PATCH 02/13] Changes to GS tests working without conversion These are the remaining changes that are needed to get the Gries & Schneider tests working without conversions done inside the Expression() constructor. In the benchmarking branch, we'll modify this further so we can get an additional long-running benchmark test. --- mathics/builtin/arithfns/basic.py | 2 +- mathics/builtin/numpy_utils/with_numpy.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index 0f7f12b54..336a0f216 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -357,7 +357,7 @@ def is_negative(value): op = "-" else: op = "+" - values.append(Expression(SymbolHoldForm, item)) + values.append(to_expression(SymbolHoldForm, item)) ops.append(String(op)) return Expression( SymbolInfix, diff --git a/mathics/builtin/numpy_utils/with_numpy.py b/mathics/builtin/numpy_utils/with_numpy.py index b4a2046f3..9cb29d0d4 100755 --- a/mathics/builtin/numpy_utils/with_numpy.py +++ b/mathics/builtin/numpy_utils/with_numpy.py @@ -1,17 +1,17 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ A couple of helper functions for doing numpy-like stuff with numpy. """ -from mathics.core.expression import Expression from functools import reduce -import numpy + import ast import inspect +import numpy import sys +from mathics.core.list import ListExpression # # INTERNAL FUNCTIONS @@ -154,10 +154,10 @@ def instantiate_elements(a, new_element, d=1): # if an array of dimension 'd' is reached. if len(a.shape) == d: - leaves = [new_element(x) for x in a] + elements = [new_element(x) for x in a] else: - leaves = [instantiate_elements(e, new_element, d) for e in a] - return Expression("List", *leaves) + elements = [instantiate_elements(e, new_element, d) for e in a] + return ListExpression(*elements) # From e2f02d6a3f752e7a79c5cac41007fbf7d73a5cf5 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sat, 18 Jun 2022 16:03:28 -0400 Subject: [PATCH 03/13] Revise Nintegrate to allow scipy to be optional * NIntegrate[] no longer requires scipy. * There were a number of small changes/fixes involving NIntegrate. * NIntegrate tests have been expanded. --- CHANGES.rst | 2 + mathics/algorithm/optimizers.py | 5 ++- mathics/builtin/numbers/calculus.py | 63 ++++++++++++++++------------- test/test_nintegrate.py | 9 ++++- 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 69a464533..d59808d92 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,7 @@ Enhancements * In assignment to messages associated with symbols, the attribute ``Protected`` is not having into account, following the standard in WMA. With this and the above change, Combinatorical 2.0 works as written. * ``Share[]`` performs an explicit call to the Python garbage collection and returns the amount of memory free. * Improving the compatibility of ``TeXForm`` and ``MathMLForm`` outputs with WMA. MatML tags around numbers appear as "" tags instead of "", except in the case of ``InputForm`` expressions. In TeXForm some quotes around strings have been removed to conform to WMA. It is not clear whether this is the correct behavior. +* Revise ``Nintegrate[]`` to allow scipy to be optional. @@ -97,6 +98,7 @@ Bugs * Partial fix of ``FillSimplify`` * Streams used in MathicsOpen are now freed and their file descriptors now released. Issue #326. * Some temporary files that were created are now removed from the filesystem. Issue #309. +* There were a number of small changes/fixes involving ``NIntegrate`` and its Method options. ``Nintegrate`` tests have been expanded. 4.0.1 ----- diff --git a/mathics/algorithm/optimizers.py b/mathics/algorithm/optimizers.py index 3158b8827..761fc71fa 100644 --- a/mathics/algorithm/optimizers.py +++ b/mathics/algorithm/optimizers.py @@ -360,9 +360,11 @@ def sub(evaluation): return x0, True +native_optimizer_messages = {} + native_local_optimizer_methods = { "Automatic": find_minimum_newton1d, - "newton": find_minimum_newton1d, + "Newton": find_minimum_newton1d, } native_findroot_methods = { @@ -370,6 +372,7 @@ def sub(evaluation): "Newton": find_root_newton, "Secant": find_root_secant, } +native_findroot_messages = {} def is_zero( diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index 9c7efc7d0..4e56c8316 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -1287,7 +1287,6 @@ class _BaseFinder(Builtin): """ attributes = hold_all | protected - requires = ["scipy"] methods = {} messages = { "snum": "Value `1` is not a number.", @@ -1483,9 +1482,13 @@ class FindRoot(_BaseFinder): ) try: - from mathics.algorithm.optimizers import native_findroot_methods + from mathics.algorithm.optimizers import ( + native_findroot_methods, + native_findroot_messages, + ) methods.update(native_findroot_methods) + messages.update(native_findroot_messages) except Exception: pass try: @@ -1519,8 +1522,8 @@ class FindMinimum(_BaseFinder): = {2., {x -> 3.}} >> FindMinimum[Sin[x], {x, 1}] = {-1., {x -> -1.5708}} - >> phi[x_?NumberQ]:=NIntegrate[u,{u,0,x}]; - >> FindMinimum[phi[x]-x,{x,1.2}] + >> phi[x_?NumberQ]:=NIntegrate[u,{u,0,x}, Method->"Internal"]; + >> Quiet[FindMinimum[phi[x]-x,{x, 1.2}, Method->"Newton"]] = {-0.5, {x -> 1.00001}} >> Clear[phi]; For a not so well behaving function, the result can be less accurate: @@ -1532,9 +1535,13 @@ class FindMinimum(_BaseFinder): methods = {} summary_text = "local minimum optimization" try: - from mathics.algorithm.optimizers import native_local_optimizer_methods + from mathics.algorithm.optimizers import ( + native_local_optimizer_methods, + native_optimizer_messages, + ) methods.update(native_local_optimizer_methods) + messages.update(native_optimizer_messages) except Exception: pass try: @@ -1562,8 +1569,8 @@ class FindMaximum(_BaseFinder): = {2., {x -> 3.}} >> FindMaximum[Sin[x], {x, 1}] = {1., {x -> 1.5708}} - >> phi[x_?NumberQ]:=NIntegrate[u,{u,0,x}]; - >> FindMaximum[-phi[x]+x,{x,1.2}] + >> phi[x_?NumberQ]:=NIntegrate[u, {u, 0., x}, Method->"Internal"]; + >> Quiet[FindMaximum[-phi[x] + x, {x, 1.2}, Method->"Newton"]] = {0.5, {x -> 1.00001}} >> Clear[phi]; For a not so well behaving function, the result can be less accurate: @@ -2017,37 +2024,35 @@ class NIntegrate(Builtin):
'NIntegrate[$expr$, $interval$]'
returns a numeric approximation to the definite integral of $expr$ with limits $interval$ and with a precision of $prec$ digits. -
'NIntegrate[$expr$, $interval1$, $interval2$, ...]' -
returns a numeric approximation to the multiple integral of $expr$ with limits $interval1$, $interval2$ and with a precision of $prec$ digits. +
'NIntegrate[$expr$, $interval1$, $interval2$, ...]' +
returns a numeric approximation to the multiple integral of $expr$ with limits $interval1$, $interval2$ and with a precision of $prec$ digits. - >> NIntegrate[Exp[-x],{x,0,Infinity},Tolerance->1*^-6] + >> NIntegrate[Exp[-x],{x,0,Infinity},Tolerance->1*^-6, Method->"Internal"] = 1. - >> NIntegrate[Exp[x],{x,-Infinity, 0},Tolerance->1*^-6] + >> NIntegrate[Exp[x],{x,-Infinity, 0},Tolerance->1*^-6, Method->"Internal"] = 1. - >> NIntegrate[Exp[-x^2/2.],{x,-Infinity, Infinity},Tolerance->1*^-6] - = 2.50663 + >> NIntegrate[Exp[-x^2/2.],{x,-Infinity, Infinity},Tolerance->1*^-6, Method->"Internal"] + = 2.50664 - >> Table[1./NIntegrate[x^k,{x,0,1},Tolerance->1*^-6], {k,0,6}] - : The specified method failed to return a number. Falling back into the internal evaluator. - = {1., 2., 3., 4., 5., 6., 7.} + """ - >> NIntegrate[1 / z, {z, -1 - I, 1 - I, 1 + I, -1 + I, -1 - I}, Tolerance->1.*^-4] - : Integration over a complex domain is not implemented yet - = NIntegrate[1 / z, {z, -1 - I, 1 - I, 1 + I, -1 + I, -1 - I}, Tolerance -> 0.0001] - ## = 6.2832 I + # ## The Following tests fails if sympy is not installed. + # >> Table[1./NIntegrate[x^k,{x,0,1},Tolerance->1*^-6], {k,0,6}] + # : The specified method failed to return a number. Falling back into the internal evaluator. + # = {1., 2., 3., 4., 5., 6., 7.} - Integrate singularities with weak divergences: - >> Table[ NIntegrate[x^(1./k-1.), {x,0,1.}, Tolerance->1*^-6], {k,1,7.} ] - = {1., 2., 3., 4., 5., 6., 7.} + # >> NIntegrate[1 / z, {z, -1 - I, 1 - I, 1 + I, -1 + I, -1 - I}, Tolerance->1.*^-4] + # ## = 6.2832 I - Mutiple Integrals : - >> NIntegrate[x * y,{x, 0, 1}, {y, 0, 1}] - = 0.25 + # Integrate singularities with weak divergences: + # >> Table[ NIntegrate[x^(1./k-1.), {x,0,1.}, Tolerance->1*^-6], {k,1,7.}] + # = {1., 2., 3., 4., 5., 6., 7.} - """ + # Mutiple Integrals : + # >> NIntegrate[x * y,{x, 0, 1}, {y, 0, 1}] + # = 0.25 - requires = ["scipy"] summary_text = "numerical integration in one or several variables" messages = { "bdmtd": "The Method option should be a built-in method name.", @@ -2122,6 +2127,8 @@ def apply_with_func_domain(self, func, domain, evaluation, options): method = method.value elif isinstance(method, Symbol): method = method.get_name() + # strip context + method = method[method.rindex("`") + 1 :] else: evaluation.message("NIntegrate", "bdmtd", method) return diff --git a/test/test_nintegrate.py b/test/test_nintegrate.py index b7d560d5c..bf6d06817 100644 --- a/test/test_nintegrate.py +++ b/test/test_nintegrate.py @@ -19,7 +19,13 @@ generic_tests_for_nintegrate = [ (r"NIntegrate[x^2, {x,0,1}, {method} ]", r"1/3.", ""), - (r"NIntegrate[x^2 y^(-1.+1/3.), {x,0,1},{y,0,1}, {method}]", r"1.", ""), + (r"NIntegrate[x^2 y^2, {y,0,1}, {x,0,1}, {method} ]", r"1/9.", ""), + # FIXME: improve singularity handling in NIntegrate + # ( + # r"NIntegrate[x^2 y^(-1.+1/3.), {x,1.*^-9,1},{y, 1.*^-9,1}, {method}]", + # r"1.", + # "", + # ), ] tests_for_nintegrate = sum( @@ -35,6 +41,7 @@ else: tests_for_nintegrate = [ (r"NIntegrate[x^2, {x,0,1}]", r"1/3.", ""), + (r"NIntegrate[x^2 y^2, {y,0,1}, {x,0,1}]", r"1/9.", ""), # FIXME: this can integrate to Infinity # (r"NIntegrate[x^2 y^(-.5), {x,0,1},{y,0,1}]", r"1.", ""), ] From c01673c57019f6a93b486bea59b4aa54bd87d08e Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jun 2022 16:26:05 -0400 Subject: [PATCH 04/13] Move and go over NIntegrate pytest --- test/{ => builtin/numbers}/test_nintegrate.py | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) rename test/{ => builtin/numbers}/test_nintegrate.py (82%) diff --git a/test/test_nintegrate.py b/test/builtin/numbers/test_nintegrate.py similarity index 82% rename from test/test_nintegrate.py rename to test/builtin/numbers/test_nintegrate.py index bf6d06817..933b2c726 100644 --- a/test/test_nintegrate.py +++ b/test/builtin/numbers/test_nintegrate.py @@ -1,20 +1,18 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- +""" +NIntegrate[] tests - +This also +""" +import importlib import pytest -from .helper import evaluate - - -try: - import scipy.integrate - - usescipy = True -except: - usescipy = False +from test.helper import evaluate +# From How to check if a Python module exists without importing it: +# https://stackoverflow.com/a/14050282/546218 +scipy_integrate = importlib.find_loader("scipy.integrate") -if usescipy: +if scipy_integrate is not None: methods = ["Automatic", "Romberg", "Internal", "NQuadrature"] generic_tests_for_nintegrate = [ From 297c1f7b86525fd3f714c8c0e05c8bfa670aa777 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jun 2022 16:53:55 -0400 Subject: [PATCH 05/13] importlib.find_loader() -> imporlib.util.find_spec() --- test/builtin/numbers/test_nintegrate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/builtin/numbers/test_nintegrate.py b/test/builtin/numbers/test_nintegrate.py index 933b2c726..57110ddae 100644 --- a/test/builtin/numbers/test_nintegrate.py +++ b/test/builtin/numbers/test_nintegrate.py @@ -10,7 +10,7 @@ # From How to check if a Python module exists without importing it: # https://stackoverflow.com/a/14050282/546218 -scipy_integrate = importlib.find_loader("scipy.integrate") +scipy_integrate = importlib.util.find_spec("scipy.integrate") if scipy_integrate is not None: methods = ["Automatic", "Romberg", "Internal", "NQuadrature"] From eb4498ef86da580e3488736bd84e87cca09c93ea Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jun 2022 18:15:22 -0400 Subject: [PATCH 06/13] Revert an uneeded to_expresssion Also adjust some items->elements and try to add type annotations. A FIXME was noted because in a branch where things don't look quite right. --- mathics/builtin/arithfns/basic.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index 336a0f216..4dc0b62bd 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -319,7 +319,7 @@ class Plus(BinaryOperator, SympyFunction): def format_plus(self, items, evaluation): "Plus[items__]" - def negate(item): + def negate(item): # -> Expression (see FIXME below) if item.has_form("Times", 1, None): if isinstance(item.elements[0], Number): neg = -item.elements[0] @@ -333,11 +333,13 @@ def negate(item): else: return Expression(SymbolTimes, IntegerM1, *item.elements) elif isinstance(item, Number): + # FIXME: Isn't this is the wrong type? We should be + # returning an Expression, return -item.to_sympy() else: return Expression(SymbolTimes, IntegerM1, item) - def is_negative(value): + def is_negative(value) -> bool: if isinstance(value, Complex): real, imag = value.to_sympy().as_real_imag() if real <= 0 and imag <= 0: @@ -346,18 +348,18 @@ def is_negative(value): return True return False - items = items.get_sequence() - values = [to_expression(SymbolHoldForm, item) for item in items[:1]] + elements = items.get_sequence() + values = [to_expression(SymbolHoldForm, element) for element in elements[:1]] ops = [] - for item in items[1:]: + for element in elements[1:]: if ( - item.has_form("Times", 1, None) and is_negative(item.elements[0]) - ) or is_negative(item): - item = negate(item) + element.has_form("Times", 1, None) and is_negative(element.elements[0]) + ) or is_negative(element): + element = negate(element) op = "-" else: op = "+" - values.append(to_expression(SymbolHoldForm, item)) + values.append(Expression(SymbolHoldForm, element)) ops.append(String(op)) return Expression( SymbolInfix, From e5f9bbbd7e08599855a3dbe8e883d511607cc250 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jun 2022 19:38:03 -0400 Subject: [PATCH 07/13] 72 more conversions to go And the usual imports and class variable ordering, and docstring tagging canonicalization --- mathics/builtin/list/associations.py | 14 +- mathics/builtin/list/constructing.py | 17 +-- mathics/builtin/list/eol.py | 68 ++++++---- mathics/builtin/numbers/diffeqns.py | 25 ++-- mathics/builtin/numbers/exptrig.py | 192 +++++++++++++++------------ mathics/core/systemsymbols.py | 1 + 6 files changed, 171 insertions(+), 146 deletions(-) diff --git a/mathics/builtin/list/associations.py b/mathics/builtin/list/associations.py index 33ce8b63a..1f7beb682 100644 --- a/mathics/builtin/list/associations.py +++ b/mathics/builtin/list/associations.py @@ -12,20 +12,18 @@ Test, ) from mathics.builtin.box.inout import RowBox - from mathics.builtin.lists import list_boxes - -from mathics.core.expression import Expression from mathics.core.atoms import Integer -from mathics.core.list import ListExpression, to_mathics_list +from mathics.core.attributes import hold_all_complete, protected +from mathics.core.expression import Expression +from mathics.core.list import to_mathics_list from mathics.core.symbols import Symbol, SymbolTrue from mathics.core.systemsymbols import ( SymbolAssociation, SymbolMakeBoxes, + SymbolMissing, ) -from mathics.core.attributes import hold_all_complete, protected - class Association(Builtin): """ @@ -157,7 +155,7 @@ def find_key(exprs, rules_dictionary: dict = {}): return ( result[key] if result - else Expression("Missing", Symbol("KeyAbsent"), key) + else Expression(SymbolMissing, Symbol("KeyAbsent"), key) ) except TypeError: return None @@ -273,7 +271,7 @@ def get_keys(expr): expr.has_form("Association", None) and AssociationQ(expr).evaluate(evaluation) is SymbolTrue ): - return ListExpression(*[get_keys(element) for element in expr.elements]) + return to_mathics_list(*expr.elements, elements_conversion_fn=get_keys) else: evaluation.message("Keys", "invrl", expr) raise TypeError diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index 274f6955e..ff3cd60f4 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -10,26 +10,23 @@ from mathics.builtin.base import Builtin, Pattern - -from mathics.core.element import ElementsProperties - from mathics.builtin.lists import ( _IterationFunction, get_tuples, ) - +from mathics.core.atoms import Integer, Symbol +from mathics.core.attributes import hold_first, listable, protected from mathics.core.convert import from_sympy - +from mathics.core.element import ElementsProperties from mathics.core.expression import ( Expression, to_expression, structure, ) -from mathics.core.atoms import Integer from mathics.core.list import ListExpression from mathics.core.symbols import Atom -from mathics.core.attributes import hold_first, listable, protected +SymbolNormal = Symbol("Normal") class Array(Builtin): @@ -155,7 +152,7 @@ def apply_general(self, expr, evaluation): return return Expression( expr.get_head(), - *[Expression("Normal", element) for element in expr.elements], + *[Expression(SymbolNormal, element) for element in expr.elements], ) @@ -447,9 +444,9 @@ class Table(_IterationFunction): summary_text = "make a table of values of an expression" - def get_result(self, items): + def get_result(self, elements) -> ListExpression: return ListExpression( - *items, + *elements, elements_properties=ElementsProperties(elements_fully_evaluated=True), ) diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index dbcf4be1e..20b405d62 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -31,27 +31,35 @@ from mathics.builtin.base import MessageException from mathics.builtin.box.inout import RowBox -from mathics.core.expression import Expression from mathics.core.atoms import Integer, Integer0, Integer1 +from mathics.core.attributes import ( + hold_first, + hold_rest, + n_hold_rest, + protected, + read_protected, +) +from mathics.core.expression import Expression from mathics.core.list import ListExpression, to_mathics_list +from mathics.core.rules import Rule from mathics.core.symbols import Atom, Symbol, SymbolNull, SymbolTrue from mathics.core.systemsymbols import ( SymbolAppend, + SymbolByteArray, SymbolFailed, + SymbolInfinity, SymbolMakeBoxes, + SymbolMissing, SymbolSequence, SymbolSet, ) -from mathics.core.rules import Rule - -from mathics.core.attributes import ( - hold_first, - hold_rest, - n_hold_rest, - protected, - read_protected, -) +SymbolAppendTo = Symbol("AppendTo") +SymbolDeleteCases = Symbol("DeleteCases") +SymbolDrop = Symbol("Drop") +SymbolPrepend = Symbol("Prepend") +SymbolPrependTo = Symbol("PrependTo") +SymbolTake = Symbol("Take") class Append(Builtin): @@ -96,8 +104,8 @@ def apply(self, expr, item, evaluation): class AppendTo(Builtin): """
-
'AppendTo[$s$, $item$]' -
append $item$ to value of $s$ and sets $s$ to the result. +
'AppendTo[$s$, $elem$]' +
append $elem$ to value of $s$ and sets $s$ to the result.
>> s = {}; @@ -129,19 +137,21 @@ class AppendTo(Builtin): "rvalue": "`1` is not a variable with a value, so its value cannot be changed.", } - def apply(self, s, item, evaluation): - "AppendTo[s_, item_]" + def apply(self, s, element, evaluation): + "AppendTo[s_, element_]" resolved_s = s.evaluate(evaluation) if s == resolved_s: return evaluation.message("AppendTo", "rvalue", s) if not isinstance(resolved_s, Atom): result = Expression( - SymbolSet, s, Expression(SymbolAppend, resolved_s, item) + SymbolSet, s, Expression(SymbolAppend, resolved_s, element) ) return result.evaluate(evaluation) - return evaluation.message("AppendTo", "normal", Expression("AppendTo", s, item)) + return evaluation.message( + "AppendTo", "normal", Expression(SymbolAppendTo, s, element) + ) class Cases(Builtin): @@ -317,7 +327,7 @@ def apply_ls_n(self, items, pattern, levelspec, n, evaluation): levelspec = python_levelspec(levelspec) - if n is Symbol("Infinity"): + if n is SymbolInfinity: n = -1 elif n.get_head_name() == "System`Integer": n = n.get_int_value() @@ -325,13 +335,13 @@ def apply_ls_n(self, items, pattern, levelspec, n, evaluation): evaluation.message( "DeleteCases", "innf", - Expression("DeleteCases", items, pattern, levelspec, n), + Expression(SymbolDeleteCases, items, pattern, levelspec, n), ) else: evaluation.message( "DeleteCases", "innf", - Expression("DeleteCases", items, pattern, levelspec, n), + Expression(SymbolDeleteCases, items, pattern, levelspec, n), ) return SymbolNull @@ -401,11 +411,11 @@ class Drop(Builtin): = Drop[{1, 2, 3, 4, 5, 6}, {-5, -2, -2}] """ - summary_text = "remove a number of elements from a list" messages = { "normal": "Nonatomic expression expected at position `1` in `2`.", "drop": "Cannot drop positions `1` through `2` in `3`.", } + summary_text = "remove a number of elements from a list" def apply(self, items, seqs, evaluation): "Drop[items_, seqs___]" @@ -414,7 +424,7 @@ def apply(self, items, seqs, evaluation): if isinstance(items, Atom): return evaluation.message( - "Drop", "normal", 1, Expression("Drop", items, *seqs) + "Drop", "normal", 1, Expression(SymbolDrop, items, *seqs) ) try: @@ -633,7 +643,7 @@ def check_pattern(input_list, pat, result, beginLevel): if is_found: return to_mathics_list(*result) else: - return Expression("Missing", "NotFound") if default is None else default + return Expression(SymbolMissing, "NotFound") if default is None else default def apply_default(self, expr, pattern, default, evaluation): "FirstPosition[expr_, pattern_, default_]" @@ -957,7 +967,7 @@ def apply(self, list, i, evaluation): if idx.get_head_name() == "System`Integer": idx = idx.get_int_value() if idx == 0: - return Symbol("System`ByteArray") + return SymbolByteArray data = list.elements[0].value lendata = len(data) if idx < 0: @@ -1130,11 +1140,13 @@ def apply(self, s, item, evaluation): return evaluation.message("PrependTo", "rvalue", s) if not isinstance(resolved_s, Atom): - result = Expression("Set", s, Expression("Prepend", resolved_s, item)) + result = Expression( + SymbolSet, s, Expression(SymbolPrepend, resolved_s, item) + ) return result.evaluate(evaluation) return evaluation.message( - "PrependTo", "normal", Expression("PrependTo", s, item) + "PrependTo", "normal", Expression(SymbolPrependTo, s, item) ) @@ -1302,8 +1314,8 @@ def apply(self, items, expr, evaluation): evaluation.message("Select", "normal") return - def cond(leaf): - test = Expression(expr, leaf) + def cond(element): + test = Expression(expr, element) return test.evaluate(evaluation) is SymbolTrue return items.filter(items.head, cond, evaluation) @@ -1421,7 +1433,7 @@ def apply(self, items, seqs, evaluation): if isinstance(items, Atom): return evaluation.message( - "Take", "normal", 1, Expression("Take", items, *seqs) + "Take", "normal", 1, Expression(SymbolTake, items, *seqs) ) try: diff --git a/mathics/builtin/numbers/diffeqns.py b/mathics/builtin/numbers/diffeqns.py index 2085a92c1..806212b5c 100644 --- a/mathics/builtin/numbers/diffeqns.py +++ b/mathics/builtin/numbers/diffeqns.py @@ -6,10 +6,11 @@ import sympy from mathics.builtin.base import Builtin -from mathics.core.expression import Expression from mathics.core.convert import from_sympy +from mathics.core.expression import Expression +from mathics.core.list import ListExpression from mathics.core.symbols import Atom, Symbol -from mathics.core.systemsymbols import SymbolFunction +from mathics.core.systemsymbols import SymbolFunction, SymbolRule class DSolve(Builtin): @@ -134,7 +135,7 @@ def apply(self, eqn, y, x, evaluation): func = y except AttributeError: func = Expression(y, *syms) - function_form = Expression("List", *syms) + function_form = ListExpression(*syms) if isinstance(func, Atom): evaluation.message("DSolve", "dsfun", y) @@ -180,21 +181,18 @@ def apply(self, eqn, y, x, evaluation): sym_result = [sym_result] if function_form is None: - return Expression( - "List", + return ListExpression( *[ - Expression("List", Expression("Rule", *from_sympy(soln).leaves)) + ListExpression(Expression(SymbolRule, *from_sympy(soln).elements)) for soln in sym_result ] ) else: - return Expression( - "List", + return ListExpression( *[ - Expression( - "List", + ListExpression( Expression( - "Rule", + SymbolRule, y, Expression( SymbolFunction, @@ -214,9 +212,8 @@ def apply(self, eqn, y, x, evaluation): class C(Builtin): """
-
'C'[$n$] -
represents the $n$th constant in a solution to a - differential equation. +
'C'[$n$] +
represents the $n$th constant in a solution to a differential equation.
""" diff --git a/mathics/builtin/numbers/exptrig.py b/mathics/builtin/numbers/exptrig.py index edf4bdfe5..0cdf51455 100644 --- a/mathics/builtin/numbers/exptrig.py +++ b/mathics/builtin/numbers/exptrig.py @@ -15,20 +15,28 @@ from contextlib import contextmanager from itertools import chain +from mathics.builtin.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin -from mathics.core.expression import Expression from mathics.core.atoms import ( - Real, Integer, Integer0, + IntegerM1, + Real, from_python, ) -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import SymbolCos, SymbolSin - -from mathics.builtin.arithmetic import _MPMathFunction - from mathics.core.attributes import listable, numeric_function, protected +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.symbols import Symbol, SymbolPower +from mathics.core.systemsymbols import SymbolCos, SymbolE, SymbolSin + +SymbolArcCos = Symbol("ArcCos") +SymbolArcCosh = Symbol("ArcCosh") +SymbolArcSin = Symbol("ArcSin") +SymbolArcSinh = Symbol("ArcSinh") +SymbolArcTan = Symbol("ArcTan") +SymbolCosh = Symbol("Cosh") +SymbolSinh = Symbol("Sinh") class Fold(object): @@ -205,14 +213,14 @@ class AnglePath(Builtin): @staticmethod def _compute(x0, y0, phi0, steps, evaluation): if not steps: - return Expression("List") + return ListExpression() if steps[0].get_head_name() == "System`List": def parse(step): if step.get_head_name() != "System`List": raise _IllegalStepSpecification - arguments = step.leaves + arguments = step.elements if len(arguments) != 2: raise _IllegalStepSpecification return arguments @@ -226,12 +234,12 @@ def parse(step): try: fold = AnglePathFold(parse) - leaves = [ - Expression("List", x, y) for x, y, _ in fold.fold((x0, y0, phi0), steps) + elements = [ + ListExpression(x, y) for x, y, _ in fold.fold((x0, y0, phi0), steps) ] - return Expression("List", *leaves) + return ListExpression(*elements) except _IllegalStepSpecification: - evaluation.message("AnglePath", "steps", Expression("List", *steps)) + evaluation.message("AnglePath", "steps", ListExpression(*steps)) def apply(self, steps, evaluation): "AnglePath[{steps___}]" @@ -255,7 +263,7 @@ def apply_xy_phi0(self, x, y, phi0, steps, evaluation): def apply_xy_dx(self, x, y, dx, dy, steps, evaluation): "AnglePath[{{x_, y_}, {dx_, dy_}}, {steps___}]" - phi0 = Expression("ArcTan", dx, dy) + phi0 = Expression(SymbolArcTan, dx, dy) return AnglePath._compute(x, y, phi0, steps.get_sequence(), evaluation) @@ -482,17 +490,17 @@ class ArcCsc(_MPMathFunction): } def to_sympy(self, expr, **kwargs): - if len(expr.leaves) == 1: + if len(expr.elements) == 1: return Expression( - "ArcSin", Expression("Power", expr.leaves[0], Integer(-1)) + SymbolArcSin, Expression(SymbolPower, expr.elements[0], Integer(-1)) ).to_sympy() class ArcCsch(_MPMathFunction): """
-
'ArcCsch[$z$]' -
returns the inverse hyperbolic cosecant of $z$. +
'ArcCsch[$z$]' +
returns the inverse hyperbolic cosecant of $z$.
>> ArcCsch[0] @@ -501,8 +509,6 @@ class ArcCsch(_MPMathFunction): = 0.881374 """ - summary_text = "inverse hyperbolic cosecant function" - sympy_name = "" mpmath_name = "acsch" rules = { @@ -511,18 +517,21 @@ class ArcCsch(_MPMathFunction): "Derivative[1][ArcCsch]": "-1 / (Sqrt[1+1/#^2] * #^2) &", } + summary_text = "inverse hyperbolic cosecant function" + sympy_name = "" + def to_sympy(self, expr, **kwargs): - if len(expr.leaves) == 1: + if len(expr.elements) == 1: return Expression( - "ArcSinh", Expression("Power", expr.leaves[0], Integer(-1)) + SymbolArcSinh, Expression(SymbolPower, expr.elements[0], IntegerM1) ).to_sympy() class ArcSec(_MPMathFunction): """
-
'ArcSec[$z$]' -
returns the inverse secant of $z$. +
'ArcSec[$z$]' +
returns the inverse secant of $z$.
>> ArcSec[1] @@ -531,8 +540,6 @@ class ArcSec(_MPMathFunction): = Pi """ - summary_text = "inverse secant function" - sympy_name = "" mpmath_name = "asec" rules = { @@ -541,18 +548,21 @@ class ArcSec(_MPMathFunction): "ArcSec[1]": "0", } + summary_text = "inverse secant function" + sympy_name = "" + def to_sympy(self, expr, **kwargs): - if len(expr.leaves) == 1: + if len(expr.elements) == 1: return Expression( - "ArcCos", Expression("Power", expr.leaves[0], Integer(-1)) + SymbolArcCos, Expression(SymbolPower, expr.elements[0], IntegerM1) ).to_sympy() class ArcSech(_MPMathFunction): """
-
'ArcSech[$z$]' -
returns the inverse hyperbolic secant of $z$. +
'ArcSech[$z$]' +
returns the inverse hyperbolic secant of $z$.
>> ArcSech[0] @@ -563,8 +573,6 @@ class ArcSech(_MPMathFunction): = 1.31696 """ - summary_text = "inverse hyperbolic secant function" - sympy_name = "" mpmath_name = "asech" rules = { @@ -573,10 +581,13 @@ class ArcSech(_MPMathFunction): "Derivative[1][ArcSech]": "-1 / (# * Sqrt[(1-#)/(1+#)] (1+#)) &", } + summary_text = "inverse hyperbolic secant function" + sympy_name = "" + def to_sympy(self, expr, **kwargs): - if len(expr.leaves) == 1: + if len(expr.elements) == 1: return Expression( - "ArcCosh", Expression("Power", expr.leaves[0], Integer(-1)) + SymbolArcCosh, Expression(SymbolPower, expr.elements[0], IntegerM1) ).to_sympy() @@ -593,8 +604,6 @@ class ArcSin(_MPMathFunction): = Pi / 2 """ - summary_text = "inverse sine function" - sympy_name = "asin" mpmath_name = "asin" rules = { @@ -603,12 +612,15 @@ class ArcSin(_MPMathFunction): "ArcSin[1]": "Pi / 2", } + summary_text = "inverse sine function" + sympy_name = "asin" + class ArcSinh(_MPMathFunction): """
-
'ArcSinh[$z$]' -
returns the inverse hyperbolic sine of $z$. +
'ArcSinh[$z$]' +
returns the inverse hyperbolic sine of $z$.
>> ArcSinh[0] @@ -661,8 +673,6 @@ class ArcTan(_MPMathFunction): = -Pi / 2 """ - summary_text = "inverse tangent function" - sympy_name = "atan" mpmath_name = "atan" rules = { @@ -673,6 +683,9 @@ class ArcTan(_MPMathFunction): ArcTan[y/x], If[y >= 0, ArcTan[y/x] + Pi, ArcTan[y/x] - Pi]]]""", } + summary_text = "inverse tangent function" + sympy_name = "atan" + class ArcTanh(_MPMathFunction): """ @@ -693,8 +706,6 @@ class ArcTanh(_MPMathFunction): = ArcTanh[2 + I] """ - summary_text = "inverse hyperbolic tangent function" - sympy_name = "atanh" mpmath_name = "atanh" numpy_name = "arctanh" @@ -702,6 +713,9 @@ class ArcTanh(_MPMathFunction): "Derivative[1][ArcTanh]": "1/(1-#^2)&", } + summary_text = "inverse hyperbolic tangent function" + sympy_name = "atanh" + class Cos(_MPMathFunction): """ @@ -717,7 +731,6 @@ class Cos(_MPMathFunction): = -1.83697×10^-16 """ - summary_text = "cosine function" mpmath_name = "cos" rules = { @@ -728,31 +741,34 @@ class Cos(_MPMathFunction): "Derivative[1][Cos]": "-Sin[#]&", } + summary_text = "cosine function" + class Cosh(_MPMathFunction): """
-
'Cosh[$z$]' -
returns the hyperbolic cosine of $z$. +
'Cosh[$z$]' +
returns the hyperbolic cosine of $z$.
>> Cosh[0] = 1 """ - summary_text = "hyperbolic cosine function" mpmath_name = "cosh" rules = { "Derivative[1][Cosh]": "Sinh[#]&", } + summary_text = "hyperbolic cosine function" + class Cot(_MPMathFunction): """
-
'Cot[$z$]' -
returns the cotangent of $z$. +
'Cot[$z$]' +
returns the cotangent of $z$.
>> Cot[0] @@ -761,7 +777,6 @@ class Cot(_MPMathFunction): = 0.642093 """ - summary_text = "cotangent function" mpmath_name = "cot" rules = { @@ -769,19 +784,20 @@ class Cot(_MPMathFunction): "Cot[0]": "ComplexInfinity", } + summary_text = "cotangent function" + class Coth(_MPMathFunction): """
-
'Coth[$z$]' -
returns the hyperbolic cotangent of $z$. +
'Coth[$z$]' +
returns the hyperbolic cotangent of $z$.
>> Coth[0] = ComplexInfinity """ - summary_text = "hyperbolic cotangent function" mpmath_name = "coth" rules = { @@ -790,12 +806,14 @@ class Coth(_MPMathFunction): "Derivative[1][Coth]": "-Csch[#1]^2&", } + summary_text = "hyperbolic cotangent function" + class Csc(_MPMathFunction): """
-
'Csc[$z$]' -
returns the cosecant of $z$. +
'Csc[$z$]' +
returns the cosecant of $z$.
>> Csc[0] @@ -806,7 +824,6 @@ class Csc(_MPMathFunction): = 1.1884 """ - summary_text = "cosecant function" mpmath_name = "csc" rules = { @@ -814,10 +831,12 @@ class Csc(_MPMathFunction): "Csc[0]": "ComplexInfinity", } + summary_text = "cosecant function" + def to_sympy(self, expr, **kwargs): - if len(expr.leaves) == 1: + if len(expr.elements) == 1: return Expression( - "Power", Expression(SymbolSin, expr.leaves[0]), Integer(-1) + SymbolPower, Expression(SymbolSin, expr.elements[0]), Integer(-1) ).to_sympy() @@ -832,8 +851,6 @@ class Csch(_MPMathFunction): = ComplexInfinity """ - summary_text = "hyperbolic cosecant function" - sympy_name = "" mpmath_name = "csch" rules = { @@ -842,18 +859,21 @@ class Csch(_MPMathFunction): "Derivative[1][Csch]": "-Coth[#1] Csch[#1]&", } + summary_text = "hyperbolic cosecant function" + sympy_name = "" + def to_sympy(self, expr, **kwargs): - if len(expr.leaves) == 1: + if len(expr.elements) == 1: return Expression( - "Power", Expression("Sinh", expr.leaves[0]), Integer(-1) + SymbolPower, Expression(SymbolSinh, expr.elements[0]), IntegerM1 ).to_sympy() class Exp(_MPMathFunction): """
-
'Exp[$z$]' -
returns the exponential function of $z$. +
'Exp[$z$]' +
returns the exponential function of $z$.
>> Exp[1] @@ -870,14 +890,14 @@ class Exp(_MPMathFunction): = Overflow[] """ - summary_text = "exponential function" rules = { "Exp[x_]": "E ^ x", "Derivative[1][Exp]": "Exp", } + summary_text = "exponential function" - def from_sympy(self, sympy_name, leaves): - return Expression("Power", Symbol("E"), leaves[0]) + def from_sympy(self, sympy_name, elements): + return Expression(SymbolPower, SymbolE, elements[0]) class Haversine(_MPMathFunction): @@ -966,10 +986,10 @@ class Log(_MPMathFunction): "Log[x_?InexactNumberQ]": "Log[E, x]", } - def prepare_sympy(self, leaves): - if len(leaves) == 2: - leaves = [leaves[1], leaves[0]] - return leaves + def prepare_sympy(self, elements): + if len(elements) == 2: + elements = [elements[1], elements[0]] + return elements def get_mpmath_function(self, args): return lambda base, x: mpmath.log(x, base) @@ -1071,43 +1091,43 @@ class Sec(_MPMathFunction): } def to_sympy(self, expr, **kwargs): - if len(expr.leaves) == 1: + if len(expr.elements) == 1: return Expression( - "Power", Expression(SymbolCos, expr.leaves[0]), Integer(-1) + SymbolPower, Expression(SymbolCos, expr.elements[0]), Integer(-1) ).to_sympy() class Sech(_MPMathFunction): """
-
'Sech[$z$]' -
returns the hyperbolic secant of $z$. +
'Sech[$z$]' +
returns the hyperbolic secant of $z$.
>> Sech[0] = 1 """ - summary_text = "hyperbolic secant function" - sympy_name = "" mpmath_name = "sech" rules = { "Derivative[1][Sech]": "-Sech[#1] Tanh[#1]&", } + summary_text = "hyperbolic secant function" + sympy_name = "" def to_sympy(self, expr, **kwargs): - if len(expr.leaves) == 1: + if len(expr.elements) == 1: return Expression( - "Power", Expression("Cosh", expr.leaves[0]), Integer(-1) + SymbolPower, Expression(SymbolCosh, expr.elements[0]), IntegerM1 ).to_sympy() class Sin(_MPMathFunction): """
-
'Sin[$z$]' -
returns the sine of $z$. +
'Sin[$z$]' +
returns the sine of $z$.
>> Sin[0] @@ -1141,8 +1161,8 @@ class Sin(_MPMathFunction): class Sinh(_MPMathFunction): """
-
'Sinh[$z$]' -
returns the hyperbolic sine of $z$. +
'Sinh[$z$]' +
returns the hyperbolic sine of $z$.
>> Sinh[0] @@ -1160,8 +1180,8 @@ class Sinh(_MPMathFunction): class Tan(_MPMathFunction): """
-
'Tan[$z$]' -
returns the tangent of $z$. +
'Tan[$z$]' +
returns the tangent of $z$.
>> Tan[0] @@ -1186,8 +1206,8 @@ class Tan(_MPMathFunction): class Tanh(_MPMathFunction): """
-
'Tanh[$z$]' -
returns the hyperbolic tangent of $z$. +
'Tanh[$z$]' +
returns the hyperbolic tangent of $z$.
>> Tanh[0] diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 6f3547be7..9d68adfe3 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -79,6 +79,7 @@ SymbolMean = Symbol("Mean") SymbolMessageName = Symbol("MessageName") SymbolMinus = Symbol("Minus") +SymbolMissing = Symbol("Missing") SymbolMap = Symbol("Map") SymbolMatchQ = Symbol("MatchQ") SymbolMatrixPower = Symbol("MatrixPower") From 92c3ba39b43e192cd3b7103d14ec2758bc4c8113 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 18 Jun 2022 20:05:16 -0400 Subject: [PATCH 08/13] A few more of the less-obvious cases --- mathics/builtin/inference.py | 4 ++-- mathics/builtin/numbers/algebra.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/inference.py b/mathics/builtin/inference.py index 84eddd41c..c2b34ec89 100644 --- a/mathics/builtin/inference.py +++ b/mathics/builtin/inference.py @@ -13,7 +13,7 @@ from mathics.core.parser import parse_builtin_rule from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import SymbolAnd, SymbolNot, SymbolOr +from mathics.core.systemsymbols import SymbolAnd, SymbolEqual, SymbolNot, SymbolOr # TODO: Extend these rules? @@ -331,7 +331,7 @@ def get_assumption_rules_dispatch(evaluation): SymbolFalse, ) ) - for head in ("Equal", "Equivalent"): + for head in (SymbolEqual, Symbol("Equivalent")): assumption_rules.append( Rule( Expression(head, pat.elements[0], pat.elements[1]), diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index 4ce9758bb..6f2a8f0a5 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -1596,7 +1596,9 @@ def apply_with_assumptions(self, expr, assum, evaluation, options={}): SymbolList ) # Now, reevaluate the expression with all the assumptions. - simplify_expr = Expression(self.get_name(), expr, *options_to_rules(options)) + simplify_expr = Expression( + Symbol(self.get_name()), expr, *options_to_rules(options) + ) return dynamic_scoping( lambda ev: simplify_expr.evaluate(ev), {"System`$Assumptions": assumptions}, From f9ee4741ae95c39ff083f297ec9d89b0665e8ac6 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sat, 18 Jun 2022 15:39:44 -0300 Subject: [PATCH 09/13] the part of 362 related to common_doc rocky's review --- .github/workflows/ubuntu-minimal.yml | 34 +++++ .github/workflows/windows.yml | 2 +- CHANGES.rst | 20 ++- mathics/builtin/__init__.py | 1 + mathics/builtin/base.py | 60 +++++--- mathics/builtin/drawing/image.py | 165 +++++++++++---------- mathics/builtin/numbers/calculus.py | 2 +- mathics/builtin/scipy_utils/integrators.py | 5 +- mathics/builtin/scipy_utils/optimizers.py | 11 +- mathics/builtin/system.py | 11 +- mathics/core/util.py | 2 + mathics/doc/common_doc.py | 43 +++--- test/builtin/image/test_image.py | 59 ++++++++ test/builtin/numbers/test_nintegrate.py | 7 +- 14 files changed, 273 insertions(+), 149 deletions(-) create mode 100644 .github/workflows/ubuntu-minimal.yml create mode 100644 test/builtin/image/test_image.py diff --git a/.github/workflows/ubuntu-minimal.yml b/.github/workflows/ubuntu-minimal.yml new file mode 100644 index 000000000..9709ecd5e --- /dev/null +++ b/.github/workflows/ubuntu-minimal.yml @@ -0,0 +1,34 @@ +name: Mathics (ubuntu-minimal) + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + env: + NO_CYTHON: 1 + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: [3.9] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + sudo apt-get update -qq && sudo apt-get install -qq liblapack-dev llvm-dev + python -m pip install --upgrade pip + # Can remove after next Mathics-Scanner release + # python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full] + - name: Install Mathics with full dependencies + run: | + make develop + - name: Test Mathics + run: | + make -j3 check diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e91bc7127..7e29b95d8 100755 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -35,6 +35,6 @@ jobs: python setup.py install - name: Test Mathics run: | - pip install -e .[dev,full] + pip install -e .[dev] set PYTEST_WORKERS="-n3" make check diff --git a/CHANGES.rst b/CHANGES.rst index d59808d92..75c6db793 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,9 +13,9 @@ Enhancements * In assignment to messages associated with symbols, the attribute ``Protected`` is not having into account, following the standard in WMA. With this and the above change, Combinatorical 2.0 works as written. * ``Share[]`` performs an explicit call to the Python garbage collection and returns the amount of memory free. * Improving the compatibility of ``TeXForm`` and ``MathMLForm`` outputs with WMA. MatML tags around numbers appear as "" tags instead of "", except in the case of ``InputForm`` expressions. In TeXForm some quotes around strings have been removed to conform to WMA. It is not clear whether this is the correct behavior. -* Revise ``Nintegrate[]`` to allow scipy to be optional. - - +* Allow scipy and skimage to be optional. In particular: + - Revise ``Nintegrate[]`` use ``Method="Internal"`` when scipy isn't available +* Pyston up to versions from 2.2 to 2.3.4 are supported as are PyPy versions from 3.7-7.3.9.0 up 3.9-7.3.9. However those Python interpreters may have limitations and limitations on packages that they support. Documentation ............. @@ -47,19 +47,16 @@ Internals * ``Definition`` has a new property ``is_numeric``. * ``Symbol.is_numeric`` and ``Expression.is_numeric`` now uses the attribute ``Definition.is_numeric`` to determine the returned value. * ``NIntegrate`` internal algorithms and interfaces to ``scipy`` were moved to ``mathics.algorithm.integrators`` and ``mathics.builtin.scipy_utils.integrators`` respectively. -* To speed up attributes read, and RAM usage, attributes are now stored in a bitset instead of a tuple of strings. * Definitions for symbols ``CurrentContext`` and ``ContextPath[]`` are mirrored in the ``mathics.core.definitions.Definitions`` object for faster access. -* To speed up the lookup of symbols names, ``Definitions`` object now have two properties: ``current_context`` and ``context_path``. These properties stores the values of the corresponding symbols in the `builtin` definitions. + * ``FullForm[List[...]]`` now is shown as ``{...}`` according to the WL standard. * ``Expression.is_numeric()`` accepts an ``Evaluation`` object as a parameter; the definitions attribute of that is used. -* ``apply_N`` was introduced in module ``mathics.builtin.numeric`` to speed up the critical built-in function``N``. Its use instead of the idiom ``Expression(SymbolN, expr, prec).evaluate(evaluation)`` makes the evaluation faster. * A failure in the order in which ``mathics.core.definitions`` stores the rules was fixed. * ``any`` /``all`` calls were unrolled as loops in Cythonized modules: this avoids the overhead of a function call replacing it by a (C) for loop, which is faster. * ``BaseExpression.get_head`` now avoids building a symbol and then look for its name. It saves two function calls. * ``SameQ`` first checks type, then ``id``s, and then names in symbols. * In ``mathics.builtin.patterns.PatternTest``, if the condition is one of the most used tests (``NumberQ``, ``NumericQ``, ``StringQ``, etc) the ``match`` method is overwritten to specialized versions that avoid function calls. * in the same aim, ``mathics.core.patterns.AtomPattern`` now specializes the comparison depending of the ``Atom`` type. -* To speed up the Mathics ``Expression`` manipulation code, `Symbol`s objects are now a singleton class. This avoids a lot of unnecesary string comparisons, and calls to ``ensure_context``. * To speed up development, you can set ``NO_CYTHON`` to skip Cythonizing Python modules * A bug was fixed relating to the order in which ``mathics.core.definitions`` stores the rules * Improved support for ``Series`` Issue #46. @@ -70,6 +67,15 @@ Internals * ``N[_,_,Method->method]`` was reworked. Issue #137. * The methods ``boxes_to_*`` were moved to ``BoxExpression``. * remove ``flatten_*`` from the ``Atom`` interface. +* + +Speed improvements: +.................. + +* In ``Expression`` manipulation code, `Symbol`s objects are now a singleton class. This avoids a lot of unnecesary string comparisons, and calls to ``ensure_context``. +* Attributes are now stored in a bitset instead of a tuple of strings. +* To speed up the lookup of symbols names, ``Definitions`` object now have two properties: ``current_context`` and ``context_path``. These properties stores the values of the corresponding symbols in the ``builtin`` definitions. +* ``apply_N`` was add to speed up the then often-used built-in function ``N``. Package update diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index 6e2b0ddf9..95ac55eae 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -34,6 +34,7 @@ SympyObject, Operator, PatternObject, + check_requires_list, ) diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 4a64db8c5..756db9003 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -7,7 +7,7 @@ import importlib from itertools import chain import typing -from typing import Any, Iterable, cast +from typing import Any, Callable, Iterable, List, Optional, cast from mathics.builtin.exceptions import ( @@ -42,6 +42,21 @@ from mathics.core.attributes import protected, read_protected +def check_requires_list(requires: list) -> bool: + """ + Check if module names in ``requires`` can be imported and return True if they can or False if not. + """ + for package in requires: + lib_is_installed = True + try: + lib_is_installed = importlib.util.find_spec(package) is not None + except ImportError: + lib_is_installed = False + if not lib_is_installed: + return False + return True + + def get_option(options, name, evaluation, pop=False, evaluate=True): # we do not care whether an option X is given as System`X, # Global`X, or with any prefix from $ContextPath for that @@ -112,7 +127,7 @@ class Builtin: ``` def apply(x, evaluation): "F[x_Real]" - return Expression(Symbol("G"), x*2) + return Expression("G", x*2) ``` adds a ``BuiltinRule`` to the symbol's definition object that implements ``F[x_]->G[x*2]``. @@ -407,7 +422,6 @@ def get_functions(self, prefix="apply", is_pymodule=False): unavailable_function = self._get_unavailable_function() for name in dir(self): if name.startswith(prefix): - function = getattr(self, name) pattern = function.__doc__ if pattern is None: # Fixes PyPy bug @@ -439,25 +453,27 @@ def get_functions(self, prefix="apply", is_pymodule=False): def get_option(options, name, evaluation, pop=False): return get_option(options, name, evaluation, pop) - def _get_unavailable_function(self): - requires = getattr(self, "requires", []) - - for package in requires: - try: - importlib.import_module(package) - except ImportError: - - def apply(**kwargs): # will override apply method - kwargs["evaluation"].message( - "General", - "pyimport", # see inout.py - strip_context(self.get_name()), - package, - ) + def _get_unavailable_function(self) -> Optional[Callable]: + """ + If some of the required libraries for a symbol are not available, + returns a default function that override the ``apply_`` methods + of the class. Otherwise, returns ``None``. + """ - return apply + def apply_unavailable(**kwargs): # will override apply method + kwargs["evaluation"].message( + "General", + "pyimport", # see inout.py + strip_context(self.get_name()), + # WARNING: package isn't defined here, but mysteriously is + # defined in the place gets apply (or rather "eval"'d). + # Without it we there are a number of doctests we won't run + # even though they are should be. + package, # noqa + ) - return None + requires = getattr(self, "requires", []) + return None if check_requires_list(requires) else apply_unavailable def get_option_string(self, *params): s = self.get_option(*params) @@ -543,7 +559,7 @@ def get_operator_display(self) -> typing.Optional[str]: class Predefined(Builtin): - def get_functions(self, prefix="apply", is_pymodule=False): + def get_functions(self, prefix="apply", is_pymodule=False) -> List[Callable]: functions = list(super().get_functions(prefix)) if prefix == "apply" and hasattr(self, "evaluate"): functions.append((Symbol(self.get_name()), self.evaluate)) @@ -721,7 +737,7 @@ class BoxExpression(BuiltinElement): # considered "inert". However, it could happend that an Expression having them as an element # be evaluable, and try to apply rules. For example, # InputForm[ToBoxes[a+b]] - # should be evaluated to ``Expression(SymbolRowBox, '"a"', '"+"', '"b"')``. + # should be evaluated to ``Expression("RowBox", '"a"', '"+"', '"b"')``. # # Changes to do, after the refactor of mathics.core: # diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 3ddeadccd..e60167e4d 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__doc__ = """ +""" Image[] and image related functions Note that you (currently) need scikit-image installed in order for this module to work. @@ -51,9 +51,8 @@ SymbolMatrixQ = Symbol("MatrixQ") SymbolThreshold = Symbol("Threshold") - +# Note a list of packages that are needed for image Builtins. _image_requires = ("numpy", "PIL") - _skimage_requires = _image_requires + ("skimage", "scipy", "matplotlib", "networkx") try: @@ -73,20 +72,32 @@ from io import BytesIO +# The following classes are used to allow inclusion of +# Buultin Functions only when certain Python packages +# are available. They do this by setting the `requires` class variable. + class _ImageBuiltin(Builtin): requires = _image_requires class _ImageTest(Test): + """ + Testing Image Builtins -- those function names ending with "Q" -- that require scikit-image. + """ + requires = _image_requires class _SkimageBuiltin(_ImageBuiltin): + """ + Image Builtins that require scikit-image. + """ + requires = _skimage_requires -# import and export +# Code related to Mathics Functions that import and export. class _Exif: @@ -132,8 +143,8 @@ def extract(im, evaluation): class ImageImport(_ImageBuiltin): """
-
'ImageImport["path"]' -
import an image from the file "path". +
'ImageImport["path"]' +
import an image from the file "path".
## Image @@ -172,13 +183,13 @@ def apply(self, path, evaluation): class ImageExport(_ImageBuiltin): """
-
'ImageExport["path", $image$]' -
export $image$ as file in "path". +
'ImageExport["path", $image$]' +
export $image$ as file in "path".
""" - summary_text = "export an image to a file" messages = {"noimage": "only an Image[] can be exported into an image file"} + summary_text = "export an image to a file" def apply(self, path, expr, opts, evaluation): """ImageExport[path_String, expr_, opts___]""" @@ -237,7 +248,7 @@ def apply(self, image, args, evaluation): class ImageAdd(_ImageArithmetic): """
-
'ImageAdd[$image$, $expr_1$, $expr_2$, ...]' +
'ImageAdd[$image$, $expr_1$, $expr_2$, ...]'
adds all $expr_i$ to $image$ where each $expr_i$ must be an image or a real number.
@@ -273,7 +284,7 @@ class ImageAdd(_ImageArithmetic): class ImageSubtract(_ImageArithmetic): """
-
'ImageSubtract[$image$, $expr_1$, $expr_2$, ...]' +
'ImageSubtract[$image$, $expr_1$, $expr_2$, ...]'
subtracts all $expr_i$ from $image$ where each $expr_i$ must be an image or a real number.
@@ -299,7 +310,7 @@ class ImageSubtract(_ImageArithmetic): class ImageMultiply(_ImageArithmetic): """
-
'ImageMultiply[$image$, $expr_1$, $expr_2$, ...]' +
'ImageMultiply[$image$, $expr_1$, $expr_2$, ...]'
multiplies all $expr_i$ with $image$ where each $expr_i$ must be an image or a real number.
@@ -318,9 +329,9 @@ class ImageMultiply(_ImageArithmetic): : Expecting a number, image, or graphics instead of x. = ImageMultiply[-Image-, x] - >> ein = Import["ExampleData/Einstein.jpg"]; - >> noise = RandomImage[{0.7, 1.3}, ImageDimensions[ein]]; - >> ImageMultiply[noise, ein] + S> ein = Import["ExampleData/Einstein.jpg"]; + S> noise = RandomImage[{0.7, 1.3}, ImageDimensions[ein]]; + S> ImageMultiply[noise, ein] = -Image- """ @@ -354,20 +365,19 @@ class RandomImage(_ImageBuiltin): = -Image- """ - summary_text = "build an image with random pixels" options = {"ColorSpace": "Automatic"} + messages = { + "bddim": "The specified dimension `1` should be a pair of positive integers.", + "imgcstype": "`1` is an invalid color space specification.", + } rules = { "RandomImage[]": "RandomImage[{0, 1}, {150, 150}]", "RandomImage[max_?RealNumberQ]": "RandomImage[{0, max}, {150, 150}]", "RandomImage[{minval_?RealNumberQ, maxval_?RealNumberQ}]": "RandomImage[{minval, maxval}, {150, 150}]", "RandomImage[max_?RealNumberQ, {w_Integer, h_Integer}]": "RandomImage[{0, max}, {w, h}]", } - - messages = { - "bddim": "The specified dimension `1` should be a pair of positive integers.", - "imgcstype": "`1` is an invalid color space specification.", - } + summary_text = "build an image with random pixels" def apply(self, minval, maxval, w, h, evaluation, options): "RandomImage[{minval_?RealNumberQ, maxval_?RealNumberQ}, {w_Integer, h_Integer}, OptionsPattern[RandomImage]]" @@ -404,58 +414,46 @@ def apply(self, minval, maxval, w, h, evaluation, options): class ImageResize(_ImageBuiltin): """
-
'ImageResize[$image$, $width$]' +
'ImageResize[$image$, $width$]'
-
'ImageResize[$image$, {$width$, $height$}]' + +
'ImageResize[$image$, {$width$, $height$}]'
- >> ein = Import["ExampleData/Einstein.jpg"]; - >> ImageDimensions[ein] + S> ein = Import["ExampleData/Einstein.jpg"] + = -Image- + + S> ImageDimensions[ein] = {615, 768} - >> ImageResize[ein, {400, 600}] + S> ImageResize[ein, {400, 600}] = -Image- - #> ImageDimensions[%] + S> ImageDimensions[%] = {400, 600} - >> ImageResize[ein, 256] + S> ImageResize[ein, {256}] = -Image- - >> ImageDimensions[%] - = {256, 320} - The default sampling method is Bicubic - >> ImageResize[ein, 256, Resampling -> "Bicubic"] - = -Image- - #> ImageDimensions[%] - = {256, 320} - >> ImageResize[ein, 256, Resampling -> "Nearest"] - = -Image- - #> ImageDimensions[%] - = {256, 320} - >> ImageResize[ein, 256, Resampling -> "Gaussian"] - = -Image- - #> ImageDimensions[%] - = {256, 320} - #> ImageResize[ein, {256, 256}, Resampling -> "Gaussian"] - : Gaussian resampling needs to maintain aspect ratio. - = ImageResize[-Image-, {256, 256}, Resampling -> Gaussian] - #> ImageResize[ein, 256, Resampling -> "Invalid"] - : Invalid resampling method Invalid. - = ImageResize[-Image-, 256, Resampling -> Invalid] - - #> ImageDimensions[ImageResize[ein, {256}]] + S> ImageDimensions[%] = {256, 256} - #> ImageResize[ein, {x}] - : The size {x} is not a valid image size specification. - = ImageResize[-Image-, {x}] - #> ImageResize[ein, x] - : The size x is not a valid image size specification. - = ImageResize[-Image-, x] - """ + The Resampling option can be used to specify how to resample the image. Options are: +
    +
  • Automatic +
  • Bicubic +
  • Gaussian +
  • Nearest +
- summary_text = "resize an image" - options = {"Resampling": "Automatic"} + The default sampling method is Bicubic. + + S> ImageResize[ein, 256, Resampling -> "Bicubic"] + = -Image- + + S> ImageResize[ein, 256, Resampling -> "Gaussian"] + = ... + : ... + """ messages = { "imgrssz": "The size `1` is not a valid image size specification.", @@ -464,6 +462,9 @@ class ImageResize(_ImageBuiltin): "skimage": "Please install scikit-image to use Resampling -> Gaussian.", } + options = {"Resampling": "Automatic"} + summary_text = "resize an image" + def _get_image_size_spec(self, old_size, new_size) -> Optional[float]: predefined_sizes = { "System`Tiny": 75, @@ -879,9 +880,10 @@ class Blur(_ImageBuiltin): class Sharpen(_ImageBuiltin): """
-
'Sharpen[$image$]' +
'Sharpen[$image$]'
gives a sharpened version of $image$. -
'Sharpen[$image$, $r$]' + +
'Sharpen[$image$, $r$]'
sharpens $image$ with a kernel of size $r$.
@@ -904,7 +906,7 @@ def apply(self, image, r, evaluation): class GaussianFilter(_ImageBuiltin): """
-
'GaussianFilter[$image$, $r$]' +
'GaussianFilter[$image$, $r$]'
blurs $image$ using a Gaussian blur filter of radius $r$.
@@ -1329,7 +1331,7 @@ def apply(self, image, n: Integer, evaluation): class Threshold(_SkimageBuiltin): """
-
'Threshold[$image$]' +
'Threshold[$image$]'
gives a value suitable for binarizing $image$.
@@ -1338,11 +1340,11 @@ class Threshold(_SkimageBuiltin): >> img = Import["ExampleData/lena.tif"]; >> Threshold[img] = 0.456739 - >> Binarize[img, %] + X> Binarize[img, %] = -Image- - >> Threshold[img, Method -> "Mean"] + X> Threshold[img, Method -> "Mean"] = 0.486458 - >> Threshold[img, Method -> "Median"] + X> Threshold[img, Method -> "Median"] = 0.504726 """ @@ -1381,20 +1383,22 @@ def apply(self, image, evaluation, options): class Binarize(_SkimageBuiltin): """
-
'Binarize[$image$]' +
'Binarize[$image$]'
gives a binarized version of $image$, in which each pixel is either 0 or 1. -
'Binarize[$image$, $t$]' + +
'Binarize[$image$, $t$]'
map values $x$ > $t$ to 1, and values $x$ <= $t$ to 0. -
'Binarize[$image$, {$t1$, $t2$}]' + +
'Binarize[$image$, {$t1$, $t2$}]'
map $t1$ < $x$ < $t2$ to 1, and all other values to 0.
>> img = Import["ExampleData/lena.tif"]; - >> Binarize[img] + X> Binarize[img] = -Image- - >> Binarize[img, 0.7] + X> Binarize[img, 0.7] = -Image- - >> Binarize[img, {0.2, 0.6}] + X> Binarize[img, {0.2, 0.6}] = -Image- """ @@ -1855,7 +1859,7 @@ class ImageType(_ImageBuiltin): >> ImageType[Image[{{0, 1}, {1, 0}}]] = Real - >> ImageType[Binarize[img]] + X> ImageType[Binarize[img]] = Bit """ @@ -1870,19 +1874,20 @@ def apply(self, image, evaluation): class BinaryImageQ(_ImageTest): """
-
'BinaryImageQ[$image]' +
'BinaryImageQ[$image]'
returns True if the pixels of $image are binary bit values, and False otherwise.
- >> img = Import["ExampleData/lena.tif"]; + S> img = Import["ExampleData/lena.tif"]; S> BinaryImageQ[img] = False S> BinaryImageQ[Binarize[img]] - = True + = ... + : ... """ - summary_text = "test whether pixels in an image ar binary bit values" + summary_text = "test whether pixels in an image are binary bit values" def test(self, expr): return isinstance(expr, Image) and expr.storage_type() == "Bit" @@ -1906,7 +1911,7 @@ def _image_pixels(matrix): class ImageQ(_ImageTest): """
-
'ImageQ[Image[$pixels]]' +
'ImageQ[Image[$pixels]]'
returns True if $pixels has dimensions from which an Image can be constructed, and False otherwise.
@@ -2113,7 +2118,7 @@ def storage_type(self): return "Bit16" elif dtype == numpy.uint8: return "Byte" - elif dtype == numpy.bool: + elif dtype == bool: return "Bit" else: return str(dtype) diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index 4e56c8316..74ec58bd6 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -2033,7 +2033,7 @@ class NIntegrate(Builtin): >> NIntegrate[Exp[x],{x,-Infinity, 0},Tolerance->1*^-6, Method->"Internal"] = 1. >> NIntegrate[Exp[-x^2/2.],{x,-Infinity, Infinity},Tolerance->1*^-6, Method->"Internal"] - = 2.50664 + = 2.5066... """ diff --git a/mathics/builtin/scipy_utils/integrators.py b/mathics/builtin/scipy_utils/integrators.py index 6e373929a..f3c7a11e0 100644 --- a/mathics/builtin/scipy_utils/integrators.py +++ b/mathics/builtin/scipy_utils/integrators.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- import sys +from mathics.builtin import check_requires_list +from mathics.core.utils import IS_PYPY -IS_PYPY = "__pypy__" in sys.builtin_module_names -if IS_PYPY: +if IS_PYPY or not check_requires_list(["scipy", "numpy"]): raise ImportError import numpy as np diff --git a/mathics/builtin/scipy_utils/optimizers.py b/mathics/builtin/scipy_utils/optimizers.py index 26d5027db..93e3bd54a 100644 --- a/mathics/builtin/scipy_utils/optimizers.py +++ b/mathics/builtin/scipy_utils/optimizers.py @@ -1,20 +1,23 @@ # -*- coding: utf-8 -*- import sys +from mathics.builtin import check_requires_list + +from mathics.core.atoms import Number, Real from mathics.core.expression import Expression from mathics.core.evaluation import Evaluation -from mathics.core.atoms import Number, Real +from mathics.core.evaluators import apply_N from mathics.core.list import ListExpression from mathics.core.symbols import Symbol from mathics.core.systemsymbols import SymbolAutomatic, SymbolInfinity, SymbolFailed -from mathics.core.evaluators import apply_N +from mathics.core.utils import IS_PYPY SymbolCompile = Symbol("Compile") -IS_PYPY = "__pypy__" in sys.builtin_module_names -if IS_PYPY: +if IS_PYPY or not check_requires_list(["scipy", "numpy"]): raise ImportError + from scipy.optimize import ( minimize_scalar, # minimize, diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index a90b0ff4d..264fe8a94 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -15,6 +15,7 @@ from mathics.core.atoms import ( Integer, Integer0, + IntegerM1, Real, String, ) @@ -510,7 +511,7 @@ class SystemMemory(Predefined): name = "$SystemMemory" def evaluate(self, evaluation) -> Integer: - return Integer(-1) + return IntegerM1 class MemoryAvailable(Builtin): """ @@ -535,7 +536,7 @@ class MemoryInUse(Builtin): """
'MemoryInUse[]' -
Returns the amount of memory used by the definitions object. +
Returns the amount of memory used by the definitions object. If we can't determine this we return -1.
>> MemoryInUse[] @@ -552,7 +553,11 @@ def apply_0(self, evaluation) -> Integer: definitions = evaluation.definitions seen = set() - default_size = getsizeof(0) + try: + default_size = getsizeof(0) + except TypeError: + return IntegerM1 + handlers = { tuple: iter, list: iter, diff --git a/mathics/core/util.py b/mathics/core/util.py index b75be8a9d..37c796a17 100644 --- a/mathics/core/util.py +++ b/mathics/core/util.py @@ -10,6 +10,8 @@ import time +IS_PYPY = "__pypy__" in sys.builtin_module_names + # A small, simple timing tool MIN_ELAPSE_REPORT = int(os.environ.get("MIN_ELAPSE_REPORT", "0")) diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py index b6ad88e52..3abdfc1d9 100644 --- a/mathics/doc/common_doc.py +++ b/mathics/doc/common_doc.py @@ -37,8 +37,9 @@ from mathics import builtin from mathics import settings -from mathics.builtin import get_module_doc +from mathics.builtin import get_module_doc, check_requires_list from mathics.core.evaluation import Message, Print +from mathics.core.util import IS_PYPY from mathics.doc.utils import slugify # These regular expressions pull out information from docstring or text in a file. @@ -665,6 +666,8 @@ def get_tests(self): # iterated below. Probably some other code is faulty and # when fixed the below loop and collection into doctest_list[] # will be removed. + if not docsubsection.installed: + continue doctest_list = [] index = 1 for doctests in docsubsection.items: @@ -834,6 +837,11 @@ def __init__(self): # user manual if submodule.__doc__ is None: continue + elif IS_PYPY and submodule.__name__ == "builtins": + # PyPy seems to add this module on its own, + # but it is not something htat can be importable + continue + if submodule in modules_seen: continue @@ -848,7 +856,6 @@ def __init__(self): modules_seen.add(submodule) guide_section.subsections.append(section) - builtins = builtins_by_module[submodule.__name__] subsections = [ builtin for builtin in builtins @@ -906,13 +913,8 @@ def add_section( object to the chapter, a DocChapter object. "section_object" is either a Python module or a Class object instance. """ - installed = True - for package in getattr(section_object, "requires", []): - try: - importlib.import_module(package) - except ImportError: - installed = False - break + installed = check_requires_list(getattr(section_object, "requires", [])) + # FIXME add an additional mechanism in the module # to allow a docstring and indicate it is not to go in the # user manual @@ -949,17 +951,12 @@ def add_subsection( operator=None, in_guide=False, ): - installed = True - for package in getattr(instance, "requires", []): - try: - importlib.import_module(package) - except ImportError: - installed = False - break + installed = check_requires_list(getattr(instance, "requires", [])) # FIXME add an additional mechanism in the module # to allow a docstring and indicate it is not to go in the # user manual + if not instance.__doc__: return subsection = DocSubsection( @@ -1105,13 +1102,7 @@ def __init__(self, module=None): chapter = DocChapter(builtin_part, title, XMLDoc(text)) for name in self.symbols: instance = self.symbols[name] - installed = True - for package in getattr(instance, "requires", []): - try: - importlib.import_module(package) - except ImportError: - installed = False - break + installed = check_requires_list(getattr(instance, "requires", [])) section = DocSection( chapter, strip_system_prefix(name), @@ -1329,8 +1320,12 @@ def get_tests(self): # A guide section's subsection are Sections without the Guide. # it is *their* subsections where we generally find tests. for section in self.subsections: + if not section.installed: + continue for subsection in section.subsections: # FIXME we are omitting the section title here... + if not subsection.installed: + continue for doctests in subsection.items: yield doctests.get_tests() @@ -1661,7 +1656,7 @@ def strip_sentinal(line): self.private = testcase[0] == "#" # Ignored test cases are NOT executed, but shown as part of the docs - # Sandboxed test cases are NOT executed if environtment SANDBOX is set + # Sandboxed test cases are NOT executed if environment SANDBOX is set if testcase[0] == "X" or (testcase[0] == "S" and getenv("SANDBOX", False)): self.ignore = True # substitute '>' again so we get the correct formatting diff --git a/test/builtin/image/test_image.py b/test/builtin/image/test_image.py new file mode 100644 index 000000000..b2466f192 --- /dev/null +++ b/test/builtin/image/test_image.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +""" +Tests for mathics.core.drawing.image: + +Image[] and image related functions. +""" +import importlib +import os +import pytest + +from test.helper import evaluate +from mathics.core.symbols import SymbolNull + +# From How to check if a Python module exists without importing it: +# https://stackoverflow.com/a/14050282/546218 +skimage_module = importlib.util.find_spec("skimage") + +image_tests = [('img = Import["ExampleData/lena.tif"];', None, "")] +if skimage_module is not None: + image_tests += [ + ("BinaryImageQ[img]", "False", ""), + ("BinaryImageQ[Binarize[img]]", "True", ""), + ( + """ein = Import["ExampleData/Einstein.jpg"]; ImageDimensions[ein]""", + "{615, 768}", + "", + ), + # FIXME: I wonder if the testing framework is broken here. + # ('ImageResize[img], {400, 600}]', "-Image-", ""), + # ("ImageDimensions[%]", "{400, 600}", ""), + # ("ImageResize[ein, 256]", "-Image-", ""), + # ("ImageDimensions[%]", "{256, 320}", ""), + # ('ImageResize[ein, 256, Resampling -> "Bicubic"]', "-Image-", ""), + # ('ImageResize[ein, {256, 256}, Resampling -> "Gaussian"]', "", + # "Gaussian resampling needs to maintain aspect ratio.") + # ('ImageResize[ein, 256, Resampling -> "Invalid"]', "", "Invalid resampling method Invalid."), + # ('ImageResize[ein, 256, Resampling -> Invalid]', "", "Invalid resampling method Invalid."), + # ('ImageResize[ein, {x}]', "", "The size {x} is not a valid image size specification."), + # ('ImageResize[ein, x]', "", "The size x is not a valid image size specification."), + # ("ImageType[Binarize[img]]", "Bit", ""), + # ("Binarize[img, 0.7]", "-Image-", ""), + # ("Binarize[img, {0.2, 0.6}", "-Image-", "") + # Are there others? + ] + + +@pytest.mark.skipif( + os.getenv("SANDBOX", False), + reason="Test doesn't work in a sandboxed environment with access to local files", +) +@pytest.mark.parametrize(("str_expr, str_expected, msg"), image_tests) +def test_image(str_expr: str, str_expected: str, msg: str, message=""): + result = evaluate(str_expr) + if result is not SymbolNull or str_expected is not None: + expected = evaluate(str_expected) + if msg: + assert result == expected, msg + else: + assert result == expected diff --git a/test/builtin/numbers/test_nintegrate.py b/test/builtin/numbers/test_nintegrate.py index 57110ddae..2bf95e44e 100644 --- a/test/builtin/numbers/test_nintegrate.py +++ b/test/builtin/numbers/test_nintegrate.py @@ -2,17 +2,14 @@ """ NIntegrate[] tests -This also """ import importlib import pytest from test.helper import evaluate +from mathics.builtin import check_requires_list -# From How to check if a Python module exists without importing it: -# https://stackoverflow.com/a/14050282/546218 -scipy_integrate = importlib.util.find_spec("scipy.integrate") -if scipy_integrate is not None: +if check_requires_list(["scipy", "scipy.integrate"]): methods = ["Automatic", "Romberg", "Internal", "NQuadrature"] generic_tests_for_nintegrate = [ From 4f0c362635f0c12d99530825920aa1f62be39a1b Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 19 Jun 2022 12:51:23 -0300 Subject: [PATCH 10/13] restoring lines --- mathics/builtin/base.py | 1 + mathics/doc/common_doc.py | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 756db9003..e344d0d35 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -461,6 +461,7 @@ def _get_unavailable_function(self) -> Optional[Callable]: """ def apply_unavailable(**kwargs): # will override apply method + package = from_python(self.requires) kwargs["evaluation"].message( "General", "pyimport", # see inout.py diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py index 3abdfc1d9..0d9ee17a6 100644 --- a/mathics/doc/common_doc.py +++ b/mathics/doc/common_doc.py @@ -837,10 +837,10 @@ def __init__(self): # user manual if submodule.__doc__ is None: continue - elif IS_PYPY and submodule.__name__ == "builtins": - # PyPy seems to add this module on its own, - # but it is not something htat can be importable - continue + # elif IS_PYPY and submodule.__name__ == "builtins": + # # PyPy seems to add this module on its own, + # # but it is not something htat can be importable + # continue if submodule in modules_seen: continue @@ -856,6 +856,7 @@ def __init__(self): modules_seen.add(submodule) guide_section.subsections.append(section) + builtins = builtins_by_module[submodule.__name__] subsections = [ builtin for builtin in builtins From 29d8075776fcf49bd82159f312593d627502880a Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 19 Jun 2022 13:47:52 -0300 Subject: [PATCH 11/13] last fixes --- mathics/doc/common_doc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py index 0d9ee17a6..fac27abdf 100644 --- a/mathics/doc/common_doc.py +++ b/mathics/doc/common_doc.py @@ -837,10 +837,10 @@ def __init__(self): # user manual if submodule.__doc__ is None: continue - # elif IS_PYPY and submodule.__name__ == "builtins": - # # PyPy seems to add this module on its own, - # # but it is not something htat can be importable - # continue + elif IS_PYPY and submodule.__name__ == "builtins": + # PyPy seems to add this module on its own, + # but it is not something htat can be importable + continue if submodule in modules_seen: continue From 568d357236075fc9249b1dcf7716d4cefb11120f Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 19 Jun 2022 13:48:46 -0300 Subject: [PATCH 12/13] improve message --- mathics/builtin/inout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index 3e968e30e..cb351a51c 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -2008,7 +2008,7 @@ class General(Builtin): "syntax": "`1`", "invalidargs": "Invalid arguments.", "notboxes": "`1` is not a valid box structure.", - "pyimport": '`1`[] is not available. Python module "`2`" is not installed.', + "pyimport": '`1`[] is not available. Python modules "`2`" are required.', } summary_text = "general-purpose messages" From 58cc653c0f26ff62d6ef7b7c592bad43ba0f1a55 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 19 Jun 2022 13:54:27 -0300 Subject: [PATCH 13/13] import from_python --- mathics/builtin/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index e344d0d35..b131cc5dd 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -31,6 +31,7 @@ MachineReal, PrecisionReal, String, + from_python, ) from mathics.core.expression import Expression, SymbolDefault, to_expression from mathics.core.number import get_precision, PrecisionValueError