From 0905730e0f6fb319da88adf75e4cfa78929ad8ca Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 2 Aug 2021 03:39:43 -0400 Subject: [PATCH 001/193] Remove the advertizing hype of sum summary text It just makes thing unnecessarily wordy --- mathics/builtin/list/constructing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index 1ab56087f..de820eb08 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -70,7 +70,7 @@ class Array(Builtin): } summary_text = ( - "form an array of any dimension by applying a function to successive indices" + "form an array by applying a function to successive indices" ) def apply(self, f, dimsexpr, origins, head, evaluation): @@ -134,7 +134,7 @@ class ConstantArray(Builtin): "ConstantArray[c_, n_Integer]": "ConstantArray[c, {n}]", } - summary_text = "form a constant array of any dimension" + summary_text = "form a constant array" class Normal(Builtin): @@ -425,7 +425,7 @@ class Table(_IterationFunction): } summary_text = ( - "form a Mathematical Table of any dimension from expressions or lists" + "form a Mathematical Table from expressions or lists" ) def get_result(self, items): From 4d3e72d9e5a3b1daa6decb0f18219ff48442b9f5 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 3 Aug 2021 03:54:20 -0400 Subject: [PATCH 002/193] Graphics3D doc and format tweaks Small changes to improve documentation and layout. Also stricter signature checking on cylinder. --- mathics/builtin/drawing/graphics3d.py | 35 +++++++++++++++------------ mathics/builtin/list/constructing.py | 8 ++---- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index bb06f0d32..07dfe7aba 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -""" -Three-Dimensional Graphics +"""Three-Dimensional Graphics + +Functions for working with 3D graphics. """ from mathics.version import __version__ # noqa used in loading to check consistency. @@ -71,7 +72,7 @@ class Graphics3D(Graphics):
'Graphics3D[$primitives$, $options$]'
represents a three-dimensional graphic. - See also the Section "Plotting" for a list of Plot options. +
See also the Section "Plotting" for a list of Plot options. >> Graphics3D[Polygon[{{0,0,0}, {0,1,1}, {1,0,0}}]] @@ -134,12 +135,14 @@ class Graphics3D(Graphics): box_suffix = "3DBox" + messages = {"invlight": "`1` is not a valid list of light sources."} + rules = { "MakeBoxes[Graphics3D[content_, OptionsPattern[Graphics3D]], " " OutputForm]": '"-Graphics3D-"' } - messages = {"invlight": "`1` is not a valid list of light sources."} + summary_text = "a three-dimensional graphics image wrapper" def total_extent_3d(extents): @@ -340,12 +343,14 @@ def apply_min(self, xmin, ymin, zmin, evaluation): class Cylinder(Builtin): """
-
'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}]' -
represents a cylinder of radius 1. -
'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}, $r$]' -
is a cylinder of radius $r$ starting at ($x1$, $y1$, $z1$) and ending at ($x2$, $y2$, $z2$). -
'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}, ... }, $r$]' -
is a collection cylinders of radius $r$ +
'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}]' +
represents a cylinder of radius 1. + +
'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}, $r$]' +
is a cylinder of radius $r$ starting at ($x1$, $y1$, $z1$) and ending at ($x2$, $y2$, $z2$). + +
'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}, ... }, $r$]' +
is a collection cylinders of radius $r$.
>> Graphics3D[Cylinder[{{0, 0, 0}, {1, 1, 1}}, 1]] @@ -355,18 +360,18 @@ class Cylinder(Builtin): = -Graphics3D- """ + messages = {"oddn": "The number of points must be even."} + rules = { "Cylinder[]": "Cylinder[{{0, 0, 0}, {1, 1, 1}}, 1]", - "Cylinder[positions_]": "Cylinder[positions, 1]", + "Cylinder[positions_List]": "Cylinder[positions, 1]", } - messages = {"oddn": "The number of points must be even."} - def apply_check(self, positions, radius, evaluation): - "Cylinder[positions_, radius_?NumericQ]" + "Cylinder[positions_List, radius_?NumericQ]" if len(positions.get_leaves()) % 2 == 1: - # number of points is odd so abort + # The number of points is odd, so abort. evaluation.error("Cylinder", "oddn", positions) return Expression("Cylinder", positions, radius) diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index de820eb08..a071bf43b 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -69,9 +69,7 @@ class Array(Builtin): "plen": "`1` and `2` should have the same length.", } - summary_text = ( - "form an array by applying a function to successive indices" - ) + summary_text = "form an array by applying a function to successive indices" def apply(self, f, dimsexpr, origins, head, evaluation): "Array[f_, dimsexpr_, origins_:1, head_:List]" @@ -424,9 +422,7 @@ class Table(_IterationFunction): "Table[expr_, n_Integer]": "Table[expr, {n}]", } - summary_text = ( - "form a Mathematical Table from expressions or lists" - ) + summary_text = "form a Mathematical Table from expressions or lists" def get_result(self, items): return Expression(SymbolList, *items) From a83fd9305189fb9d519befe9afa3b6c925816ff5 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 2 Aug 2021 08:20:42 -0300 Subject: [PATCH 003/193] Remove duplicate code from cylinders --- mathics/builtin/box/graphics3d.py | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index c07107f73..2cc6e558d 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -364,15 +364,9 @@ def boxes_to_json(self, leaves=None, **options): # Handle other graphics formats. format_fn = lookup_method(elements, "json") - json_repr = format_fn(elements, **options) - - # TODO: Cubeoid (like this) - # json_repr = [{'faceColor': (1, 1, 1, 1), 'position': [(0,0,0), None], - # 'size':[(1,1,1), None], 'type': 'cube'}] - json_repr = json.dumps( { - "elements": json_repr, + "elements": format_fn(elements, **options), "axes": { "hasaxes": axes, "ticks": ticks, @@ -743,18 +737,6 @@ def init(self, graphics, style, item): self.points = [Coords3D(graphics, pos=point) for point in points] self.radius = item.leaves[1].to_python() - def to_asy(self): - if self.face_color is None: - face_color = (1, 1, 1) - else: - face_color = self.face_color.to_js() - - rgb = f"rgb({face_color[0]}, {face_color[1]}, {face_color[2]})" - return "".join( - f"draw(surface(cylinder({tuple(coord.pos()[0])}, {self.radius}, {self.height})), {rgb});" - for coord in self.points - ) - def extent(self): result = [] # FIXME: instead of `coords.add(±self.radius, ±self.radius, ±self.radius)` we should do: From f368f48e878afd016a108a0276496c0a57c6b3b8 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 2 Aug 2021 12:28:12 -0300 Subject: [PATCH 004/193] Remove cuboids' dependency on polygon --- mathics/builtin/box/graphics3d.py | 31 ++++++ mathics/builtin/drawing/graphics3d.py | 138 ++++---------------------- mathics/builtin/graphics.py | 1 + mathics/format/asy.py | 52 ++++++++++ mathics/format/json.py | 23 ++++- 5 files changed, 127 insertions(+), 118 deletions(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index 2cc6e558d..d8fd12970 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -714,6 +714,36 @@ def _apply_boxscaling(self, boxscale): coords.scale(boxscale) +class Cuboid3DBox(_Graphics3DElement): + """ + Internal Python class used when Boxing a 'Cuboid' object. + """ + + def init(self, graphics, style, item): + super(Cuboid3DBox, self).init(graphics, item, style) + + self.edge_color, self.face_color = style.get_style(_Color, face_element=True) + + if len(item.leaves) != 1: + raise BoxConstructError + + points = item.leaves[0] + if not all( + len(point) == 3 and all(isinstance(p, numbers.Real) for p in point) + for point in points + ): + raise BoxConstructError + + self.points = [Coords3D(graphics, pos=point) for point in points] + + def extent(self): + return [coords.pos()[0] for coords in self.points] + + def _apply_boxscaling(self, boxscale): + # TODO + pass + + class Cylinder3DBox(_Graphics3DElement): """ Internal Python class used when Boxing a 'Cylinder' object. @@ -872,6 +902,7 @@ def _apply_boxscaling(self, boxscale): GLOBALS3D.update( { "System`Arrow3DBox": Arrow3DBox, + "System`Cuboid3DBox": Cuboid3DBox, "System`Cylinder3DBox": Cylinder3DBox, "System`Line3DBox": Line3DBox, "System`Point3DBox": Point3DBox, diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 07dfe7aba..3f917aaa8 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -7,11 +7,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.core.expression import ( - Expression, - from_python, - SymbolList, -) +from mathics.core.expression import Expression from mathics.builtin.base import BoxConstructError, Builtin, InstanceableBuiltin from mathics.builtin.colors.color_directives import RGBColor @@ -218,126 +214,34 @@ class Cuboid(Builtin):
'Cuboid[{$xmin$, $ymin$, $zmin$}]'
is a unit cube. -
'Cuboid[{$xmin$, $ymin$, $zmin$}, {$xmax$, $ymax$, $zmax$}]' -
represents a cuboid extending from {$xmin$, $ymin$, $zmin$} to {$xmax$, $ymax$, $zmax$}. +
'Cuboid[{{$xmin$, $ymin$, $zmin$}, {$xmax$, $ymax$, $zmax$}}]' +
is a cuboid with two opposite corners at ($xmin$, $ymin$, $zmin$) and ($xmax$, $ymax$, $zmax$). +
'Cuboid[{{$x1min$, $y1min$, $z1min$}, {$x1max$, $y1max$, $z1max$}, ...}]' +
is a collection of cuboids.
>> Graphics3D[Cuboid[{0, 0, 1}]] = -Graphics3D- - >> Graphics3D[{Red, Cuboid[{0, 0, 0}, {1, 1, 0.5}], Blue, Cuboid[{0.25, 0.25, 0.5}, {0.75, 0.75, 1}]}] + >> Graphics3D[{Red, Cuboid[{{0, 0, 0}, {1, 1, 0.5}}], Blue, Cuboid[{{0.25, 0.25, 0.5}, {0.75, 0.75, 1}}]}] = -Graphics3D- """ - rules = {"Cuboid[]": "Cuboid[{0,0,0}]"} - - def apply_full(self, xmin, ymin, zmin, xmax, ymax, zmax, evaluation): - "Cuboid[{xmin_, ymin_, zmin_}, {xmax_, ymax_, zmax_}]" - - xmin, ymin, zmin = [ - value.round_to_float(evaluation) for value in (xmin, ymin, zmin) - ] - xmax, ymax, zmax = [ - value.round_to_float(evaluation) for value in (xmax, ymax, zmax) - ] - if None in (xmin, ymin, zmin, xmax, ymax, zmax): - return # TODO - - if (xmax <= xmin) or (ymax <= ymin) or (zmax <= zmin): - return # TODO - - polygons = [ - # X - Expression( - "List", - Expression(SymbolList, xmin, ymin, zmin), - Expression(SymbolList, xmin, ymax, zmin), - Expression(SymbolList, xmin, ymax, zmax), - ), - Expression( - "List", - Expression(SymbolList, xmin, ymin, zmin), - Expression(SymbolList, xmin, ymin, zmax), - Expression(SymbolList, xmin, ymax, zmax), - ), - Expression( - "List", - Expression(SymbolList, xmax, ymin, zmin), - Expression(SymbolList, xmax, ymax, zmin), - Expression(SymbolList, xmax, ymax, zmax), - ), - Expression( - "List", - Expression(SymbolList, xmax, ymin, zmin), - Expression(SymbolList, xmax, ymin, zmax), - Expression(SymbolList, xmax, ymax, zmax), - ), - # Y - Expression( - "List", - Expression(SymbolList, xmin, ymin, zmin), - Expression(SymbolList, xmax, ymin, zmin), - Expression(SymbolList, xmax, ymin, zmax), - ), - Expression( - "List", - Expression(SymbolList, xmin, ymin, zmin), - Expression(SymbolList, xmin, ymin, zmax), - Expression(SymbolList, xmax, ymin, zmax), - ), - Expression( - "List", - Expression(SymbolList, xmin, ymax, zmin), - Expression(SymbolList, xmax, ymax, zmin), - Expression(SymbolList, xmax, ymax, zmax), - ), - Expression( - "List", - Expression(SymbolList, xmin, ymax, zmin), - Expression(SymbolList, xmin, ymax, zmax), - Expression(SymbolList, xmax, ymax, zmax), - ), - # Z - Expression( - "List", - Expression(SymbolList, xmin, ymin, zmin), - Expression(SymbolList, xmin, ymax, zmin), - Expression(SymbolList, xmax, ymax, zmin), - ), - Expression( - "List", - Expression(SymbolList, xmin, ymin, zmin), - Expression(SymbolList, xmax, ymin, zmin), - Expression(SymbolList, xmax, ymax, zmin), - ), - Expression( - "List", - Expression(SymbolList, xmin, ymin, zmax), - Expression(SymbolList, xmin, ymax, zmax), - Expression(SymbolList, xmax, ymax, zmax), - ), - Expression( - "List", - Expression(SymbolList, xmin, ymin, zmax), - Expression(SymbolList, xmax, ymin, zmax), - Expression(SymbolList, xmax, ymax, zmax), - ), - ] - - return Expression("Polygon", Expression(SymbolList, *polygons)) - - def apply_min(self, xmin, ymin, zmin, evaluation): - "Cuboid[{xmin_, ymin_, zmin_}]" - xmin, ymin, zmin = [ - value.round_to_float(evaluation) for value in (xmin, ymin, zmin) - ] - if None in (xmin, ymin, zmin): - return # TODO - - (xmax, ymax, zmax) = (from_python(value + 1) for value in (xmin, ymin, zmin)) - (xmin, ymin, zmin) = (from_python(value) for value in (xmin, ymin, zmin)) - - return self.apply_full(xmin, ymin, zmin, xmax, ymax, zmax, evaluation) + rules = { + "Cuboid[]": "Cuboid[{{0, 0, 0}, {1, 1, 1}}]", + "Cuboid[{xmin, ymin, zmin}]": "Cuboid[{{xmin, ymin, zmin}, {xmin + 1, ymin + 1, zmin + 1}}]", + } + + messages = {"oddn": "The number of points must be even."} + + def apply_check(self, positions, evaluation): + "Cuboid[positions_]" + + if len(positions.get_leaves()) % 2 == 1: + # number of points is odd so abort + evaluation.error("Cuboid", "oddn", positions) + + return Expression("Cuboid", positions) class Cylinder(Builtin): diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 26c8cad0b..d0d51d192 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -1375,6 +1375,7 @@ class Large(Builtin): "Arrow", "BezierCurve", "Circle", + "Cuboid", "Cylinder", "Disk", "FilledCurve", diff --git a/mathics/format/asy.py b/mathics/format/asy.py index e05dd0617..46d772637 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -21,6 +21,7 @@ Graphics3DElements, Arrow3DBox, Coords3D, + Cuboid3DBox, Cylinder3DBox, Line3DBox, Point3DBox, @@ -200,6 +201,57 @@ def bezier_curve_box(self, **options) -> str: add_conversion_fn(BezierCurveBox, bezier_curve_box) +def cuboid3dbox(self, **options) -> str: + if self.face_color is None: + face_color = (1, 1, 1) + else: + face_color = self.face_color.to_js() + + asy = "// Cuboid3DBox\n" + + pen = asy_create_pens( + edge_color=self.edge_color, + face_color=face_color, + stroke_width=l, + is_face_element=True, + ) + + i = 0 + while i < len(self.points) / 2: + try: + point1_obj = self.points[i * 2] + if isinstance(point1_obj, Coords3D): + point1 = point1_obj.pos()[0] + else: + point1 = point1_obj[0] + point2_obj = self.points[i * 2 + 1] + if isinstance(point2_obj, Coords3D): + point2 = point2_obj.pos()[0] + else: + point2 = point2_obj[0] + + rgb = "rgb({0},{1},{1})".format(*face_color[:3]) + + asy += f""" + draw(unitcube * scale( + {point2[0] - point1[0]}, + {point2[1] - point1[1]}, + {point2[2] - point1[2]} + ), {rgb}); + """ + + except: # noqa + pass + + i += 1 + + # print(asy) + return asy + + +add_conversion_fn(Cuboid3DBox) + + def cylinder3dbox(self, **options) -> str: if self.face_color is None: face_color = (1, 1, 1) diff --git a/mathics/format/json.py b/mathics/format/json.py index c5ba4b399..4649eb3af 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -10,8 +10,9 @@ ) from mathics.builtin.box.graphics3d import ( - Cylinder3DBox, Arrow3DBox, + Cuboid3DBox, + Cylinder3DBox, Line3DBox, Point3DBox, Polygon3DBox, @@ -82,6 +83,26 @@ def arrow_3d_box(self): add_conversion_fn(Arrow3DBox, arrow_3d_box) +def cuboid_3d_box(self): + """ + Compact (lower-level) JSON formatting of a Cuboid3DBox. + """ + face_color = self.face_color + if face_color is not None: + face_color = face_color.to_js() + data = convert_coord_collection( + [self.points], + "cuboid", + face_color, + {"color": face_color}, + ) + # print("### json Cuboid3DBox", data) + return data + + +add_conversion_fn(Cuboid3DBox, cuboid_3d_box) + + def cylinder_3d_box(self): """ Compact (lower-level) JSON formatting of a Cylinder3DBox. From b44241a610846920115a0b5ff0edb17bdb0b5bd3 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 3 Aug 2021 03:48:50 -0400 Subject: [PATCH 005/193] Revise how Cuboid is implemented --- mathics/builtin/drawing/graphics3d.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 3f917aaa8..13393d3c2 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -211,13 +211,19 @@ class Sphere(Builtin): class Cuboid(Builtin): """ + Cuboid also known as interval, rectangle, square, cube, rectangular parallelepiped, tesseract, orthotope, and box.
-
'Cuboid[{$xmin$, $ymin$, $zmin$}]' -
is a unit cube. -
'Cuboid[{{$xmin$, $ymin$, $zmin$}, {$xmax$, $ymax$, $zmax$}}]' -
is a cuboid with two opposite corners at ($xmin$, $ymin$, $zmin$) and ($xmax$, $ymax$, $zmax$). -
'Cuboid[{{$x1min$, $y1min$, $z1min$}, {$x1max$, $y1max$, $z1max$}, ...}]' -
is a collection of cuboids. +
'Cuboid[{$p_min$}]' +
is a unit cube with its lower corner at point $p_min$. + +
'Cuboid[$p_min$, $p_max$]' +
is a cuboid with lower corner $p_min$ and upper corner $p_max$. + +
'Cuboid[$p1_min$, $p1_max$, ...}]' +
is a collection of cuboids. + +
'Cuboid[]' is equivalent to 'Cuboid[{0,0,0}]'. +
>> Graphics3D[Cuboid[{0, 0, 1}]] @@ -227,18 +233,19 @@ class Cuboid(Builtin): = -Graphics3D- """ + messages = {"oddn": "The number of points must be even."} + rules = { "Cuboid[]": "Cuboid[{{0, 0, 0}, {1, 1, 1}}]", - "Cuboid[{xmin, ymin, zmin}]": "Cuboid[{{xmin, ymin, zmin}, {xmin + 1, ymin + 1, zmin + 1}}]", } - messages = {"oddn": "The number of points must be even."} + summary_text = "unit cube" def apply_check(self, positions, evaluation): - "Cuboid[positions_]" + "Cuboid[positions_List]" if len(positions.get_leaves()) % 2 == 1: - # number of points is odd so abort + # The number of points is odd, so abort. evaluation.error("Cuboid", "oddn", positions) return Expression("Cuboid", positions) From 603e06c27c7ae947a23ca01758ab2c82d037809e Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 3 Aug 2021 08:52:57 -0400 Subject: [PATCH 006/193] Remove DateString items from DateList doc --- mathics/builtin/datentime.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index 37c2b3270..229eca8fc 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -446,16 +446,14 @@ def to_datelist(self, epochtime, evaluation): class DateList(_DateFormat): """
-
'DateList[]' +
'DateList[]'
returns the current local time in the form {$year$, $month$, $day$, $hour$, $minute$, $second$}. -
'DateList[$time$]' + +
'DateList[$time$]'
returns a formatted date for the number of seconds $time$ since epoch Jan 1 1900. -
'DateList[{$y$, $m$, $d$, $h$, $m$, $s$}]' + +
'DateList[{$y$, $m$, $d$, $h$, $m$, $s$}]'
converts an incomplete date list to the standard representation. -
'DateString[$string$]' -
returns the formatted date list of a date string specification. -
'DateString[$string$, {$e1$, $e2$, ...}]' -
returns the formatted date list of a $string$ obtained from elements $ei$.
>> DateList[0] @@ -513,19 +511,25 @@ def apply(self, epochtime, evaluation): class DateString(_DateFormat): """
-
'DateString[]' +
'DateString[]'
returns the current local time and date as a string. -
'DateString[$elem$]' + +
'DateString[$elem$]'
returns the time formatted according to $elems$. -
'DateString[{$e1$, $e2$, ...}]' + +
'DateString[{$e1$, $e2$, ...}]'
concatinates the time formatted according to elements $ei$. -
'DateString[$time$]' + +
'DateString[$time$]'
returns the date string of an AbsoluteTime. -
'DateString[{$y$, $m$, $d$, $h$, $m$, $s$}]' + +
'DateString[{$y$, $m$, $d$, $h$, $m$, $s$}]'
returns the date string of a date list specification. -
'DateString[$string$]' + +
'DateString[$string$]'
returns the formatted date string of a date string specification. -
'DateString[$spec$, $elems$]' + +
'DateString[$spec$, $elems$]'
formats the time in turns of $elems$. Both $spec$ and $elems$ can take any of the above formats.
From a899fbea87712bff1135e2db25ba3ecdcae10fae Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 3 Aug 2021 10:29:03 -0300 Subject: [PATCH 007/193] Fix bug in `Cuboid3DBox` --- mathics/builtin/box/graphics3d.py | 2 +- mathics/builtin/drawing/graphics3d.py | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index d8fd12970..fe48c562c 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -727,7 +727,7 @@ def init(self, graphics, style, item): if len(item.leaves) != 1: raise BoxConstructError - points = item.leaves[0] + points = item.leaves[0].to_python() if not all( len(point) == 3 and all(isinstance(p, numbers.Real) for p in point) for point in points diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 13393d3c2..3f3f90a58 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -7,7 +7,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.core.expression import Expression +from mathics.core.expression import Expression, Real from mathics.builtin.base import BoxConstructError, Builtin, InstanceableBuiltin from mathics.builtin.colors.color_directives import RGBColor @@ -17,6 +17,7 @@ Graphics, Style, ) +from mathics.builtin.lists import List def coords3D(value): @@ -213,17 +214,16 @@ class Cuboid(Builtin): """ Cuboid also known as interval, rectangle, square, cube, rectangular parallelepiped, tesseract, orthotope, and box.
-
'Cuboid[{$p_min$}]' +
'Cuboid[$p_min$]'
is a unit cube with its lower corner at point $p_min$. -
'Cuboid[$p_min$, $p_max$]' +
'Cuboid[{$p_min$, $p_max$}]'
is a cuboid with lower corner $p_min$ and upper corner $p_max$. -
'Cuboid[$p1_min$, $p1_max$, ...}]' +
'Cuboid[{$p1_min$, $p1_max$, ...}]'
is a collection of cuboids.
'Cuboid[]' is equivalent to 'Cuboid[{0,0,0}]'. -
>> Graphics3D[Cuboid[{0, 0, 1}]] @@ -241,6 +241,21 @@ class Cuboid(Builtin): summary_text = "unit cube" + def apply_unit_cube(self, xmin, ymin, zmin, evaluation): + "Cuboid[{xmin_, ymin_, zmin_}]" + + return Expression( + "Cuboid", + List( + List(xmin, ymin, zmin), + List( + Real(xmin.to_python() + 1), + Real(ymin.to_python() + 1), + Real(zmin.to_python() + 1), + ), + ), + ) + def apply_check(self, positions, evaluation): "Cuboid[positions_List]" From a20414ee8a3ae72a1607c27c7c9624c19de838fb Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 3 Aug 2021 11:28:30 -0300 Subject: [PATCH 008/193] Fix bug in `Cuboid`'s `TeXForm` --- mathics/format/asy.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/mathics/format/asy.py b/mathics/format/asy.py index 46d772637..4f0c1b31d 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -207,14 +207,9 @@ def cuboid3dbox(self, **options) -> str: else: face_color = self.face_color.to_js() - asy = "// Cuboid3DBox\n" + rgb = "rgb({0},{1},{1})".format(*face_color[:3]) - pen = asy_create_pens( - edge_color=self.edge_color, - face_color=face_color, - stroke_width=l, - is_face_element=True, - ) + asy = "// Cuboid3DBox\n" i = 0 while i < len(self.points) / 2: @@ -230,8 +225,6 @@ def cuboid3dbox(self, **options) -> str: else: point2 = point2_obj[0] - rgb = "rgb({0},{1},{1})".format(*face_color[:3]) - asy += f""" draw(unitcube * scale( {point2[0] - point1[0]}, @@ -258,6 +251,10 @@ def cylinder3dbox(self, **options) -> str: else: face_color = self.face_color.to_js() + # FIXME: currently always drawing around the axis X+Y + axes_point = (1, 1, 0) + rgb = "rgb({0},{1},{1})".format(*face_color[:3]) + asy = "// Cylinder3DBox\n" i = 0 while i < len(self.points) / 2: @@ -280,9 +277,6 @@ def cylinder3dbox(self, **options) -> str: + (point1[2] - point2[2]) ** 2 ) ** 0.5 - # FIXME: currently always drawing around the axis X+Y - axes_point = (1, 1, 0) - rgb = "rgb({0},{1},{1})".format(*face_color[:3]) asy += ( f"draw(surface(cylinder({tuple(point1)}, {self.radius}, {distance}, {axes_point})), {rgb});" + "\n" From c013d73bd5bda1f5c6f0efbef1a6744e8f8af641 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 3 Aug 2021 20:34:37 -0400 Subject: [PATCH 009/193] Aymptote: scale needs to come before figure Cuboid 2nd example. Possible workaround to ensure the 2nd test gets into the doc. --- mathics/builtin/drawing/graphics3d.py | 2 ++ mathics/format/asy.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 3f3f90a58..c0711da83 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -231,6 +231,8 @@ class Cuboid(Builtin): >> Graphics3D[{Red, Cuboid[{{0, 0, 0}, {1, 1, 0.5}}], Blue, Cuboid[{{0.25, 0.25, 0.5}, {0.75, 0.75, 1}}]}] = -Graphics3D- + + ## """ messages = {"oddn": "The number of points must be even."} diff --git a/mathics/format/asy.py b/mathics/format/asy.py index 4f0c1b31d..990f3f4f6 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -226,11 +226,11 @@ def cuboid3dbox(self, **options) -> str: point2 = point2_obj[0] asy += f""" - draw(unitcube * scale( + draw(scale( {point2[0] - point1[0]}, {point2[1] - point1[1]}, {point2[2] - point1[2]} - ), {rgb}); + ) * unitcube, {rgb}); """ except: # noqa From 47693ebba16e1bbccc6a2a3bceb3dd894f78a636 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 3 Aug 2021 23:09:55 -0300 Subject: [PATCH 010/193] Fix position of cuboids in Asymptote --- mathics/format/asy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/format/asy.py b/mathics/format/asy.py index 990f3f4f6..e4d5cc045 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -226,7 +226,7 @@ def cuboid3dbox(self, **options) -> str: point2 = point2_obj[0] asy += f""" - draw(scale( + draw(shift({point1[0]}, {point1[1]}, {point1[2]}) * scale( {point2[0] - point1[0]}, {point2[1] - point1[1]}, {point2[2] - point1[2]} From 698f59dcc9035cdae08835d198e259ab8c3877ca Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 3 Aug 2021 22:02:00 -0400 Subject: [PATCH 011/193] Add Mathics Core version to PDF title --- mathics/doc/tex/.gitignore | 3 ++- mathics/doc/tex/Makefile | 2 +- mathics/doc/tex/doc2latex.py | 18 ++++++++++++++---- mathics/doc/tex/mathics.tex | 5 ++++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/mathics/doc/tex/.gitignore b/mathics/doc/tex/.gitignore index cdc41b8ff..83dfc86b2 100644 --- a/mathics/doc/tex/.gitignore +++ b/mathics/doc/tex/.gitignore @@ -1,4 +1,4 @@ -/testing.tex +/core-version.tex /doc_tex_data.pcl /documentation.tex /images/ @@ -39,3 +39,4 @@ /mathics.pre /mathics.toc /testing.aux +/testing.tex diff --git a/mathics/doc/tex/Makefile b/mathics/doc/tex/Makefile index ee231b491..d54a39488 100644 --- a/mathics/doc/tex/Makefile +++ b/mathics/doc/tex/Makefile @@ -43,4 +43,4 @@ clean: rm -f mathics_*.* || true rm -f mathics-*.* documentation.tex $(DOC_TEX_DATA_PCL) || true rm -f mathics.pdf mathics.dvi test-mathics.pdf test-mathics.dvi || true - rm -f mathics-test.pdf mathics-test.dvi || true + rm -f mathics-test.pdf mathics-test.dvi core-version.tex || true diff --git a/mathics/doc/tex/doc2latex.py b/mathics/doc/tex/doc2latex.py index 8760146b5..828b565e4 100755 --- a/mathics/doc/tex/doc2latex.py +++ b/mathics/doc/tex/doc2latex.py @@ -5,13 +5,14 @@ """ import os +import os.path as osp import pickle from argparse import ArgumentParser import mathics -from mathics import version_string +from mathics import version_string, __version__ from mathics import settings from mathics.doc.common_doc import MathicsMainDocumentation @@ -45,8 +46,8 @@ def open_ensure_dir(f, *args, **kwargs): try: return open(f, *args, **kwargs) except (IOError, OSError): - d = os.path.dirname(f) - if d and not os.path.exists(d): + d = osp.dirname(f) + if d and not osp.exists(d): os.makedirs(d) return open(f, *args, **kwargs) @@ -63,11 +64,20 @@ def print_and_log(*args): def write_latex(doc_data, quiet=False): documentation = MathicsMainDocumentation() if not quiet: - print(f"Writing LaTeX {DOC_LATEX_FILE}") + print(f"Writing LaTeX document to {DOC_LATEX_FILE}") with open_ensure_dir(DOC_LATEX_FILE, "wb") as doc: content = documentation.latex(doc_data, quiet=quiet) content = content.encode("utf-8") doc.write(content) + DOC_VERSION_FILE = osp.join(osp.dirname(DOC_LATEX_FILE), "core-version.tex") + if not quiet: + print(f"Writing Mathics Core Version Information to {DOC_VERSION_FILE}") + with open(DOC_VERSION_FILE, "w") as doc: + doc.write( + r"""%% Mathics core version number created via doc2latex.py +\newcommand{\version}{%s}""" + % __version__ + ) def main(): diff --git a/mathics/doc/tex/mathics.tex b/mathics/doc/tex/mathics.tex index 00023f799..7db35592a 100644 --- a/mathics/doc/tex/mathics.tex +++ b/mathics/doc/tex/mathics.tex @@ -68,7 +68,9 @@ \includegraphics[height=0.1\linewidth]{logo-heptatom.pdf} \includegraphics[height=0.08125\linewidth]{logo-text-nodrop.pdf} \\[.5em] -{\LARGE\color{subtitle}\textit{\textmd{A free, open-source alternative to Mathematica}}}} + {\LARGE\color{subtitle}\textit{\textmd{A free, open-source alternative to Mathematica}}} + \par\textmd{Mathics Core Version \version} +} \author{The Mathics Team} @@ -245,6 +247,7 @@ \hyphenation{assign-ments} \begin{document} +\input{core-version.tex} \maketitle From 59ff4b3e38f42c88cca17fd7e56f30a31ff0745f Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 4 Aug 2021 06:46:29 -0400 Subject: [PATCH 012/193] Add colophon listing versions used to build doc --- mathics/doc/tex/.gitignore | 1 + mathics/doc/tex/Makefile | 8 ++++++-- mathics/doc/tex/doc2latex.py | 35 +++++++++++++++++++++++++++++------ mathics/doc/tex/mathics.tex | 16 ++++++++++++---- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/mathics/doc/tex/.gitignore b/mathics/doc/tex/.gitignore index 83dfc86b2..c8dfe1be0 100644 --- a/mathics/doc/tex/.gitignore +++ b/mathics/doc/tex/.gitignore @@ -40,3 +40,4 @@ /mathics.toc /testing.aux /testing.tex +/version-info.tex diff --git a/mathics/doc/tex/Makefile b/mathics/doc/tex/Makefile index d54a39488..c4aef0b51 100644 --- a/mathics/doc/tex/Makefile +++ b/mathics/doc/tex/Makefile @@ -16,9 +16,13 @@ doc-data $(DOC_TEX_DATA_PCL): (cd ../.. && $(PYTHON) docpipeline.py --output --keep-going) #: Build mathics PDF -mathics.pdf: mathics.tex documentation.tex logo-text-nodrop.pdf logo-heptatom.pdf $(DOC_TEX_DATA_PCL) +mathics.pdf: mathics.tex documentation.tex logo-text-nodrop.pdf logo-heptatom.pdf version-info.tex $(DOC_TEX_DATA_PCL) $(LATEXMK) --verbose -f -pdf -pdflatex="$(XETEX) -halt-on-error" mathics +#: File containing version information +version-info.tex: doc2latex.py + $(PYTHON) doc2latex.py + #: Build test PDF mathics-test.pdf: mathics-test.tex testing.tex $(LATEXMK) --verbose -f -pdf -pdflatex="$(XETEX) -halt-on-error" mathics-test @@ -43,4 +47,4 @@ clean: rm -f mathics_*.* || true rm -f mathics-*.* documentation.tex $(DOC_TEX_DATA_PCL) || true rm -f mathics.pdf mathics.dvi test-mathics.pdf test-mathics.dvi || true - rm -f mathics-test.pdf mathics-test.dvi core-version.tex || true + rm -f mathics-test.pdf mathics-test.dvi version-info.tex || true diff --git a/mathics/doc/tex/doc2latex.py b/mathics/doc/tex/doc2latex.py index 828b565e4..bb03e1583 100755 --- a/mathics/doc/tex/doc2latex.py +++ b/mathics/doc/tex/doc2latex.py @@ -7,6 +7,8 @@ import os import os.path as osp import pickle +import subprocess +import sys from argparse import ArgumentParser @@ -61,6 +63,29 @@ def print_and_log(*args): logfile.write(string) +def get_versions(): + def try_cmd(cmd_list: tuple, stdout_or_stderr: str) -> str: + status = subprocess.run(cmd_list, capture_output=True) + if status.returncode == 0: + out = getattr(status, stdout_or_stderr) + return out.decode("utf-8").split("\n")[0] + else: + return "Unknown" + + versions = { + "MathicsCoreVersion": __version__, + "PythonVersion": sys.version, + } + + for name, cmd, field in ( + ["AsymptoteVersion", ("asy", "--version"), "stderr"], + ["XeTeXVersion", ("xetex", "--version"), "stdout"], + ["GhostscriptVersion", ("gs", "--version"), "stdout"], + ): + versions[name] = try_cmd(cmd, field) + return versions + + def write_latex(doc_data, quiet=False): documentation = MathicsMainDocumentation() if not quiet: @@ -69,15 +94,13 @@ def write_latex(doc_data, quiet=False): content = documentation.latex(doc_data, quiet=quiet) content = content.encode("utf-8") doc.write(content) - DOC_VERSION_FILE = osp.join(osp.dirname(DOC_LATEX_FILE), "core-version.tex") + DOC_VERSION_FILE = osp.join(osp.dirname(DOC_LATEX_FILE), "version-info.tex") if not quiet: print(f"Writing Mathics Core Version Information to {DOC_VERSION_FILE}") with open(DOC_VERSION_FILE, "w") as doc: - doc.write( - r"""%% Mathics core version number created via doc2latex.py -\newcommand{\version}{%s}""" - % __version__ - ) + doc.write("%% Mathics core version number created via doc2latex.py\n\n") + for name, version_info in get_versions().items(): + doc.write("""\\newcommand{\\%s}{%s}\n""" % (name, version_info)) def main(): diff --git a/mathics/doc/tex/mathics.tex b/mathics/doc/tex/mathics.tex index 7db35592a..971d115c3 100644 --- a/mathics/doc/tex/mathics.tex +++ b/mathics/doc/tex/mathics.tex @@ -43,7 +43,7 @@ \usepackage{breqn} \usepackage{environ} \usepackage{multicol} - +\usepackage[]{colophon} \usepackage[k-tight]{minitoc} \setlength{\mtcindent}{0pt} \mtcsetformat{minitoc}{tocrightmargin}{2.55em plus 1fil} @@ -69,7 +69,7 @@ \includegraphics[height=0.08125\linewidth]{logo-text-nodrop.pdf} \\[.5em] {\LARGE\color{subtitle}\textit{\textmd{A free, open-source alternative to Mathematica}}} - \par\textmd{Mathics Core Version \version} + \par\textmd{Mathics Core Version \MathicsCoreVersion} } \author{The Mathics Team} @@ -247,7 +247,7 @@ \hyphenation{assign-ments} \begin{document} -\input{core-version.tex} +\input{version-info.tex} \maketitle @@ -270,5 +270,13 @@ \input{documentation.tex} \printindex - +\begin{colophon} +\begin{description} + \item[Mathics Core] \hfill \\ \MathicsCoreVersion + \item[Python] \hfill \\ \PythonVersion + \item[XeTeX] \hfill \\ \XeTeXVersion + \item[Asymptote] \hfill \\ \AsymptoteVersion + \item[Ghostscript] \hfill \\ \GhostscriptVersion +\end{description} +\end{colophon} \end{document} From 012ff28ad1c50604845b619d078da879e373c677 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Wed, 4 Aug 2021 12:13:51 -0300 Subject: [PATCH 013/193] Map `Cuboid` with 2d coordinates to `Rectangle` --- mathics/builtin/drawing/graphics3d.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index c0711da83..2910890a4 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -215,7 +215,10 @@ class Cuboid(Builtin): Cuboid also known as interval, rectangle, square, cube, rectangular parallelepiped, tesseract, orthotope, and box.
'Cuboid[$p_min$]' -
is a unit cube with its lower corner at point $p_min$. +
is a unit cube/square with its lower corner at point $p_min$. + +
'Cuboid[$p_min$, $p_max$] +
is a 2d square with with lower corner $p_min$ and upper corner $p_max$.
'Cuboid[{$p_min$, $p_max$}]'
is a cuboid with lower corner $p_min$ and upper corner $p_max$. @@ -232,6 +235,9 @@ class Cuboid(Builtin): >> Graphics3D[{Red, Cuboid[{{0, 0, 0}, {1, 1, 0.5}}], Blue, Cuboid[{{0.25, 0.25, 0.5}, {0.75, 0.75, 1}}]}] = -Graphics3D- + >> Graphics[Cuboid[{0, 0}]] + = -Graphics- + ## """ @@ -243,6 +249,18 @@ class Cuboid(Builtin): summary_text = "unit cube" + def apply_unit_square(self, xmin, ymin, evaluation): + "Cuboid[{xmin_, ymin_}]" + + return Expression( + "Rectangle", + List(xmin, ymin), + List( + Real(xmin.to_python() + 1), + Real(ymin.to_python() + 1), + ), + ) + def apply_unit_cube(self, xmin, ymin, zmin, evaluation): "Cuboid[{xmin_, ymin_, zmin_}]" @@ -258,6 +276,11 @@ def apply_unit_cube(self, xmin, ymin, zmin, evaluation): ), ) + def apply_rectangle(self, xmin, ymin, xmax, ymax, evaluation): + "Cuboid[{xmin_, ymin_}, {xmax_, ymax_}]" + + return Expression("Rectangle", List(xmin, ymin), List(xmax, ymax)) + def apply_check(self, positions, evaluation): "Cuboid[positions_List]" From f0146f2c66d5003199a65a36c58aad85c986c4ba Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Wed, 4 Aug 2021 21:54:42 -0300 Subject: [PATCH 014/193] Multiply pointSize by 0.5 to follow the new API --- mathics/format/json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/format/json.py b/mathics/format/json.py index 4649eb3af..c5aab3fb4 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -157,7 +157,7 @@ def point_3d_box(self) -> list: self.lines, "point", face_color, - {"color": face_color, "pointSize": relative_point_size}, + {"color": face_color, "pointSize": relative_point_size * 0.5}, ) # print("### json Point3DBox", data) From 4c0695be7259d04ed7eb4c0973fd8e4a996000ac Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 6 Aug 2021 05:56:05 -0400 Subject: [PATCH 015/193] Add transformation functions... Segregate image internals --- CHANGES.rst | 14 + mathics/builtin/drawing/image.py | 102 +----- mathics/builtin/drawing/image_internals.py | 97 +++++ mathics/builtin/tensors.py | 391 +++++++++++++-------- 4 files changed, 369 insertions(+), 235 deletions(-) create mode 100644 mathics/builtin/drawing/image_internals.py diff --git a/CHANGES.rst b/CHANGES.rst index ca12d3658..153da3260 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,20 @@ CHANGES ======= +4.0.1 +----- + +New builtins +++++++++++++ + +Tensor functions: + +* ``RotationTransform`` +* ``ScalingTransform`` +* ``ShearingTransform`` +* ``TransformationFunction`` +* ``TranslationTransform`` + 4.0.0 ----- diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 44af23493..b73de6339 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -28,6 +28,16 @@ colorspaces as known_colorspaces, ) +from mathics.builtin.drawing.image_internals import ( + pixels_as_float, + pixels_as_ubyte, + pixels_as_uint, + matrix_to_numpy, + numpy_to_matrix, + numpy_flip, + convolve, +) + import base64 import functools import math @@ -67,98 +77,6 @@ class _SkimageBuiltin(_ImageBuiltin): requires = _skimage_requires -# helpers - - -def pixels_as_float(pixels): - dtype = pixels.dtype - if dtype in (numpy.float32, numpy.float64): - return pixels - elif dtype == numpy.uint8: - return pixels.astype(numpy.float32) / 255.0 - elif dtype == numpy.uint16: - return pixels.astype(numpy.float32) / 65535.0 - elif dtype == numpy.bool: - return pixels.astype(numpy.float32) - else: - raise NotImplementedError - - -def pixels_as_ubyte(pixels): - dtype = pixels.dtype - if dtype in (numpy.float32, numpy.float64): - pixels = numpy.maximum(numpy.minimum(pixels, 1.0), 0.0) - return (pixels * 255.0).astype(numpy.uint8) - elif dtype == numpy.uint8: - return pixels - elif dtype == numpy.uint16: - return (pixels / 256).astype(numpy.uint8) - elif dtype == numpy.bool: - return pixels.astype(numpy.uint8) * 255 - else: - raise NotImplementedError - - -def pixels_as_uint(pixels): - dtype = pixels.dtype - if dtype in (numpy.float32, numpy.float64): - pixels = numpy.maximum(numpy.minimum(pixels, 1.0), 0.0) - return (pixels * 65535.0).astype(numpy.uint16) - elif dtype == numpy.uint8: - return pixels.astype(numpy.uint16) * 256 - elif dtype == numpy.uint16: - return pixels - elif dtype == numpy.bool: - return pixels.astype(numpy.uint8) * 65535 - else: - raise NotImplementedError - - -def matrix_to_numpy(a): - def matrix(): - for y in a.leaves: - yield [x.round_to_float() for x in y.leaves] - - return numpy.array(list(matrix())) - - -def numpy_to_matrix(pixels): - channels = pixels.shape[2] - if channels == 1: - return pixels[:, :, 0].tolist() - else: - return pixels.tolist() - - -def numpy_flip(pixels, axis): - f = (numpy.flipud, numpy.fliplr)[axis] - return f(pixels) - - -def convolve(in1, in2, fixed=True): - # a very much boiled down version scipy.signal.signaltools.fftconvolve with added padding, see - # https://github.com/scipy/scipy/blob/master/scipy/signal/signaltools.py; please see the Scipy - # LICENSE in the accompanying files. - - in1 = numpy.asarray(in1) - in2 = numpy.asarray(in2) - - padding = numpy.array(in2.shape) // 2 - if fixed: # add "Fixed" padding? - in1 = numpy.pad(in1, padding, "edge") - - s1 = numpy.array(in1.shape) - s2 = numpy.array(in2.shape) - shape = s1 + s2 - 1 - - sp1 = numpy.fft.rfftn(in1, shape) - sp2 = numpy.fft.rfftn(in2, shape) - ret = numpy.fft.irfftn(sp1 * sp2, shape) - - excess = (numpy.array(ret.shape) - s1) // 2 + padding - return ret[tuple(slice(p, -p) for p in excess)] - - # import and export diff --git a/mathics/builtin/drawing/image_internals.py b/mathics/builtin/drawing/image_internals.py new file mode 100644 index 000000000..7504eca69 --- /dev/null +++ b/mathics/builtin/drawing/image_internals.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +"""helper functions for images +""" + +from mathics.version import __version__ # noqa used in loading to check consistency. + +import numpy + + +def convolve(in1, in2, fixed=True): + # a very much boiled down version scipy.signal.signaltools.fftconvolve with added padding, see + # https://github.com/scipy/scipy/blob/master/scipy/signal/signaltools.py; please see the Scipy + # LICENSE in the accompanying files. + + in1 = numpy.asarray(in1) + in2 = numpy.asarray(in2) + + padding = numpy.array(in2.shape) // 2 + if fixed: # add "Fixed" padding? + in1 = numpy.pad(in1, padding, "edge") + + s1 = numpy.array(in1.shape) + s2 = numpy.array(in2.shape) + shape = s1 + s2 - 1 + + sp1 = numpy.fft.rfftn(in1, shape) + sp2 = numpy.fft.rfftn(in2, shape) + ret = numpy.fft.irfftn(sp1 * sp2, shape) + + excess = (numpy.array(ret.shape) - s1) // 2 + padding + return ret[tuple(slice(p, -p) for p in excess)] + + +def matrix_to_numpy(a): + def matrix(): + for y in a.leaves: + yield [x.round_to_float() for x in y.leaves] + + return numpy.array(list(matrix())) + + +def numpy_flip(pixels, axis): + f = (numpy.flipud, numpy.fliplr)[axis] + return f(pixels) + + +def numpy_to_matrix(pixels): + channels = pixels.shape[2] + if channels == 1: + return pixels[:, :, 0].tolist() + else: + return pixels.tolist() + + +def pixels_as_float(pixels): + dtype = pixels.dtype + if dtype in (numpy.float32, numpy.float64): + return pixels + elif dtype == numpy.uint8: + return pixels.astype(numpy.float32) / 255.0 + elif dtype == numpy.uint16: + return pixels.astype(numpy.float32) / 65535.0 + elif dtype == numpy.bool: + return pixels.astype(numpy.float32) + else: + raise NotImplementedError + + +def pixels_as_ubyte(pixels): + dtype = pixels.dtype + if dtype in (numpy.float32, numpy.float64): + pixels = numpy.maximum(numpy.minimum(pixels, 1.0), 0.0) + return (pixels * 255.0).astype(numpy.uint8) + elif dtype == numpy.uint8: + return pixels + elif dtype == numpy.uint16: + return (pixels / 256).astype(numpy.uint8) + elif dtype == numpy.bool: + return pixels.astype(numpy.uint8) * 255 + else: + raise NotImplementedError + + +def pixels_as_uint(pixels): + dtype = pixels.dtype + if dtype in (numpy.float32, numpy.float64): + pixels = numpy.maximum(numpy.minimum(pixels, 1.0), 0.0) + return (pixels * 65535.0).astype(numpy.uint16) + elif dtype == numpy.uint8: + return pixels.astype(numpy.uint16) * 256 + elif dtype == numpy.uint16: + return pixels + elif dtype == numpy.bool: + return pixels.astype(numpy.uint8) * 65535 + else: + raise NotImplementedError diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index c411d2855..d9c3a9bc0 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -26,6 +26,78 @@ from mathics.builtin.lists import get_part +def get_default_distance(p): + if all(q.is_numeric() for q in p): + return "SquaredEuclideanDistance" + elif all(q.get_head_name() == "System`List" for q in p): + dimensions = [get_dimensions(q) for q in p] + if len(dimensions) < 1: + return None + d0 = dimensions[0] + if not all(d == d0 for d in dimensions[1:]): + return None + if len(dimensions[0]) == 1: # vectors? + + def is_boolean(x): + return x.get_head_name() == "System`Symbol" and x in ( + SymbolTrue, + SymbolFalse, + ) + + if all(all(is_boolean(e) for e in q.leaves) for q in p): + return "JaccardDissimilarity" + return "SquaredEuclideanDistance" + elif all(isinstance(q, String) for q in p): + return "EditDistance" + else: + from mathics.builtin.colors.color_directives import expression_to_color + + if all(expression_to_color(q) is not None for q in p): + return "ColorDistance" + + return None + + +def get_dimensions(expr, head=None): + if expr.is_atom(): + return [] + else: + if head is not None and not expr.head.sameQ(head): + return [] + sub_dim = None + sub = [] + for leaf in expr.leaves: + sub = get_dimensions(leaf, expr.head) + if sub_dim is None: + sub_dim = sub + else: + if sub_dim != sub: + sub = [] + break + return [len(expr.leaves)] + sub + + +class ArrayDepth(Builtin): + """ +
+
'ArrayDepth[$a$]' +
returns the depth of the non-ragged array $a$, defined as + 'Length[Dimensions[$a$]]'. +
+ + >> ArrayDepth[{{a,b},{c,d}}] + = 2 + >> ArrayDepth[x] + = 0 + """ + + rules = { + "ArrayDepth[list_]": "Length[Dimensions[list]]", + } + + summary_text = "the rank of a tensor" + + class ArrayQ(Builtin): """
@@ -55,6 +127,8 @@ class ArrayQ(Builtin): "ArrayQ[expr_, pattern_]": "ArrayQ[expr, pattern, True&]", } + summary_text = "test whether an object is a tensor of a given rank" + def apply(self, expr, pattern, test, evaluation): "ArrayQ[expr_, pattern_, test_]" @@ -91,64 +165,36 @@ def check(level, expr): return SymbolTrue -class VectorQ(Builtin): +class DiagonalMatrix(Builtin): """
-
'VectorQ[$v$]' -
returns 'True' if $v$ is a list of elements which are - not themselves lists. -
'VectorQ[$v$, $f$]' -
returns 'True' if $v$ is a vector and '$f$[$x$]' returns - 'True' for each element $x$ of $v$. +
'DiagonalMatrix[$list$]' +
gives a matrix with the values in $list$ on its diagonal and zeroes elsewhere.
- >> VectorQ[{a, b, c}] - = True - """ - - rules = { - "VectorQ[expr_]": "ArrayQ[expr, 1]", - "VectorQ[expr_, test_]": "ArrayQ[expr, 1, test]", - } - - -class MatrixQ(Builtin): - """ -
-
'MatrixQ[$m$]' -
returns 'True' if $m$ is a list of equal-length lists. -
'MatrixQ[$m$, $f$]' -
only returns 'True' if '$f$[$x$]' returns 'True' for each - element $x$ of the matrix $m$. -
+ >> DiagonalMatrix[{1, 2, 3}] + = {{1, 0, 0}, {0, 2, 0}, {0, 0, 3}} + >> MatrixForm[%] + = 1 0 0 + . + . 0 2 0 + . + . 0 0 3 - >> MatrixQ[{{1, 3}, {4.0, 3/2}}, NumberQ] - = True + #> DiagonalMatrix[a + b] + = DiagonalMatrix[a + b] """ - rules = { - "MatrixQ[expr_]": "ArrayQ[expr, 2]", - "MatrixQ[expr_, test_]": "ArrayQ[expr, 2, test]", - } - + def apply(self, list, evaluation): + "DiagonalMatrix[list_List]" -def get_dimensions(expr, head=None): - if expr.is_atom(): - return [] - else: - if head is not None and not expr.head.sameQ(head): - return [] - sub_dim = None - sub = [] - for leaf in expr.leaves: - sub = get_dimensions(leaf, expr.head) - if sub_dim is None: - sub_dim = sub - else: - if sub_dim != sub: - sub = [] - break - return [len(expr.leaves)] + sub + result = [] + n = len(list.leaves) + for index, item in enumerate(list.leaves): + row = [Integer0] * n + row[index] = item + result.append(Expression("List", *row)) + return Expression("List", *result) class Dimensions(Builtin): @@ -180,31 +226,14 @@ class Dimensions(Builtin): = {1, 0} """ + summary_text = "the dimensions of a tensor" + def apply(self, expr, evaluation): "Dimensions[expr_]" return Expression("List", *[Integer(dim) for dim in get_dimensions(expr)]) -class ArrayDepth(Builtin): - """ -
-
'ArrayDepth[$a$]' -
returns the depth of the non-ragged array $a$, defined as - 'Length[Dimensions[$a$]]'. -
- - >> ArrayDepth[{{a,b},{c,d}}] - = 2 - >> ArrayDepth[x] - = 0 - """ - - rules = { - "ArrayDepth[list_]": "Length[Dimensions[list]]", - } - - class Dot(BinaryOperator): """
@@ -234,6 +263,24 @@ class Dot(BinaryOperator): "Dot[a_List, b_List]": "Inner[Times, a, b, Plus]", } + summary_text = "dot product" + + +class IdentityMatrix(Builtin): + """ +
+
'IdentityMatrix[$n$]' +
gives the identity matrix with $n$ rows and columns. +
+ + >> IdentityMatrix[3] + = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}} + """ + + rules = { + "IdentityMatrix[n_Integer]": "DiagonalMatrix[Table[1, {n}]]", + } + class Inner(Builtin): """ @@ -268,10 +315,6 @@ class Inner(Builtin): = {{1 / Sqrt[b], 0}, {a / Sqrt[b], Sqrt[b]}} """ - rules = { - "Inner[f_, list1_, list2_]": "Inner[f, list1, list2, Plus]", - } - messages = { "incom": ( "Length `1` of dimension `2` in `3` is incommensurate with " @@ -279,6 +322,12 @@ class Inner(Builtin): ), } + rules = { + "Inner[f_, list1_, list2_]": "Inner[f, list1, list2, Plus]", + } + + summary_text = "generalized inner product" + def apply(self, f, list1, list2, g, evaluation): "Inner[f_, list1_, list2_, g_]" @@ -359,6 +408,8 @@ class Outer(Builtin): = {{0, 1, 0}, {1, 0, 1}, {0, ComplexInfinity, 0}} """ + summary_text = "generalized outer product" + def apply(self, f, lists, evaluation): "Outer[f_, lists__]" @@ -390,11 +441,121 @@ def rec(item, rest_lists, current): return rec(lists[0], lists[1:], []) +class MatrixQ(Builtin): + """ +
+
'MatrixQ[$m$]' +
returns 'True' if $m$ is a list of equal-length lists. +
'MatrixQ[$m$, $f$]' +
only returns 'True' if '$f$[$x$]' returns 'True' for each + element $x$ of the matrix $m$. +
+ + >> MatrixQ[{{1, 3}, {4.0, 3/2}}, NumberQ] + = True + """ + + rules = { + "MatrixQ[expr_]": "ArrayQ[expr, 2]", + "MatrixQ[expr_, test_]": "ArrayQ[expr, 2, test]", + } + + +class RotationTransform(Builtin): + """ +
+
'RotationTransform[$phi$]' +
gives a rotation by $phi$. + +
'RotationTransform[$phi$, $p$]' +
gives a rotation by $phi$ around the point $p$. +
+ """ + + rules = { + "RotationTransform[phi_]": "TransformationFunction[{{Cos[phi], -Sin[phi], 0}, {Sin[phi], Cos[phi], 0}, {0, 0, 1}}]", + "RotationTransform[phi_, p_]": "TranslationTransform[p] . RotationTransform[phi] . TranslationTransform[-p]", + } + + +class ScalingTransform(Builtin): + """ +
+
'ScalingTransform[$v$]' +
gives a scaling transform of $v$. $v$ may be a scalar or a vector. + +
'ScalingTransform[$phi$, $p$]' +
gives a scaling transform of $v$ that is centered at the point $p$. +
+ """ + + rules = { + "ScalingTransform[v_]": "TransformationFunction[DiagonalMatrix[Join[v, {1}]]]", + "ScalingTransform[v_, p_]": "TranslationTransform[p] . ScalingTransform[v] . TranslationTransform[-p]", + } + + +class ShearingTransform(Builtin): + """ +
+
'ShearingTransform[$phi$, {1, 0}, {0, 1}]' +
gives a horizontal shear by the angle $phi$. +
'ShearingTransform[$phi$, {0, 1}, {1, 0}]' +
gives a vertical shear by the angle $phi$. +
'ShearingTransform[$phi$, $u$, $u$, $p$]' +
gives a shear centered at the point $p$. +
+ """ + + rules = { + "ShearingTransform[phi_, {1, 0}, {0, 1}]": "TransformationFunction[{{1, Tan[phi], 0}, {0, 1, 0}, {0, 0, 1}}]", + "ShearingTransform[phi_, {0, 1}, {1, 0}]": "TransformationFunction[{{1, 0, 0}, {Tan[phi], 1, 0}, {0, 0, 1}}]", + "ShearingTransform[phi_, u_, v_, p_]": "TranslationTransform[p] . ShearingTransform[phi, u, v] . TranslationTransform[-p]", + } + + +class TransformationFunction(Builtin): + """ +
+
'TransformationFunction[$m$]' +
represents a transformation. +
+ + >> RotationTransform[Pi].TranslationTransform[{1, -1}] + = TransformationFunction[{{-1, 0, -1}, {0, -1, 1}, {0, 0, 1}}] + + >> TranslationTransform[{1, -1}].RotationTransform[Pi] + = TransformationFunction[{{-1, 0, 1}, {0, -1, -1}, {0, 0, 1}}] + """ + + rules = { + "Dot[TransformationFunction[a_], TransformationFunction[b_]]": "TransformationFunction[a . b]", + "TransformationFunction[m_][v_]": "Take[m . Join[v, {1}], Length[v]]", + } + + +class TranslationTransform(Builtin): + """ +
+
'TranslationTransform[$v$]' +
gives the translation by the vector $v$. +
+ + >> TranslationTransform[{1, 2}] + = TransformationFunction[{{1, 0, 1}, {0, 1, 2}, {0, 0, 1}}] + """ + + rules = { + "TranslationTransform[v_]": "TransformationFunction[IdentityMatrix[Length[v] + 1] + " + "(Join[ConstantArray[0, Length[v]], {#}]& /@ Join[v, {0}])]", + } + + class Transpose(Builtin): """
-
'Tranpose[$m$]' -
transposes rows and columns in the matrix $m$. +
'Tranpose[$m$]' +
transposes rows and columns in the matrix $m$.
>> Transpose[{{1, 2, 3}, {4, 5, 6}}] @@ -410,6 +571,8 @@ class Transpose(Builtin): = Transpose[x] """ + summary_text = "transpose to rearrange indices in any way" + def apply(self, m, evaluation): "Transpose[m_?MatrixQ]" @@ -423,81 +586,23 @@ def apply(self, m, evaluation): return Expression("List", *[Expression("List", *row) for row in result]) -class DiagonalMatrix(Builtin): +class VectorQ(Builtin): """
-
'DiagonalMatrix[$list$]' -
gives a matrix with the values in $list$ on its diagonal and zeroes elsewhere. -
+
'VectorQ[$v$]' +
returns 'True' if $v$ is a list of elements which are not themselves lists. - >> DiagonalMatrix[{1, 2, 3}] - = {{1, 0, 0}, {0, 2, 0}, {0, 0, 3}} - >> MatrixForm[%] - = 1 0 0 - . - . 0 2 0 - . - . 0 0 3 - - #> DiagonalMatrix[a + b] - = DiagonalMatrix[a + b] - """ - - def apply(self, list, evaluation): - "DiagonalMatrix[list_List]" - - result = [] - n = len(list.leaves) - for index, item in enumerate(list.leaves): - row = [Integer0] * n - row[index] = item - result.append(Expression("List", *row)) - return Expression("List", *result) - - -class IdentityMatrix(Builtin): - """ -
-
'IdentityMatrix[$n$]' -
gives the identity matrix with $n$ rows and columns. +
'VectorQ[$v$, $f$]' +
returns 'True' if $v$ is a vector and '$f$[$x$]' returns 'True' for each element $x$ of $v$.
- >> IdentityMatrix[3] - = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}} + >> VectorQ[{a, b, c}] + = True """ rules = { - "IdentityMatrix[n_Integer]": "DiagonalMatrix[Table[1, {n}]]", + "VectorQ[expr_]": "ArrayQ[expr, 1]", + "VectorQ[expr_, test_]": "ArrayQ[expr, 1, test]", } - -def get_default_distance(p): - if all(q.is_numeric() for q in p): - return "SquaredEuclideanDistance" - elif all(q.get_head_name() == "System`List" for q in p): - dimensions = [get_dimensions(q) for q in p] - if len(dimensions) < 1: - return None - d0 = dimensions[0] - if not all(d == d0 for d in dimensions[1:]): - return None - if len(dimensions[0]) == 1: # vectors? - - def is_boolean(x): - return x.get_head_name() == "System`Symbol" and x in ( - SymbolTrue, - SymbolFalse, - ) - - if all(all(is_boolean(e) for e in q.leaves) for q in p): - return "JaccardDissimilarity" - return "SquaredEuclideanDistance" - elif all(isinstance(q, String) for q in p): - return "EditDistance" - else: - from mathics.builtin.colors.color_directives import expression_to_color - - if all(expression_to_color(q) is not None for q in p): - return "ColorDistance" - - return None + summary_text = "test whether an object is a vector" From 04d439c97a284f1e8c49dcb323e64997348c33dc Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 6 Aug 2021 08:57:20 -0400 Subject: [PATCH 016/193] Add section summaries for date & time chapter --- mathics/builtin/datentime.py | 1328 ++++++++++++++++++---------------- 1 file changed, 689 insertions(+), 639 deletions(-) diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index 229eca8fc..f964d468e 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -101,168 +101,53 @@ def total_seconds(td): total_seconds = timedelta.total_seconds -class TimeRemaining(Builtin): - """ -
-
'TimeRemaining[]' -
Gives the number of seconds remaining until the earliest enclosing 'TimeConstrained' will request the current computation to stop. -
'TimeConstrained[$expr$, $t$, $failexpr$]' -
returns $failexpr$ if the time constraint is not met. -
- - If TimeConstrained is called out of a TimeConstrained expression, returns `Infinity` - >> TimeRemaining[] - = Infinity - - X> TimeConstrained[1+2; Print[TimeRemaining[]], 0.9] - | 0.899318 - - """ - - def apply(self, evaluation): - "TimeRemaining[]" - if len(evaluation.timeout_queue) > 0: - t, start_time = evaluation.timeout_queue[-1] - curr_time = datetime.now().timestamp() - deltat = t + start_time - curr_time - return Real(deltat) - else: - return SymbolInfinity - - -if sys.platform != "win32" and ("Pyston" not in sys.version): - - class TimeConstrained(Builtin): - """ -
-
'TimeConstrained[$expr$, $t$]' -
'evaluates $expr$, stopping after $t$ seconds.' -
'TimeConstrained[$expr$, $t$, $failexpr$]' -
'returns $failexpr$ if the time constraint is not met.' -
- - Possible issues: for certain time-consuming functions (like simplify) - which are based on sympy or other libraries, it is possible that - the evaluation continues after the timeout. However, at the end of the evaluation, the function will return $\\$Aborted$ and the results will not affect - the state of the mathics kernel. - - """ - - # FIXME: these tests sometimes cause SEGVs which probably means - # that TimeConstraint has bugs. - - # Consider testing via unit tests. - # >> TimeConstrained[Integrate[Sin[x]^1000000,x],1] - # = $Aborted - - # >> TimeConstrained[Integrate[Sin[x]^1000000,x], 1, Integrate[Cos[x],x]] - # = Sin[x] - - # >> s=TimeConstrained[Integrate[Sin[x] ^ 3, x], a] - # : Number of seconds a is not a positive machine-sized number or Infinity. - # = TimeConstrained[Integrate[Sin[x] ^ 3, x], a] - - # >> a=1; s - # = Cos[x] (-5 + Cos[2 x]) / 6 - - attributes = ("HoldAll",) - messages = { - "timc": "Number of seconds `1` is not a positive machine-sized number or Infinity.", - } - - def apply_2(self, expr, t, evaluation): - "TimeConstrained[expr_, t_]" - return self.apply_3(expr, t, SymbolAborted, evaluation) - - def apply_3(self, expr, t, failexpr, evaluation): - "TimeConstrained[expr_, t_, failexpr_]" - t = t.evaluate(evaluation) - if not t.is_numeric(): - evaluation.message("TimeConstrained", "timc", t) - return - try: - t = float(t.to_python()) - evaluation.timeout_queue.append((t, datetime.now().timestamp())) - request = lambda: expr.evaluate(evaluation) - res = run_with_timeout_and_stack(request, t, evaluation) - except TimeoutInterrupt: - evaluation.timeout_queue.pop() - return failexpr.evaluate(evaluation) - except: - evaluation.timeout_queue.pop() - raise - evaluation.timeout_queue.pop() - return res - - -class Timing(Builtin): - """ -
-
'Timing[$expr$]' -
measures the processor time taken to evaluate $expr$. - It returns a list containing the measured time in seconds and the result of the evaluation. -
- - >> Timing[50!] - = {..., 30414093201713378043612608166064768844377641568960512000000000000} - >> Attributes[Timing] - = {HoldAll, Protected} - """ - - attributes = ("HoldAll",) - - def apply(self, expr, evaluation): - "Timing[expr_]" - - start = time.process_time() - result = expr.evaluate(evaluation) - stop = time.process_time() - return Expression("List", Real(stop - start), result) - - -class AbsoluteTiming(Builtin): - """ -
-
'AbsoluteTiming[$expr$]' -
evaluates $expr$, returning a list of the absolute number of seconds in real time that have elapsed, together with the result obtained. -
- - >> AbsoluteTiming[50!] - = {..., 30414093201713378043612608166064768844377641568960512000000000000} - >> Attributes[AbsoluteTiming] - = {HoldAll, Protected} - """ - - attributes = ("HoldAll",) - - def apply(self, expr, evaluation): - "AbsoluteTiming[expr_]" - - start = time.time() - result = expr.evaluate(evaluation) - stop = time.time() - return Expression("List", Real(stop - start), result) - - -class DateStringFormat(Predefined): - """ -
-
'$DateStringFormat' -
gives the format used for dates generated by 'DateString'. -
- - >> $DateStringFormat - = {DateTimeShort} - """ - - name = "$DateStringFormat" - - value = "DateTimeShort" +class _Date: + def __init__(self, datelist=[], absolute=None, datestr=None): + datelist += [1900, 1, 1, 0, 0, 0.0][len(datelist) :] + self.date = datetime( + datelist[0], + datelist[1], + datelist[2], + datelist[3], + datelist[4], + int(datelist[5]), + int(1e6 * (datelist[5] % 1.0)), + ) + if absolute is not None: + self.date += timedelta(seconds=absolute) + if datestr is not None: + if absolute is not None: + raise ValueError + self.date = dateutil.parser.parse(datestr) - # TODO: Methods to change this + def addself(self, timevec): + years = self.date.year + timevec[0] + int((self.date.month + timevec[1]) / 12) + months = (self.date.month + timevec[1]) % 12 + if months == 0: + months += 12 + years -= 1 + self.date = datetime( + years, + months, + self.date.day, + self.date.hour, + self.date.minute, + self.date.second, + ) + tdelta = timedelta( + days=timevec[2], hours=timevec[3], minutes=timevec[4], seconds=timevec[5] + ) + self.date += tdelta - def evaluate(self, evaluation): - return Expression("List", String(self.value)) + def to_list(self): + return [ + self.date.year, + self.date.month, + self.date.day, + self.date.hour, + self.date.minute, + self.date.second + 1e-6 * self.date.microsecond, + ] class _DateFormat(Builtin): @@ -443,179 +328,240 @@ def to_datelist(self, epochtime, evaluation): return -class DateList(_DateFormat): +class AbsoluteTime(_DateFormat): """
-
'DateList[]' -
returns the current local time in the form {$year$, $month$, $day$, $hour$, $minute$, $second$}. +
'AbsoluteTime[]' +
gives the local time in seconds since epoch January 1, 1900, in your time zone. -
'DateList[$time$]' -
returns a formatted date for the number of seconds $time$ since epoch Jan 1 1900. +
'AbsoluteTime[{$y$, $m$, $d$, $h$, $m$, $s$}]' +
gives the absolute time specification corresponding to a date list. -
'DateList[{$y$, $m$, $d$, $h$, $m$, $s$}]' -
converts an incomplete date list to the standard representation. +
'AbsoluteTime["$string$"]' +
gives the absolute time specification for a given date string. + +
'AbsoluteTime[{"$string$",{$e1$, $e2$, ...}}]' +
takgs the date string to contain the elements "$ei$".
- >> DateList[0] - = {1900, 1, 1, 0, 0, 0.} + >> AbsoluteTime[] + = ... - >> DateList[3155673600] - = {2000, 1, 1, 0, 0, 0.} + >> AbsoluteTime[{2000}] + = 3155673600 - >> DateList[{2003, 5, 0.5, 0.1, 0.767}] - = {2003, 4, 30, 12, 6, 46.02} + >> AbsoluteTime[{"01/02/03", {"Day", "Month", "YearShort"}}] + = 3253046400 - >> DateList[{2012, 1, 300., 10}] - = {2012, 10, 26, 10, 0, 0.} + >> AbsoluteTime["6 June 1991"] + = 2885155200 - >> DateList["31/10/1991"] - = {1991, 10, 31, 0, 0, 0.} + >> AbsoluteTime[{"6-6-91", {"Day", "Month", "YearShort"}}] + = 2885155200 - >> DateList["1/10/1991"] - : The interpretation of 1/10/1991 is ambiguous. - = {1991, 1, 10, 0, 0, 0.} + ## Mathematica Bug - Mathics gets it right + #> AbsoluteTime[1000] + = 1000 + """ - #> DateList["7/8/9"] - : The interpretation of 7/8/9 is ambiguous. - = {2009, 7, 8, 0, 0, 0.} + abstract = "absolute time in seconds" - >> DateList[{"31/10/91", {"Day", "Month", "YearShort"}}] - = {1991, 10, 31, 0, 0, 0.} + summary_text = "absolute time in seconds" - >> DateList[{"31 10/91", {"Day", " ", "Month", "/", "YearShort"}}] - = {1991, 10, 31, 0, 0, 0.} + def apply_now(self, evaluation): + "AbsoluteTime[]" + return from_python(total_seconds(datetime.now() - EPOCH_START)) - If not specified, the current year assumed - >> DateList[{"5/18", {"Month", "Day"}}] - = {..., 5, 18, 0, 0, 0.} - """ - - # TODO: Somehow check that the current year is correct - - rules = { - "DateList[]": "DateList[AbsoluteTime[]]", - 'DateList["02/27/20/13"]': 'Import[Uncompress["eJxTyigpKSi20tfPzE0v1qvITk7RS87P1QfizORi/czi/HgLMwNDvYK8dCUATpsOzQ=="]]', - } + def apply_spec(self, epochtime, evaluation): + "AbsoluteTime[epochtime_]" - def apply(self, epochtime, evaluation): - "%(name)s[epochtime_]" datelist = self.to_datelist(epochtime, evaluation) if datelist is None: return - return Expression("List", *datelist) + date = _Date(datelist=datelist) + tdelta = date.date - EPOCH_START + if tdelta.microseconds == 0: + return from_python(int(total_seconds(tdelta))) + return from_python(total_seconds(tdelta)) -class DateString(_DateFormat): +class AbsoluteTiming(Builtin): """
-
'DateString[]' -
returns the current local time and date as a string. - -
'DateString[$elem$]' -
returns the time formatted according to $elems$. - -
'DateString[{$e1$, $e2$, ...}]' -
concatinates the time formatted according to elements $ei$. - -
'DateString[$time$]' -
returns the date string of an AbsoluteTime. +
'AbsoluteTiming[$expr$]' +
evaluates $expr$, returning a list of the absolute number of seconds in real time that have elapsed, together with the result obtained. +
-
'DateString[{$y$, $m$, $d$, $h$, $m$, $s$}]' -
returns the date string of a date list specification. + >> AbsoluteTiming[50!] + = {..., 30414093201713378043612608166064768844377641568960512000000000000} + >> Attributes[AbsoluteTiming] + = {HoldAll, Protected} + """ -
'DateString[$string$]' -
returns the formatted date string of a date string specification. + attributes = ("HoldAll",) -
'DateString[$spec$, $elems$]' -
formats the time in turns of $elems$. Both $spec$ and $elems$ can take any of the above formats. -
+ summary_text = "total wall-clock time to run a Mathics command" - The current date and time: - >> DateString[]; + def apply(self, expr, evaluation): + "AbsoluteTiming[expr_]" - >> DateString[{1991, 10, 31, 0, 0}, {"Day", " ", "MonthName", " ", "Year"}] - = 31 October 1991 + start = time.time() + result = expr.evaluate(evaluation) + stop = time.time() + return Expression("List", Real(stop - start), result) - >> DateString[{2007, 4, 15, 0}] - = Sun 15 Apr 2007 00:00:00 - >> DateString[{1979, 3, 14}, {"DayName", " ", "Month", "-", "YearShort"}] - = Wednesday 03-79 +class DateDifference(Builtin): + """ +
+
'DateDifference[$date1$, $date2$]' +
returns the difference between $date1$ and $date2$ in days. +
'DateDifference[$date1$, $date2$, $unit$]' +
returns the difference in the specified $unit$. +
'DateDifference[$date1$, $date2$, {$unit1$, $unit2$, ...}]' +
represents the difference as a list of integer multiples of + each $unit$, with any remainder expressed in the smallest unit. +
- Non-integer values are accepted too: - >> DateString[{1991, 6, 6.5}] - = Thu 6 Jun 1991 12:00:00 + >> DateDifference[{2042, 1, 4}, {2057, 1, 1}] + = 5476 - ## Check Leading 0 - #> DateString[{1979, 3, 14}, {"DayName", " ", "MonthShort", "-", "YearShort"}] - = Wednesday 3-79 + >> DateDifference[{1936, 8, 14}, {2000, 12, 1}, "Year"] + = {64.3425, Year} - #> DateString[{"DayName", " ", "Month", "/", "YearShort"}] - = ... + >> DateDifference[{2010, 6, 1}, {2015, 1, 1}, "Hour"] + = {40200, Hour} - ## Assumed separators - #> DateString[{"06/06/1991", {"Month", "Day", "Year"}}] - = Thu 6 Jun 1991 00:00:00 + >> DateDifference[{2003, 8, 11}, {2003, 10, 19}, {"Week", "Day"}] + = {{9, Week}, {6, Day}} + """ - ## Specified separators - #> DateString[{"06/06/1991", {"Month", "/", "Day", "/", "Year"}}] - = Thu 6 Jun 1991 00:00:00 + # FIXME: Since timedelta doesnt use large time units (years, months etc) + # this method can be innacuarate. The example below gives fractional Days + # (20.1666666667 not 20). """ + >> DateDifference[{2000, 6, 15}, {2001, 9, 4}, {"Month", "Day"}] + = {{14, "Month"}, {20, "Day"}} + """ - rules = { - "DateString[]": "DateString[DateList[], $DateStringFormat]", - "DateString[epochtime_?(VectorQ[#1, NumericQ]&)]": ( - "DateString[epochtime, $DateStringFormat]" - ), - "DateString[epochtime_?NumericQ]": ("DateString[epochtime, $DateStringFormat]"), - "DateString[format_?(VectorQ[#1, StringQ]&)]": ( - "DateString[DateList[], format]" + rules = {"DateDifference[date1_, date2_]": 'DateDifference[date1, date2, "Day"]'} + + messages = { + "date": "Argument `1` cannot be interpreted as a date.", + "inc": ( + "Argument `1` is not a time increment or " "a list of time increments." ), - "DateString[epochtime_]": "DateString[epochtime, $DateStringFormat]", } attributes = ("ReadProtected",) - def apply(self, epochtime, form, evaluation): - "DateString[epochtime_, form_]" - datelist = self.to_datelist(epochtime, evaluation) + summary_text = "find the difference in days, weeks, etc. between two dates" - if datelist is None: - return + def apply(self, date1, date2, units, evaluation): + "DateDifference[date1_, date2_, units_]" - date = _Date(datelist=datelist) + # Process dates + pydate1, pydate2 = date1.to_python(), date2.to_python() - pyform = form.to_python() - if not isinstance(pyform, list): - pyform = [pyform] + if isinstance(pydate1, list): # Date List + idate = _Date(datelist=pydate1) + elif isinstance(pydate1, (float, int)): # Absolute Time + idate = _Date(absolute=pydate1) + elif isinstance(pydate1, str): # Date string + idate = _Date(datestr=pydate2.strip('"')) + else: + evaluation.message("DateDifference", "date", date1) + return - pyform = [x.strip('"') for x in pyform] + if isinstance(pydate2, list): # Date List + fdate = _Date(datelist=pydate2) + elif isinstance(pydate2, (int, float)): # Absolute Time + fdate = _Date(absolute=pydate2) + elif isinstance(pydate1, str): # Date string + fdate = _Date(datestr=pydate2.strip('"')) + else: + evaluation.message("DateDifference", "date", date2) + return - if not all(isinstance(f, str) for f in pyform): - evaluation.message("DateString", "fmt", form) + try: + tdelta = fdate.date - idate.date + except OverflowError: + evaluation.message("General", "ovf") return - datestrs = [] - for p in pyform: - if str(p) in DATE_STRING_FORMATS.keys(): - # FIXME: Years 1900 before raise an error - tmp = date.date.strftime(DATE_STRING_FORMATS[p]) - if str(p).endswith("Short") and str(p) != "YearShort": - if str(p) == "DateTimeShort": - tmp = tmp.split(" ") - tmp = " ".join([s.lstrip("0") for s in tmp[:-1]] + [tmp[-1]]) - else: - tmp = " ".join([s.lstrip("0") for s in tmp.split(" ")]) + # Process Units + pyunits = units.to_python() + if isinstance(pyunits, str): + pyunits = [str(pyunits.strip('"'))] + elif isinstance(pyunits, list) and all(isinstance(p, str) for p in pyunits): + pyunits = [p.strip('"') for p in pyunits] + + if not all(p in TIME_INCREMENTS.keys() for p in pyunits): + evaluation.message("DateDifference", "inc", units) + + def intdiv(a, b, flag=True): + "exact integer division where possible" + if flag: + if a % b == 0: + return a // b + else: + return a / b else: - tmp = str(p) + return a // b - datestrs.append(tmp) + if not isinstance(pyunits, list): + pyunits = [pyunits] - return from_python("".join(datestrs)) + # Why doesn't this work? + # pyunits = pyunits.sort(key=TIME_INCREMENTS.get, reverse=True) + + pyunits = [(a, TIME_INCREMENTS.get(a)) for a in pyunits] + pyunits.sort(key=lambda a: a[1], reverse=True) + pyunits = [a[0] for a in pyunits] + + seconds = int(total_seconds(tdelta)) + + result = [] + flag = False + for i, unit in enumerate(pyunits): + if i + 1 == len(pyunits): + flag = True + + if unit == "Year": + result.append([intdiv(seconds, 365 * 24 * 60 * 60, flag), "Year"]) + seconds = seconds % (365 * 24 * 60 * 60) + if unit == "Quarter": + result.append([intdiv(seconds, 365 * 6 * 60 * 60, flag), "Quarter"]) + seconds = seconds % (365 * 6 * 60 * 60) + if unit == "Month": + result.append([intdiv(seconds, 365 * 2 * 60 * 60, flag), "Month"]) + seconds = seconds % (365 * 2 * 60 * 60) + if unit == "Week": + result.append([intdiv(seconds, 7 * 24 * 60 * 60, flag), "Week"]) + seconds = seconds % (7 * 24 * 60 * 60) + if unit == "Day": + result.append([intdiv(seconds, 24 * 60 * 60, flag), "Day"]) + seconds = seconds % (24 * 60 * 60) + if unit == "Hour": + result.append([intdiv(seconds, 60 * 60, flag), "Hour"]) + seconds = seconds % (60 * 60) + if unit == "Minute": + result.append([intdiv(seconds, 60, flag), "Minute"]) + seconds = seconds % 60 + if unit == "Second": + result.append( + [intdiv(seconds + total_seconds(tdelta) % 1, 1, flag), "Second"] + ) + + if len(result) == 1: + if pyunits[0] == "Day": + return from_python(result[0][0]) + return from_python(result[0]) + return from_python(result) class DateObject(_DateFormat): @@ -628,30 +574,37 @@ class DateObject(_DateFormat): = [...] """ + fmt_keywords = { + "Year": 0, + "Month": 1, + "Day": 2, + "Hour": 3, + "Minute": 4, + "Second": 5, + } + + granularities = [ + Symbol(s) + for s in ["Eternity", "Year", "Month", "Day", "Hour", "Minute", "Instant"] + ] + messages = { "notz": "Argument `1` in DateObject is not a recognized TimeZone specification.", } + options = { "TimeZone": "Automatic", "CalendarType": "Automatic", "DateFormat": "Automatic", } - granularities = [ - Symbol(s) - for s in ["Eternity", "Year", "Month", "Day", "Hour", "Minute", "Instant"] - ] rules = { "DateObject[]": "DateObject[AbsoluteTime[]]", } - fmt_keywords = { - "Year": 0, - "Month": 1, - "Day": 2, - "Hour": 3, - "Minute": 4, - "Second": 5, - } + + summary_text = ( + " an object representing a date of any granularity (year, hour, instant, ...)" + ) def apply_any(self, args, evaluation, options): "DateObject[args_, OptionsPattern[]]" @@ -725,248 +678,22 @@ def apply_makeboxes(self, datetime, gran, cal, tz, fmt, evaluation): return Expression("RowBox", Expression("List", "[", fmtds, " GTM", tz, "]")) -class AbsoluteTime(_DateFormat): - """ -
-
'AbsoluteTime[]' -
gives the local time in seconds since epoch January 1, 1900, in your time zone. - -
'AbsoluteTime[{$y$, $m$, $d$, $h$, $m$, $s$}]' -
gives the absolute time specification corresponding to a date list. - -
'AbsoluteTime["$string$"]' -
gives the absolute time specification for a given date string. - -
'AbsoluteTime[{"$string$",{$e1$, $e2$, ...}}]' -
takgs the date string to contain the elements "$ei$". -
- - >> AbsoluteTime[] - = ... - - >> AbsoluteTime[{2000}] - = 3155673600 - - >> AbsoluteTime[{"01/02/03", {"Day", "Month", "YearShort"}}] - = 3253046400 - - >> AbsoluteTime["6 June 1991"] - = 2885155200 - - >> AbsoluteTime[{"6-6-91", {"Day", "Month", "YearShort"}}] - = 2885155200 - - ## Mathematica Bug - Mathics gets it right - #> AbsoluteTime[1000] - = 1000 - """ - - abstract = "absolute time in seconds" - - def apply_now(self, evaluation): - "AbsoluteTime[]" - - return from_python(total_seconds(datetime.now() - EPOCH_START)) - - def apply_spec(self, epochtime, evaluation): - "AbsoluteTime[epochtime_]" - - datelist = self.to_datelist(epochtime, evaluation) - - if datelist is None: - return - - date = _Date(datelist=datelist) - tdelta = date.date - EPOCH_START - if tdelta.microseconds == 0: - return from_python(int(total_seconds(tdelta))) - return from_python(total_seconds(tdelta)) - - -class SystemTimeZone(Predefined): - """ -
-
'$SystemTimeZone' -
gives the current time zone for the computer system on which Mathics is being run. -
- - >> $SystemTimeZone - = ... - """ - - name = "$SystemTimeZone" - value = Real(-time.timezone / 3600.0) - - def evaluate(self, evaluation): - return self.value - - -class Now(Predefined): - """ -
-
'Now' -
gives the current time on the system. -
- - >> Now - = ... - """ - - def evaluate(self, evaluation): - return Expression("DateObject").evaluate(evaluation) - - -class TimeZone(Predefined): - """ -
-
'$TimeZone' -
gives the current time zone to assume for dates and times. -
- - >> $TimeZone - = ... - """ - - name = "$TimeZone" - value = SystemTimeZone.value.copy() - attributes = ("Unprotected",) - - rules = { - "$TimeZone": str(value), - } - - def apply(self, lhs, rhs, evaluation): - "lhs_ = rhs_" - - self.assign(lhs, rhs, evaluation) - return rhs - - def evaluate(self, evaluation) -> Real: - return self.value - - -class TimeUsed(Builtin): - """ -
-
'TimeUsed[]' -
returns the total CPU time used for this session, in seconds. -
- - >> TimeUsed[] - = ... - """ - - def apply(self, evaluation): - "TimeUsed[]" - # time.process_time() is better than - # time.clock(). See https://bugs.python.org/issue31803 - return Real(time.process_time()) - - -class SessionTime(Builtin): - """ -
-
'SessionTime[]' -
returns the total time in seconds since this session started. -
- - >> SessionTime[] - = ... - """ - - def apply(self, evaluation): - "SessionTime[]" - return Real(time.time() - START_TIME) - - -class Pause(Builtin): - """ -
-
'Pause[n]' -
pauses for $n$ seconds. -
- - >> Pause[0.5] - """ - - messages = { - "numnm": ( - "Non-negative machine-sized number expected at " "position 1 in `1`." - ), - } - - def apply(self, n, evaluation): - "Pause[n_]" - sleeptime = n.to_python() - if not isinstance(sleeptime, (int, float)) or sleeptime < 0: - evaluation.message("Pause", "numnm", Expression("Pause", n)) - return - - time.sleep(sleeptime) - return Symbol("Null") - - -class _Date: - def __init__(self, datelist=[], absolute=None, datestr=None): - datelist += [1900, 1, 1, 0, 0, 0.0][len(datelist) :] - self.date = datetime( - datelist[0], - datelist[1], - datelist[2], - datelist[3], - datelist[4], - int(datelist[5]), - int(1e6 * (datelist[5] % 1.0)), - ) - if absolute is not None: - self.date += timedelta(seconds=absolute) - if datestr is not None: - if absolute is not None: - raise ValueError - self.date = dateutil.parser.parse(datestr) - - def addself(self, timevec): - years = self.date.year + timevec[0] + int((self.date.month + timevec[1]) / 12) - months = (self.date.month + timevec[1]) % 12 - if months == 0: - months += 12 - years -= 1 - self.date = datetime( - years, - months, - self.date.day, - self.date.hour, - self.date.minute, - self.date.second, - ) - tdelta = timedelta( - days=timevec[2], hours=timevec[3], minutes=timevec[4], seconds=timevec[5] - ) - self.date += tdelta - - def to_list(self): - return [ - self.date.year, - self.date.month, - self.date.day, - self.date.hour, - self.date.minute, - self.date.second + 1e-6 * self.date.microsecond, - ] - - class DatePlus(Builtin): """
-
'DatePlus[$date$, $n$]' +
'DatePlus[$date$, $n$]'
finds the date $n$ days after $date$. -
'DatePlus[$date$, {$n$, "$unit$"}]' + +
'DatePlus[$date$, {$n$, "$unit$"}]'
finds the date $n$ units after $date$. -
'DatePlus[$date$, {{$n1$, "$unit1$"}, {$n2$, "$unit2$"}, ...}]' + +
'DatePlus[$date$, {{$n1$, "$unit1$"}, {$n2$, "$unit2$"}, ...}]'
finds the date which is $n_i$ specified units after $date$. -
'DatePlus[$n$]' + +
'DatePlus[$n$]'
finds the date $n$ days after the current date. -
'DatePlus[$offset$]' + +
'DatePlus[$offset$]'
finds the date which is offset from the current date.
@@ -979,7 +706,7 @@ class DatePlus(Builtin): = {2010, 4, 3} """ - rules = {"DatePlus[n_]": "DatePlus[Take[DateList[], 3], n]"} + attributes = ("ReadProtected",) messages = { "date": "Argument `1` cannot be interpreted as a date.", @@ -988,7 +715,9 @@ class DatePlus(Builtin): ), } - attributes = ("ReadProtected",) + rules = {"DatePlus[n_]": "DatePlus[Take[DateList[], 3], n]"} + + summary_text = "add or subtract days, weeks, etc. in a date list or string" def apply(self, date, off, evaluation): "DatePlus[date_, off_]" @@ -1041,152 +770,206 @@ def apply(self, date, off, evaluation): return result -class DateDifference(Builtin): +class DateList(_DateFormat): """
-
'DateDifference[$date1$, $date2$]' -
returns the difference between $date1$ and $date2$ in days. -
'DateDifference[$date1$, $date2$, $unit$]' -
returns the difference in the specified $unit$. -
'DateDifference[$date1$, $date2$, {$unit1$, $unit2$, ...}]' -
represents the difference as a list of integer multiples of - each $unit$, with any remainder expressed in the smallest unit. +
'DateList[]' +
returns the current local time in the form {$year$, $month$, $day$, $hour$, $minute$, $second$}. + +
'DateList[$time$]' +
returns a formatted date for the number of seconds $time$ since epoch Jan 1 1900. + +
'DateList[{$y$, $m$, $d$, $h$, $m$, $s$}]' +
converts an incomplete date list to the standard representation.
- >> DateDifference[{2042, 1, 4}, {2057, 1, 1}] - = 5476 + >> DateList[0] + = {1900, 1, 1, 0, 0, 0.} - >> DateDifference[{1936, 8, 14}, {2000, 12, 1}, "Year"] - = {64.3425, Year} + >> DateList[3155673600] + = {2000, 1, 1, 0, 0, 0.} - >> DateDifference[{2010, 6, 1}, {2015, 1, 1}, "Hour"] - = {40200, Hour} + >> DateList[{2003, 5, 0.5, 0.1, 0.767}] + = {2003, 4, 30, 12, 6, 46.02} - >> DateDifference[{2003, 8, 11}, {2003, 10, 19}, {"Week", "Day"}] - = {{9, Week}, {6, Day}} + >> DateList[{2012, 1, 300., 10}] + = {2012, 10, 26, 10, 0, 0.} + + >> DateList["31/10/1991"] + = {1991, 10, 31, 0, 0, 0.} + + >> DateList["1/10/1991"] + : The interpretation of 1/10/1991 is ambiguous. + = {1991, 1, 10, 0, 0, 0.} + + #> DateList["7/8/9"] + : The interpretation of 7/8/9 is ambiguous. + = {2009, 7, 8, 0, 0, 0.} + + >> DateList[{"31/10/91", {"Day", "Month", "YearShort"}}] + = {1991, 10, 31, 0, 0, 0.} + + >> DateList[{"31 10/91", {"Day", " ", "Month", "/", "YearShort"}}] + = {1991, 10, 31, 0, 0, 0.} + + + If not specified, the current year assumed + >> DateList[{"5/18", {"Month", "Day"}}] + = {..., 5, 18, 0, 0, 0.} """ - # FIXME: Since timedelta doesnt use large time units (years, months etc) - # this method can be innacuarate. The example below gives fractional Days - # (20.1666666667 not 20). + # TODO: Somehow check that the current year is correct + + rules = { + "DateList[]": "DateList[AbsoluteTime[]]", + 'DateList["02/27/20/13"]': 'Import[Uncompress["eJxTyigpKSi20tfPzE0v1qvITk7RS87P1QfizORi/czi/HgLMwNDvYK8dCUATpsOzQ=="]]', + } + + summary_text = "date elements as numbers in {y,m,d,h,m,s} format" + + def apply(self, epochtime, evaluation): + "%(name)s[epochtime_]" + datelist = self.to_datelist(epochtime, evaluation) + + if datelist is None: + return + + return Expression("List", *datelist) + +class DateString(_DateFormat): """ - >> DateDifference[{2000, 6, 15}, {2001, 9, 4}, {"Month", "Day"}] - = {{14, "Month"}, {20, "Day"}} +
+
'DateString[]' +
returns the current local time and date as a string. + +
'DateString[$elem$]' +
returns the time formatted according to $elems$. + +
'DateString[{$e1$, $e2$, ...}]' +
concatinates the time formatted according to elements $ei$. + +
'DateString[$time$]' +
returns the date string of an AbsoluteTime. + +
'DateString[{$y$, $m$, $d$, $h$, $m$, $s$}]' +
returns the date string of a date list specification. + +
'DateString[$string$]' +
returns the formatted date string of a date string specification. + +
'DateString[$spec$, $elems$]' +
formats the time in turns of $elems$. Both $spec$ and $elems$ can take any of the above formats. +
+ + The current date and time: + >> DateString[]; + + >> DateString[{1991, 10, 31, 0, 0}, {"Day", " ", "MonthName", " ", "Year"}] + = 31 October 1991 + + >> DateString[{2007, 4, 15, 0}] + = Sun 15 Apr 2007 00:00:00 + + >> DateString[{1979, 3, 14}, {"DayName", " ", "Month", "-", "YearShort"}] + = Wednesday 03-79 + + Non-integer values are accepted too: + >> DateString[{1991, 6, 6.5}] + = Thu 6 Jun 1991 12:00:00 + + ## Check Leading 0 + #> DateString[{1979, 3, 14}, {"DayName", " ", "MonthShort", "-", "YearShort"}] + = Wednesday 3-79 + + #> DateString[{"DayName", " ", "Month", "/", "YearShort"}] + = ... + + ## Assumed separators + #> DateString[{"06/06/1991", {"Month", "Day", "Year"}}] + = Thu 6 Jun 1991 00:00:00 + + ## Specified separators + #> DateString[{"06/06/1991", {"Month", "/", "Day", "/", "Year"}}] + = Thu 6 Jun 1991 00:00:00 + """ - rules = {"DateDifference[date1_, date2_]": 'DateDifference[date1, date2, "Day"]'} + attributes = ("ReadProtected",) - messages = { - "date": "Argument `1` cannot be interpreted as a date.", - "inc": ( - "Argument `1` is not a time increment or " "a list of time increments." + rules = { + "DateString[]": "DateString[DateList[], $DateStringFormat]", + "DateString[epochtime_?(VectorQ[#1, NumericQ]&)]": ( + "DateString[epochtime, $DateStringFormat]" ), + "DateString[epochtime_?NumericQ]": ("DateString[epochtime, $DateStringFormat]"), + "DateString[format_?(VectorQ[#1, StringQ]&)]": ( + "DateString[DateList[], format]" + ), + "DateString[epochtime_]": "DateString[epochtime, $DateStringFormat]", } - attributes = ("ReadProtected",) + summary_text = "current or specified date as a string in many possible formats" - def apply(self, date1, date2, units, evaluation): - "DateDifference[date1_, date2_, units_]" - - # Process dates - pydate1, pydate2 = date1.to_python(), date2.to_python() + def apply(self, epochtime, form, evaluation): + "DateString[epochtime_, form_]" + datelist = self.to_datelist(epochtime, evaluation) - if isinstance(pydate1, list): # Date List - idate = _Date(datelist=pydate1) - elif isinstance(pydate1, (float, int)): # Absolute Time - idate = _Date(absolute=pydate1) - elif isinstance(pydate1, str): # Date string - idate = _Date(datestr=pydate2.strip('"')) - else: - evaluation.message("DateDifference", "date", date1) + if datelist is None: return - if isinstance(pydate2, list): # Date List - fdate = _Date(datelist=pydate2) - elif isinstance(pydate2, (int, float)): # Absolute Time - fdate = _Date(absolute=pydate2) - elif isinstance(pydate1, str): # Date string - fdate = _Date(datestr=pydate2.strip('"')) - else: - evaluation.message("DateDifference", "date", date2) - return + date = _Date(datelist=datelist) - try: - tdelta = fdate.date - idate.date - except OverflowError: - evaluation.message("General", "ovf") - return + pyform = form.to_python() + if not isinstance(pyform, list): + pyform = [pyform] - # Process Units - pyunits = units.to_python() - if isinstance(pyunits, str): - pyunits = [str(pyunits.strip('"'))] - elif isinstance(pyunits, list) and all(isinstance(p, str) for p in pyunits): - pyunits = [p.strip('"') for p in pyunits] + pyform = [x.strip('"') for x in pyform] - if not all(p in TIME_INCREMENTS.keys() for p in pyunits): - evaluation.message("DateDifference", "inc", units) + if not all(isinstance(f, str) for f in pyform): + evaluation.message("DateString", "fmt", form) + return - def intdiv(a, b, flag=True): - "exact integer division where possible" - if flag: - if a % b == 0: - return a // b - else: - return a / b + datestrs = [] + for p in pyform: + if str(p) in DATE_STRING_FORMATS.keys(): + # FIXME: Years 1900 before raise an error + tmp = date.date.strftime(DATE_STRING_FORMATS[p]) + if str(p).endswith("Short") and str(p) != "YearShort": + if str(p) == "DateTimeShort": + tmp = tmp.split(" ") + tmp = " ".join([s.lstrip("0") for s in tmp[:-1]] + [tmp[-1]]) + else: + tmp = " ".join([s.lstrip("0") for s in tmp.split(" ")]) else: - return a // b + tmp = str(p) - if not isinstance(pyunits, list): - pyunits = [pyunits] + datestrs.append(tmp) - # Why doesn't this work? - # pyunits = pyunits.sort(key=TIME_INCREMENTS.get, reverse=True) + return from_python("".join(datestrs)) - pyunits = [(a, TIME_INCREMENTS.get(a)) for a in pyunits] - pyunits.sort(key=lambda a: a[1], reverse=True) - pyunits = [a[0] for a in pyunits] - seconds = int(total_seconds(tdelta)) +class DateStringFormat(Predefined): + """ +
+
'$DateStringFormat' +
gives the format used for dates generated by 'DateString'. +
- result = [] - flag = False - for i, unit in enumerate(pyunits): - if i + 1 == len(pyunits): - flag = True + >> $DateStringFormat + = {DateTimeShort} + """ - if unit == "Year": - result.append([intdiv(seconds, 365 * 24 * 60 * 60, flag), "Year"]) - seconds = seconds % (365 * 24 * 60 * 60) - if unit == "Quarter": - result.append([intdiv(seconds, 365 * 6 * 60 * 60, flag), "Quarter"]) - seconds = seconds % (365 * 6 * 60 * 60) - if unit == "Month": - result.append([intdiv(seconds, 365 * 2 * 60 * 60, flag), "Month"]) - seconds = seconds % (365 * 2 * 60 * 60) - if unit == "Week": - result.append([intdiv(seconds, 7 * 24 * 60 * 60, flag), "Week"]) - seconds = seconds % (7 * 24 * 60 * 60) - if unit == "Day": - result.append([intdiv(seconds, 24 * 60 * 60, flag), "Day"]) - seconds = seconds % (24 * 60 * 60) - if unit == "Hour": - result.append([intdiv(seconds, 60 * 60, flag), "Hour"]) - seconds = seconds % (60 * 60) - if unit == "Minute": - result.append([intdiv(seconds, 60, flag), "Minute"]) - seconds = seconds % 60 - if unit == "Second": - result.append( - [intdiv(seconds + total_seconds(tdelta) % 1, 1, flag), "Second"] - ) + name = "$DateStringFormat" + + value = "DateTimeShort" + + summary_text = "default date string format" + + # TODO: Methods to change this - if len(result) == 1: - if pyunits[0] == "Day": - return from_python(result[0][0]) - return from_python(result[0]) - return from_python(result) + def evaluate(self, evaluation): + return Expression("List", String(self.value)) class EasterSunday(Builtin): # Calendar`EasterSunday @@ -1203,6 +986,8 @@ class EasterSunday(Builtin): # Calendar`EasterSunday = {2030, 4, 21} """ + summary_text = "Find the date of Easter Sunday for a given year" + def apply(self, year, evaluation): "EasterSunday[year_Integer]" y = year.get_int_value() @@ -1224,3 +1009,268 @@ def apply(self, year, evaluation): day = ((h + l - 7 * m + 114) % 31) + 1 return Expression("List", year, Integer(month), Integer(day)) + + +class Pause(Builtin): + """ +
+
'Pause[n]' +
pauses for $n$ seconds. +
+ + >> Pause[0.5] + """ + + messages = { + "numnm": ( + "Non-negative machine-sized number expected at " "position 1 in `1`." + ), + } + + summary_text = "pauses for aseconds." + + def apply(self, n, evaluation): + "Pause[n_]" + sleeptime = n.to_python() + if not isinstance(sleeptime, (int, float)) or sleeptime < 0: + evaluation.message("Pause", "numnm", Expression("Pause", n)) + return + + time.sleep(sleeptime) + return Symbol("Null") + + +class SystemTimeZone(Predefined): + """ +
+
'$SystemTimeZone' +
gives the current time zone for the computer system on which Mathics is being run. +
+ + >> $SystemTimeZone + = ... + """ + + name = "$SystemTimeZone" + value = Real(-time.timezone / 3600.0) + + summary_text = "time zone used by your system" + + def evaluate(self, evaluation): + return self.value + + +class Now(Predefined): + """ +
+
'Now' +
gives the current time on the system. +
+ + >> Now + = ... + """ + + summary_text = "current date and time" + + def evaluate(self, evaluation): + return Expression("DateObject").evaluate(evaluation) + + +if sys.platform != "win32" and ("Pyston" not in sys.version): + + class TimeConstrained(Builtin): + """ +
+
'TimeConstrained[$expr$, $t$]' +
'evaluates $expr$, stopping after $t$ seconds.' + +
'TimeConstrained[$expr$, $t$, $failexpr$]' +
'returns $failexpr$ if the time constraint is not met.' +
+ + Possible issues: for certain time-consuming functions (like simplify) + which are based on sympy or other libraries, it is possible that + the evaluation continues after the timeout. However, at the end of the evaluation, the function will return $\\$Aborted$ and the results will not affect + the state of the mathics kernel. + + """ + + # FIXME: these tests sometimes cause SEGVs which probably means + # that TimeConstraint has bugs. + + # Consider testing via unit tests. + # >> TimeConstrained[Integrate[Sin[x]^1000000,x],1] + # = $Aborted + + # >> TimeConstrained[Integrate[Sin[x]^1000000,x], 1, Integrate[Cos[x],x]] + # = Sin[x] + + # >> s=TimeConstrained[Integrate[Sin[x] ^ 3, x], a] + # : Number of seconds a is not a positive machine-sized number or Infinity. + # = TimeConstrained[Integrate[Sin[x] ^ 3, x], a] + + # >> a=1; s + # = Cos[x] (-5 + Cos[2 x]) / 6 + + attributes = ("HoldAll",) + messages = { + "timc": "Number of seconds `1` is not a positive machine-sized number or Infinity.", + } + + summary_text = "run a command for at most a specified time" + + def apply_2(self, expr, t, evaluation): + "TimeConstrained[expr_, t_]" + return self.apply_3(expr, t, SymbolAborted, evaluation) + + def apply_3(self, expr, t, failexpr, evaluation): + "TimeConstrained[expr_, t_, failexpr_]" + t = t.evaluate(evaluation) + if not t.is_numeric(): + evaluation.message("TimeConstrained", "timc", t) + return + try: + t = float(t.to_python()) + evaluation.timeout_queue.append((t, datetime.now().timestamp())) + request = lambda: expr.evaluate(evaluation) + res = run_with_timeout_and_stack(request, t, evaluation) + except TimeoutInterrupt: + evaluation.timeout_queue.pop() + return failexpr.evaluate(evaluation) + except: + evaluation.timeout_queue.pop() + raise + evaluation.timeout_queue.pop() + return res + + +class TimeZone(Predefined): + """ +
+
'$TimeZone' +
gives the current time zone to assume for dates and times. +
+ + >> $TimeZone + = ... + """ + + attributes = ("Unprotected",) + name = "$TimeZone" + value = SystemTimeZone.value.copy() + + rules = { + "$TimeZone": str(value), + } + + summary_text = "resettable default time zone" + + def apply(self, lhs, rhs, evaluation): + "lhs_ = rhs_" + + self.assign(lhs, rhs, evaluation) + return rhs + + def evaluate(self, evaluation) -> Real: + return self.value + + +class TimeUsed(Builtin): + """ +
+
'TimeUsed[]' +
returns the total CPU time used for this session, in seconds. +
+ + >> TimeUsed[] + = ... + """ + + summary_text = ( + "the total number of seconds of CPU time in the current Mathics session" + ) + + def apply(self, evaluation): + "TimeUsed[]" + # time.process_time() is better than + # time.clock(). See https://bugs.python.org/issue31803 + return Real(time.process_time()) + + +class Timing(Builtin): + """ +
+
'Timing[$expr$]' +
measures the processor time taken to evaluate $expr$. + It returns a list containing the measured time in seconds and the result of the evaluation. +
+ + >> Timing[50!] + = {..., 30414093201713378043612608166064768844377641568960512000000000000} + >> Attributes[Timing] + = {HoldAll, Protected} + """ + + attributes = ("HoldAll",) + + summary_text = "CPU time to run a Mathics command" + + def apply(self, expr, evaluation): + "Timing[expr_]" + + start = time.process_time() + result = expr.evaluate(evaluation) + stop = time.process_time() + return Expression("List", Real(stop - start), result) + + +class SessionTime(Builtin): + """ +
+
'SessionTime[]' +
returns the total time in seconds since this session started. +
+ + >> SessionTime[] + = ... + """ + + summary_text = ( + "total elapsed time in seconds since the beginning of your Mathics session" + ) + + def apply(self, evaluation): + "SessionTime[]" + return Real(time.time() - START_TIME) + + +class TimeRemaining(Builtin): + """ +
+
'TimeRemaining[]' +
Gives the number of seconds remaining until the earliest enclosing 'TimeConstrained' will request the current computation to stop. +
'TimeConstrained[$expr$, $t$, $failexpr$]' +
returns $failexpr$ if the time constraint is not met. +
+ + If TimeConstrained is called out of a TimeConstrained expression, returns `Infinity` + >> TimeRemaining[] + = Infinity + + X> TimeConstrained[1+2; Print[TimeRemaining[]], 0.9] + | 0.899318 + + """ + + summary_text = "time before a time constraint in a running program" + + def apply(self, evaluation): + "TimeRemaining[]" + if len(evaluation.timeout_queue) > 0: + t, start_time = evaluation.timeout_queue[-1] + curr_time = datetime.now().timestamp() + deltat = t + start_time - curr_time + return Real(deltat) + else: + return SymbolInfinity From acdf5d0da25ae349d791c801e43233fad3867bce Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 7 Aug 2021 00:36:59 -0400 Subject: [PATCH 017/193] Add summary text for Procedural Programming fns --- mathics/builtin/datentime.py | 2 +- mathics/builtin/procedural.py | 813 ++++++++++++++++++---------------- 2 files changed, 430 insertions(+), 385 deletions(-) diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index f964d468e..7e3571466 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -1027,7 +1027,7 @@ class Pause(Builtin): ), } - summary_text = "pauses for aseconds." + summary_text = "pauses for a number of seconds" def apply(self, n, evaluation): "Pause[n_]" diff --git a/mathics/builtin/procedural.py b/mathics/builtin/procedural.py index 3e1459ba9..e0bf6c2f7 100644 --- a/mathics/builtin/procedural.py +++ b/mathics/builtin/procedural.py @@ -32,6 +32,107 @@ from mathics.builtin.patterns import match +class Abort(Builtin): + """ +
+
'Abort[]' +
aborts an evaluation completely and returns '$Aborted'. +
+ >> Print["a"]; Abort[]; Print["b"] + | a + = $Aborted + """ + + summary_text = "generate an abort" + + def apply(self, evaluation): + "Abort[]" + + raise AbortInterrupt + + +class Break(Builtin): + """ +
+
'Break[]' +
exits a 'For', 'While', or 'Do' loop. +
+ >> n = 0; + >> While[True, If[n>10, Break[]]; n=n+1] + >> n + = 11 + """ + + messages = { + "nofwd": "No enclosing For, While, or Do found for Break[].", + } + + summary_text = "exit a 'For', 'While', or 'Do' loop" + + def apply(self, evaluation): + "Break[]" + + raise BreakInterrupt + + +class Catch(Builtin): + """ +
+
'Catch[$expr$]' +
returns the argument of the first 'Throw' generated in the evaluation of $expr$. + +
'Catch[$expr$, $form$]' +
returns value from the first 'Throw[$value$, $tag$]' for which $form$ matches $tag$. + +
'Catch[$expr$, $form$, $f$]' +
returns $f$[$value$, $tag$]. +
+ + Exit to the enclosing 'Catch' as soon as 'Throw' is evaluated: + >> Catch[r; s; Throw[t]; u; v] + = t + + Define a function that can "throw an exception": + >> f[x_] := If[x > 12, Throw[overflow], x!] + + The result of 'Catch' is just what is thrown by 'Throw': + >> Catch[f[1] + f[15]] + = overflow + >> Catch[f[1] + f[4]] + = 25 + + #> Clear[f] + """ + + attributes = ("HoldAll",) + + summary_text = "Handles an exception raised by a 'Throw'" + + def apply_expr(self, expr, evaluation): + "Catch[expr_]" + try: + ret = expr.evaluate(evaluation) + except WLThrowInterrupt as e: + return e.value + return ret + + def apply_with_form_and_fn(self, expr, form, f, evaluation): + "Catch[expr_, form_, f__:Identity]" + try: + ret = expr.evaluate(evaluation) + except WLThrowInterrupt as e: + # TODO: check that form match tag. + # otherwise, re-raise the exception + match = Expression("MatchQ", e.tag, form).evaluate(evaluation) + if match.is_true(): + return Expression(f, e.value) + else: + # A plain raise hide, this path and preserves the traceback + # of the call that was originally given. + raise + return ret + + class CompoundExpression(BinaryOperator): """
@@ -79,9 +180,11 @@ class CompoundExpression(BinaryOperator): #> Clear[x]; Clear[z] """ + attributes = ("HoldAll", "ReadProtected") operator = ";" precedence = 10 - attributes = ("HoldAll", "ReadProtected") + + summary_text = "execute expressions in sequence" def apply(self, expr, evaluation): "CompoundExpression[expr___]" @@ -100,182 +203,52 @@ def apply(self, expr, evaluation): return result -class If(Builtin): - """ -
-
'If[$cond$, $pos$, $neg$]' -
returns $pos$ if $cond$ evaluates to 'True', and $neg$ if it evaluates to 'False'. - -
'If[$cond$, $pos$, $neg$, $other$]' -
returns $other$ if $cond$ evaluates to neither 'True' nor 'False'. - -
'If[$cond$, $pos$]' -
returns 'Null' if $cond$ evaluates to 'False'. -
- - >> If[1<2, a, b] - = a - If the second branch is not specified, 'Null' is taken: - >> If[1<2, a] - = a - >> If[False, a] //FullForm - = Null - - You might use comments (inside '(*' and '*)') to make the branches of 'If' more readable: - >> If[a, (*then*) b, (*else*) c]; - """ - - attributes = ("HoldRest",) - - def apply_2(self, condition, t, evaluation): - "If[condition_, t_]" - - if condition == SymbolTrue: - return t.evaluate(evaluation) - elif condition == SymbolFalse: - return Symbol("Null") - - def apply_3(self, condition, t, f, evaluation): - "If[condition_, t_, f_]" - - if condition == SymbolTrue: - return t.evaluate(evaluation) - elif condition == SymbolFalse: - return f.evaluate(evaluation) - - def apply_4(self, condition, t, f, u, evaluation): - "If[condition_, t_, f_, u_]" - - if condition == SymbolTrue: - return t.evaluate(evaluation) - elif condition == SymbolFalse: - return f.evaluate(evaluation) - else: - return u.evaluate(evaluation) - - -class Switch(Builtin): +class Continue(Builtin): """
-
'Switch[$expr$, $pattern1$, $value1$, $pattern2$, $value2$, ...]' -
yields the first $value$ for which $expr$ matches the corresponding $pattern$. +
'Continue[]' +
continues with the next iteration in a 'For', 'While', or 'Do' loop.
- >> Switch[2, 1, x, 2, y, 3, z] - = y - >> Switch[5, 1, x, 2, y] - = Switch[5, 1, x, 2, y] - >> Switch[5, 1, x, 2, a, _, b] - = b - >> Switch[2, 1] - : Switch called with 2 arguments. Switch must be called with an odd number of arguments. - = Switch[2, 1] - - #> a; Switch[b, b] - : Switch called with 2 arguments. Switch must be called with an odd number of arguments. - = Switch[b, b] - - ## Issue 531 - #> z = Switch[b, b]; - : Switch called with 2 arguments. Switch must be called with an odd number of arguments. - #> z - = Switch[b, b] + >> For[i=1, i<=8, i=i+1, If[Mod[i,2] == 0, Continue[]]; Print[i]] + | 1 + | 3 + | 5 + | 7 """ - attributes = ("HoldRest",) - messages = { - "argct": ( - "Switch called with `2` arguments. " - "Switch must be called with an odd number of arguments." - ), + "nofwd": "No enclosing For, While, or Do found for Continue[].", } - def apply(self, expr, rules, evaluation): - "Switch[expr_, rules___]" - - rules = rules.get_sequence() - if len(rules) % 2 != 0: - evaluation.message("Switch", "argct", "Switch", len(rules) + 1) - return - for pattern, value in zip(rules[::2], rules[1::2]): - if match(expr, pattern, evaluation): - return value.evaluate(evaluation) - # return unevaluated Switch when no pattern matches - - -class Which(Builtin): - """ -
-
'Which[$cond1$, $expr1$, $cond2$, $expr2$, ...]' -
yields $expr1$ if $cond1$ evaluates to 'True', $expr2$ if $cond2$ evaluates to 'True', etc. -
- - >> n = 5; - >> Which[n == 3, x, n == 5, y] - = y - >> f[x_] := Which[x < 0, -x, x == 0, 0, x > 0, x] - >> f[-3] - = 3 + summary_text = "continues with the next iteration in a 'For', 'While' or 'Do' loop" - #> Clear[f] + def apply(self, evaluation): + "Continue[]" - If no test yields 'True', 'Which' returns 'Null': - >> Which[False, a] + raise ContinueInterrupt - If a test does not evaluate to 'True' or 'False', evaluation stops - and a 'Which' expression containing the remaining cases is - returned: - >> Which[False, a, x, b, True, c] - = Which[x, b, True, c] - 'Which' must be called with an even number of arguments: - >> Which[a, b, c] - : Which called with 3 arguments. - = Which[a, b, c] +class Do(_IterationFunction): """ +
+
'Do[$expr$, {$max$}]' +
evaluates $expr$ $max$ times. - attributes = ("HoldAll",) +
'Do[$expr$, {$i$, $max$}]' +
evaluates $expr$ $max$ times, substituting $i$ in $expr$ with values from 1 to $max$. - def apply(self, items, evaluation): - "Which[items___]" +
'Do[$expr$, {$i$, $min$, $max$}]' +
starts with '$i$ = $max$'. - items = items.get_sequence() - nr_items = len(items) - if len(items) == 1: - evaluation.message("Which", "argctu", "Which") - return - elif len(items) % 2 == 1: - evaluation.message("Which", "argct", "Which", len(items)) - return - while items: - test, item = items[0], items[1] - test_result = test.evaluate(evaluation) - if test_result.is_true(): - return item.evaluate(evaluation) - elif test_result != SymbolFalse: - if len(items) == nr_items: - return None - return Expression("Which", *items) - items = items[2:] - return Symbol("Null") +
'Do[$expr$, {$i$, $min$, $max$, $step$}]' +
uses a step size of $step$. +
'Do[$expr$, {$i$, {$i1$, $i2$, ...}}]' +
uses values $i1$, $i2$, ... for $i$. -class Do(_IterationFunction): - """ -
-
'Do[$expr$, {$max$}]' -
evaluates $expr$ $max$ times. -
'Do[$expr$, {$i$, $max$}]' -
evaluates $expr$ $max$ times, substituting $i$ in $expr$ with values from 1 to $max$. -
'Do[$expr$, {$i$, $min$, $max$}]' -
starts with '$i$ = $max$'. -
'Do[$expr$, {$i$, $min$, $max$, $step$}]' -
uses a step size of $step$. -
'Do[$expr$, {$i$, {$i1$, $i2$, ...}}]' -
uses values $i1$, $i2$, ... for $i$. -
'Do[$expr$, {$i$, $imin$, $imax$}, {$j$, $jmin$, $jmax$}, ...]' -
evaluates $expr$ for each $j$ from $jmin$ to $jmax$, for each $i$ from $imin$ to $imax$, etc. +
'Do[$expr$, {$i$, $imin$, $imax$}, {$j$, $jmin$, $jmax$}, ...]' +
evaluates $expr$ for each $j$ from $jmin$ to $jmax$, for each $i$ from $imin$ to $imax$, etc.
>> Do[Print[i], {i, 2, 4}] | 2 @@ -300,6 +273,7 @@ class Do(_IterationFunction): """ allow_loopcontrol = True + summary_text = "evaluate an expression looping over a variable" def get_result(self, items): return Symbol("Null") @@ -308,12 +282,14 @@ def get_result(self, items): class For(Builtin): """
-
'For[$start$, $test$, $incr$, $body$]' -
evaluates $start$, and then iteratively $body$ and $incr$ as long as $test$ evaluates to 'True'. -
'For[$start$, $test$, $incr$]' -
evaluates only $incr$ and no $body$. -
'For[$start$, $test$]' -
runs the loop without any body. +
'For[$start$, $test$, $incr$, $body$]' +
evaluates $start$, and then iteratively $body$ and $incr$ as long as $test$ evaluates to 'True'. + +
'For[$start$, $test$, $incr$]' +
evaluates only $incr$ and no $body$. + +
'For[$start$, $test$]' +
runs the loop without any body.
Compute the factorial of 10 using 'For': @@ -335,6 +311,7 @@ class For(Builtin): rules = { "For[start_, test_, incr_]": "For[start, test, incr, Null]", } + summary_text = "a 'For' loop" def apply(self, start, test, incr, body, evaluation): "For[start_, test_, incr_, body_]" @@ -357,155 +334,69 @@ def apply(self, start, test, incr, body, evaluation): return Symbol("Null") -class While(Builtin): +class If(Builtin): """
-
'While[$test$, $body$]' -
evaluates $body$ as long as $test$ evaluates to 'True'. -
'While[$test$]' -
runs the loop without any body. -
- - Compute the GCD of two numbers: - >> {a, b} = {27, 6}; - >> While[b != 0, {a, b} = {b, Mod[a, b]}]; - >> a - = 3 - - #> i = 1; While[True, If[i^2 > 100, Return[i + 1], i++]] - = 12 - """ - - attributes = ("HoldAll",) - rules = { - "While[test_]": "While[test, Null]", - } - - def apply(self, test, body, evaluation): - "While[test_, body_]" - - while test.evaluate(evaluation).is_true(): - try: - evaluation.check_stopped() - body.evaluate(evaluation) - except ContinueInterrupt: - pass - except BreakInterrupt: - break - except ReturnInterrupt as e: - return e.expr - return Symbol("Null") - - -class Nest(Builtin): - """ -
-
'Nest[$f$, $expr$, $n$]' -
starting with $expr$, iteratively applies $f$ $n$ times and returns the final result. -
- - >> Nest[f, x, 3] - = f[f[f[x]]] - >> Nest[(1+#) ^ 2 &, x, 2] - = (1 + (1 + x) ^ 2) ^ 2 - """ - - def apply(self, f, expr, n, evaluation): - "Nest[f_, expr_, n_Integer]" - - n = n.get_int_value() - if n is None or n < 0: - return - result = expr - for k in range(n): - result = Expression(f, result).evaluate(evaluation) - return result +
'If[$cond$, $pos$, $neg$]' +
returns $pos$ if $cond$ evaluates to 'True', and $neg$ if it evaluates to 'False'. +
'If[$cond$, $pos$, $neg$, $other$]' +
returns $other$ if $cond$ evaluates to neither 'True' nor 'False'. -class NestList(Builtin): - """ -
-
'NestList[$f$, $expr$, $n$]' -
starting with $expr$, iteratively applies $f$ $n$ times and returns a list of all intermediate results. +
'If[$cond$, $pos$]' +
returns 'Null' if $cond$ evaluates to 'False'.
- >> NestList[f, x, 3] - = {x, f[x], f[f[x]], f[f[f[x]]]} - >> NestList[2 # &, 1, 8] - = {1, 2, 4, 8, 16, 32, 64, 128, 256} + >> If[1<2, a, b] + = a + If the second branch is not specified, 'Null' is taken: + >> If[1<2, a] + = a + >> If[False, a] //FullForm + = Null - ## TODO: improve this example when RandomChoice, PointSize, Axes->False are implemented - Chaos game rendition of the Sierpinski triangle: - >> vertices = {{0,0}, {1,0}, {.5, .5 Sqrt[3]}}; - >> points = NestList[.5(vertices[[ RandomInteger[{1,3}] ]] + #) &, {0.,0.}, 2000]; - >> Graphics[Point[points], ImageSize->Small] - = -Graphics- + You might use comments (inside '(*' and '*)') to make the branches of 'If' more readable: + >> If[a, (*then*) b, (*else*) c]; """ - def apply(self, f, expr, n, evaluation): - "NestList[f_, expr_, n_Integer]" - - n = n.get_int_value() - if n is None or n < 0: - return - - interm = expr - result = [interm] - - for k in range(n): - interm = Expression(f, interm).evaluate(evaluation) - result.append(interm) - - return from_python(result) + attributes = ("HoldRest",) + summary_text = "test if a condition is true, false, or of unknown truth value" + def apply_2(self, condition, t, evaluation): + "If[condition_, t_]" -class NestWhile(Builtin): - """ -
-
'NestWhile[$f$, $expr$, $test$]' -
applies a function $f$ repeatedly on an expression $expr$, until - applying $test$ on the result no longer yields 'True'. -
'NestWhile[$f$, $expr$, $test$, $m$]' -
supplies the last $m$ results to $test$ (default value: 1). -
'NestWhile[$f$, $expr$, $test$, All]' -
supplies all results gained so far to $test$. -
+ if condition == SymbolTrue: + return t.evaluate(evaluation) + elif condition == SymbolFalse: + return Symbol("Null") - Divide by 2 until the result is no longer an integer: - >> NestWhile[#/2&, 10000, IntegerQ] - = 625 / 2 - """ + def apply_3(self, condition, t, f, evaluation): + "If[condition_, t_, f_]" - rules = { - "NestWhile[f_, expr_, test_]": "NestWhile[f, expr, test, 1]", - } + if condition == SymbolTrue: + return t.evaluate(evaluation) + elif condition == SymbolFalse: + return f.evaluate(evaluation) - def apply(self, f, expr, test, m, evaluation): - "NestWhile[f_, expr_, test_, Pattern[m,_Integer|All]]" + def apply_4(self, condition, t, f, u, evaluation): + "If[condition_, t_, f_, u_]" - results = [expr] - while True: - if m.get_name() == "All": - test_leaves = results - else: - test_leaves = results[-m.value :] - test_expr = Expression(test, *test_leaves) - test_result = test_expr.evaluate(evaluation) - if test_result.is_true(): - next = Expression(f, results[-1]) - results.append(next.evaluate(evaluation)) - else: - break - return results[-1] + if condition == SymbolTrue: + return t.evaluate(evaluation) + elif condition == SymbolFalse: + return f.evaluate(evaluation) + else: + return u.evaluate(evaluation) class FixedPoint(Builtin): """
-
'FixedPoint[$f$, $expr$]' -
starting with $expr$, iteratively applies $f$ until the result no longer changes. -
'FixedPoint[$f$, $expr$, $n$]' -
performs at most $n$ iterations. The same that using $MaxIterations->n$ +
'FixedPoint[$f$, $expr$]' +
starting with $expr$, iteratively applies $f$ until the result no longer changes. + +
'FixedPoint[$f$, $expr$, $n$]' +
performs at most $n$ iterations. The same that using $MaxIterations->n$
>> FixedPoint[Cos, 1.0] @@ -528,6 +419,8 @@ class FixedPoint(Builtin): "SameTest": "Automatic", } + summary_text = "nest until a fixed point is reached returning the last expression" + def apply(self, f, expr, n, evaluation, options): "FixedPoint[f_, expr_, n_:DirectedInfinity[1], OptionsPattern[FixedPoint]]" if n == Expression("DirectedInfinity", 1): @@ -572,10 +465,11 @@ def apply(self, f, expr, n, evaluation, options): class FixedPointList(Builtin): """
-
'FixedPointList[$f$, $expr$]' -
starting with $expr$, iteratively applies $f$ until the result no longer changes, and returns a list of all intermediate results. -
'FixedPointList[$f$, $expr$, $n$]' -
performs at most $n$ iterations. +
'FixedPointList[$f$, $expr$]' +
starting with $expr$, iteratively applies $f$ until the result no longer changes, and returns a list of all intermediate results. + +
'FixedPointList[$f$, $expr$, $n$]' +
performs at most $n$ iterations.
>> FixedPointList[Cos, 1.0, 4] @@ -604,6 +498,8 @@ class FixedPointList(Builtin): = 0.739085 """ + summary_text = "nest until a fixed point is reached return a list " + def apply(self, f, expr, n, evaluation): "FixedPointList[f_, expr_, n_:DirectedInfinity[1]]" @@ -633,44 +529,138 @@ def apply(self, f, expr, n, evaluation): return from_python(result) -class Abort(Builtin): +class Interrupt(Builtin): """
-
'Abort[]' -
aborts an evaluation completely and returns '$Aborted'. +
'Interrupt[]' +
Interrupt an evaluation and returns '$Aborted'.
- >> Print["a"]; Abort[]; Print["b"] + >> Print["a"]; Interrupt[]; Print["b"] | a = $Aborted """ + summary_text = "interrupt evaluation and return '$Aborted'" + def apply(self, evaluation): - "Abort[]" + "Interrupt[]" raise AbortInterrupt -class Interrupt(Builtin): +class Nest(Builtin): """
-
'Interrupt[]' -
Interrupt an evaluation and returns '$Aborted'. +
'Nest[$f$, $expr$, $n$]' +
starting with $expr$, iteratively applies $f$ $n$ times and returns the final result.
- >> Print["a"]; Interrupt[]; Print["b"] - | a - = $Aborted + + >> Nest[f, x, 3] + = f[f[f[x]]] + >> Nest[(1+#) ^ 2 &, x, 2] + = (1 + (1 + x) ^ 2) ^ 2 """ - def apply(self, evaluation): - "Interrupt[]" + summary_text = "give the result of nesting a function" - raise AbortInterrupt + def apply(self, f, expr, n, evaluation): + "Nest[f_, expr_, n_Integer]" + + n = n.get_int_value() + if n is None or n < 0: + return + result = expr + for k in range(n): + result = Expression(f, result).evaluate(evaluation) + return result + + +class NestList(Builtin): + """ +
+
'NestList[$f$, $expr$, $n$]' +
starting with $expr$, iteratively applies $f$ $n$ times and returns a list of all intermediate results. +
+ + >> NestList[f, x, 3] + = {x, f[x], f[f[x]], f[f[f[x]]]} + >> NestList[2 # &, 1, 8] + = {1, 2, 4, 8, 16, 32, 64, 128, 256} + + ## TODO: improve this example when RandomChoice, PointSize, Axes->False are implemented + Chaos game rendition of the Sierpinski triangle: + >> vertices = {{0,0}, {1,0}, {.5, .5 Sqrt[3]}}; + >> points = NestList[.5(vertices[[ RandomInteger[{1,3}] ]] + #) &, {0.,0.}, 2000]; + >> Graphics[Point[points], ImageSize->Small] + = -Graphics- + """ + + summary_text = "successively nest a function" + + def apply(self, f, expr, n, evaluation): + "NestList[f_, expr_, n_Integer]" + + n = n.get_int_value() + if n is None or n < 0: + return + + interm = expr + result = [interm] + + for k in range(n): + interm = Expression(f, interm).evaluate(evaluation) + result.append(interm) + + return from_python(result) + + +class NestWhile(Builtin): + """ +
+
'NestWhile[$f$, $expr$, $test$]' +
applies a function $f$ repeatedly on an expression $expr$, until applying $test$ on the result no longer yields 'True'. + +
'NestWhile[$f$, $expr$, $test$, $m$]' +
supplies the last $m$ results to $test$ (default value: 1). + +
'NestWhile[$f$, $expr$, $test$, All]' +
supplies all results gained so far to $test$. +
+ + Divide by 2 until the result is no longer an integer: + >> NestWhile[#/2&, 10000, IntegerQ] + = 625 / 2 + """ + + rules = { + "NestWhile[f_, expr_, test_]": "NestWhile[f, expr, test, 1]", + } + + summary_text = "nest while a condition is satisfied returning the last expression" + + def apply(self, f, expr, test, m, evaluation): + "NestWhile[f_, expr_, test_, Pattern[m,_Integer|All]]" + + results = [expr] + while True: + if m.get_name() == "All": + test_leaves = results + else: + test_leaves = results[-m.value :] + test_expr = Expression(test, *test_leaves) + test_result = test_expr.evaluate(evaluation) + if test_result.is_true(): + next = Expression(f, results[-1]) + results.append(next.evaluate(evaluation)) + else: + break + return results[-1] class Return(Builtin): """
-
'Return[$expr$]' +
'Return[$expr$]'
aborts a function call and returns $expr$.
@@ -706,122 +696,175 @@ class Return(Builtin): "Return[]": "Return[Null]", } + summary_text = "return from a function" + def apply(self, expr, evaluation): "Return[expr_]" raise ReturnInterrupt(expr) -class Break(Builtin): +class Switch(Builtin): """
-
'Break[]' -
exits a 'For', 'While', or 'Do' loop. +
'Switch[$expr$, $pattern1$, $value1$, $pattern2$, $value2$, ...]' +
yields the first $value$ for which $expr$ matches the corresponding $pattern$.
- >> n = 0; - >> While[True, If[n>10, Break[]]; n=n+1] - >> n - = 11 + + >> Switch[2, 1, x, 2, y, 3, z] + = y + >> Switch[5, 1, x, 2, y] + = Switch[5, 1, x, 2, y] + >> Switch[5, 1, x, 2, a, _, b] + = b + >> Switch[2, 1] + : Switch called with 2 arguments. Switch must be called with an odd number of arguments. + = Switch[2, 1] + + #> a; Switch[b, b] + : Switch called with 2 arguments. Switch must be called with an odd number of arguments. + = Switch[b, b] + + ## Issue 531 + #> z = Switch[b, b]; + : Switch called with 2 arguments. Switch must be called with an odd number of arguments. + #> z + = Switch[b, b] """ + attributes = ("HoldRest",) + messages = { - "nofwd": "No enclosing For, While, or Do found for Break[].", + "argct": ( + "Switch called with `2` arguments. " + "Switch must be called with an odd number of arguments." + ), } - def apply(self, evaluation): - "Break[]" + summary_text = "switch based on a value, with patterns allowed" - raise BreakInterrupt + def apply(self, expr, rules, evaluation): + "Switch[expr_, rules___]" + rules = rules.get_sequence() + if len(rules) % 2 != 0: + evaluation.message("Switch", "argct", "Switch", len(rules) + 1) + return + for pattern, value in zip(rules[::2], rules[1::2]): + if match(expr, pattern, evaluation): + return value.evaluate(evaluation) + # return unevaluated Switch when no pattern matches -class Continue(Builtin): + +class Which(Builtin): """
-
'Continue[]' -
continues with the next iteration in a 'For', 'While', or 'Do' loop. +
'Which[$cond1$, $expr1$, $cond2$, $expr2$, ...]' +
yields $expr1$ if $cond1$ evaluates to 'True', $expr2$ if $cond2$ evaluates to 'True', etc.
- >> For[i=1, i<=8, i=i+1, If[Mod[i,2] == 0, Continue[]]; Print[i]] - | 1 - | 3 - | 5 - | 7 + >> n = 5; + >> Which[n == 3, x, n == 5, y] + = y + >> f[x_] := Which[x < 0, -x, x == 0, 0, x > 0, x] + >> f[-3] + = 3 + + #> Clear[f] + + If no test yields 'True', 'Which' returns 'Null': + >> Which[False, a] + + If a test does not evaluate to 'True' or 'False', evaluation stops + and a 'Which' expression containing the remaining cases is + returned: + >> Which[False, a, x, b, True, c] + = Which[x, b, True, c] + + 'Which' must be called with an even number of arguments: + >> Which[a, b, c] + : Which called with 3 arguments. + = Which[a, b, c] """ - messages = { - "nofwd": "No enclosing For, While, or Do found for Continue[].", - } + attributes = ("HoldAll",) + summary_text = "test which of a sequence of conditions are true" - def apply(self, evaluation): - "Continue[]" + def apply(self, items, evaluation): + "Which[items___]" - raise ContinueInterrupt + items = items.get_sequence() + nr_items = len(items) + if len(items) == 1: + evaluation.message("Which", "argctu", "Which") + return + elif len(items) % 2 == 1: + evaluation.message("Which", "argct", "Which", len(items)) + return + while items: + test, item = items[0], items[1] + test_result = test.evaluate(evaluation) + if test_result.is_true(): + return item.evaluate(evaluation) + elif test_result != SymbolFalse: + if len(items) == nr_items: + return None + return Expression("Which", *items) + items = items[2:] + return Symbol("Null") -class Catch(Builtin): +class While(Builtin): """
-
'Catch[$expr$]' -
returns the argument of the first 'Throw' generated in the evaluation of $expr$. +
'While[$test$, $body$]' +
evaluates $body$ as long as $test$ evaluates to 'True'. -
'Catch[$expr$, $form$]' -
returns value from the first 'Throw[$value$, $tag$]' for which $form$ matches $tag$. - -
'Catch[$expr$, $form$, $f$]' -
returns $f$[$value$, $tag$]. +
'While[$test$]' +
runs the loop without any body.
- Exit to the enclosing 'Catch' as soon as 'Throw' is evaluated: - >> Catch[r; s; Throw[t]; u; v] - = t - - Define a function that can "throw an exception": - >> f[x_] := If[x > 12, Throw[overflow], x!] - - The result of 'Catch' is just what is thrown by 'Throw': - >> Catch[f[1] + f[15]] - = overflow - >> Catch[f[1] + f[4]] - = 25 + Compute the GCD of two numbers: + >> {a, b} = {27, 6}; + >> While[b != 0, {a, b} = {b, Mod[a, b]}]; + >> a + = 3 - #> Clear[f] + #> i = 1; While[True, If[i^2 > 100, Return[i + 1], i++]] + = 12 """ attributes = ("HoldAll",) + rules = { + "While[test_]": "While[test, Null]", + } - def apply1(self, expr, evaluation): - "Catch[expr_]" - try: - ret = expr.evaluate(evaluation) - except WLThrowInterrupt as e: - return e.value - return ret + summary_text = "evaluate an expression while a criterion is true" - def apply3(self, expr, form, f, evaluation): - "Catch[expr_, form_, f__:Identity]" - try: - ret = expr.evaluate(evaluation) - except WLThrowInterrupt as e: - # TODO: check that form match tag. - # otherwise, re-raise the exception - match = Expression("MatchQ", e.tag, form).evaluate(evaluation) - if match.is_true(): - return Expression(f, e.value) - else: - # A plain raise hide, this path and preserves the traceback - # of the call that was originally given. - raise - return ret + def apply(self, test, body, evaluation): + "While[test_, body_]" + + while test.evaluate(evaluation).is_true(): + try: + evaluation.check_stopped() + body.evaluate(evaluation) + except ContinueInterrupt: + pass + except BreakInterrupt: + break + except ReturnInterrupt as e: + return e.expr + return Symbol("Null") class Throw(Builtin): """
-
'Throw[`value`]' -
stops evaluation and returns `value` as the value of the nearest enclosing Catch. +
'Throw[`value`]' +
stops evaluation and returns `value` as the value of the nearest enclosing 'Catch'. -
'Catch[`value`, `tag`]' -
is caught only by `Catch[expr,form]`, where tag matches form. +
'Catch[`value`, `tag`]' +
is caught only by `Catch[expr,form]`, where tag matches form.
Using Throw can affect the structure of what is returned by a function: @@ -839,6 +882,8 @@ class Throw(Builtin): "nocatch": "Uncaught `1` returned to top level.", } + summary_text = "throw an expression to be caught by a surrounding 'Catch'" + def apply1(self, value, evaluation): "Throw[value_]" raise WLThrowInterrupt(value) From 86bd345b5bed3a946806cde3cca0e330375b7694 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 7 Aug 2021 07:06:31 -0400 Subject: [PATCH 018/193] Formatting on TimeConstrained --- mathics/builtin/datentime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index 7e3571466..d552cd995 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -1080,7 +1080,7 @@ def evaluate(self, evaluation): if sys.platform != "win32" and ("Pyston" not in sys.version): class TimeConstrained(Builtin): - """ + r"""
'TimeConstrained[$expr$, $t$]'
'evaluates $expr$, stopping after $t$ seconds.' @@ -1091,8 +1091,8 @@ class TimeConstrained(Builtin): Possible issues: for certain time-consuming functions (like simplify) which are based on sympy or other libraries, it is possible that - the evaluation continues after the timeout. However, at the end of the evaluation, the function will return $\\$Aborted$ and the results will not affect - the state of the mathics kernel. + the evaluation continues after the timeout. However, at the end of the evaluation, the function will return '$Aborted' and the results will not affect + the state of the \Mathics kernel. """ From 0c4e67bdf92c6908b0c492563f4bfd3665fe95e8 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 7 Aug 2021 10:27:05 -0400 Subject: [PATCH 019/193] Make a pass over Bessel fns Expand _MPMathMultiFunction to handle a function which is sympy only but not mpmath. --- mathics/builtin/arithmetic.py | 2 + mathics/builtin/specialfns/bessel.py | 103 +++++++++++++++++++++------ 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 266c6bdf8..86d395892 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -175,6 +175,8 @@ def get_function(self, module, names, fallback_name, leaves): name = fallback_name if names is not None: name = names[len(leaves)] + if name is None: + return None return getattr(module, name) except KeyError: return None diff --git a/mathics/builtin/specialfns/bessel.py b/mathics/builtin/specialfns/bessel.py index c8f3ea69b..a336c63b4 100644 --- a/mathics/builtin/specialfns/bessel.py +++ b/mathics/builtin/specialfns/bessel.py @@ -283,6 +283,9 @@ class AngerJ(_Bessel): class BesselI(_Bessel): """ + + Bessel function of the first kind. See https://en.wikipedia.org/wiki/Bessel_function#Bessel_functions_of_the_first_kind:_J%CE%B1. +
'BesselI[$n$, $z$]'
returns the modified Bessel function of the first kind I_$n$($z$). @@ -301,12 +304,15 @@ class BesselI(_Bessel): attributes = ("Listable", "NumericFunction", "Protected") - sympy_name = "besseli" mpmath_name = "besseli" + sympy_name = "besseli" + summary_text = "Bessel function of the second kind" class BesselJ(_Bessel): """ + Bessel function of the first kind. See https://en.wikipedia.org/wiki/Bessel_function#Bessel_functions_of_the_first_kind:_J%CE%B1. +
'BesselJ[$n$, $z$]'
returns the Bessel function of the first kind J_$n$($z$). @@ -333,20 +339,23 @@ class BesselJ(_Bessel): >> BesselJ[1/2, x] = Sqrt[2 / Pi] Sin[x] / Sqrt[x] """ + attributes = ("Listable", "NumericFunction", "Protected") + + mpmath_name = "besselj" rules = { "Derivative[0,1][BesselJ]": "(BesselJ[#1- 1, #2] / 2 - BesselJ[#1 + 1, #2] / 2)&", } - attributes = ("Listable", "NumericFunction", "Protected") - + summary_text = "Bessel function of the first kind" sympy_name = "besselj" - mpmath_name = "besselj" class BesselK(_Bessel): """ + Modified Bessel function of the second kind. See https://en.wikipedia.org/wiki/Bessel_function#Modified_Bessel_functions:_I%CE%B1,_K%CE%B1. +
-
'BesselK[$n$, $z$]' +
'BesselK[$n$, $z$]'
returns the modified Bessel function of the second kind K_$n$($z$).
@@ -360,17 +369,19 @@ class BesselK(_Bessel): attributes = ("Listable", "NumericFunction", "Protected") mpmath_name = "besselk" - sympy_name = "besselk" rules = { "Derivative[0, 1][BesselK]": "((-BesselK[-1 + #1, #2] - BesselK[1 + #1, #2])/2)&", } + summary_text = "Modified Bessel function of the second kind" + sympy_name = "besselk" + class BesselY(_Bessel): """
-
'BesselY[$n$, $z$]' +
'BesselY[$n$, $z$]'
returns the Bessel function of the second kind Y_$n$($z$).
@@ -397,13 +408,14 @@ class BesselY(_Bessel): attributes = ("Listable", "NumericFunction", "Protected") mpmath_name = "bessely" + summary_text = "function of the second kind" sympy_name = "bessely" class BesselJZero(_Bessel): """
-
'BesselJZero[$n$, $k$]' +
'BesselJZero[$n$, $k$]'
returns the $k$th zero of the Bessel function of the first kind J_$n$($z$).
@@ -416,14 +428,14 @@ class BesselJZero(_Bessel): # attributes = ("Listable", "NumericFunction") # inherited - sympy_name = "" mpmath_name = "besseljzero" + sympy_name = "" class BesselYZero(_Bessel): """
-
'BesselYZero[$n$, $k$]' +
'BesselYZero[$n$, $k$]'
returns the $k$th zero of the Bessel function of the second kind Y_$n$($z$).
@@ -440,8 +452,6 @@ class BesselYZero(_Bessel): sympy_name = "" -# TODO: Spherical Bessel Functions - # Hankel Functions @@ -466,7 +476,7 @@ class HankelH1(_Bessel): class HankelH2(_Bessel): """
-
'HankelH2[$n$, $z$]' +
'HankelH2[$n$, $z$]'
returns the Hankel function of the second kind H_$n$^2 ($z$).
@@ -488,9 +498,10 @@ class HankelH2(_Bessel): class KelvinBei(_Bessel): """
-
'KelvinBei[$z$]' +
'KelvinBei[$z$]'
returns the Kelvin function bei($z$). -
'KelvinBei[$n$, $z$]' + +
'KelvinBei[$n$, $z$]'
returns the Kelvin function bei_$n$($z$).
@@ -521,9 +532,10 @@ class KelvinBei(_Bessel): class KelvinBer(_Bessel): """
-
'KelvinBer[$z$]' +
'KelvinBer[$z$]'
returns the Kelvin function ber($z$). -
'KelvinBer[$n$, $z$]' + +
'KelvinBer[$n$, $z$]'
returns the Kelvin function ber_$n$($z$).
@@ -554,9 +566,10 @@ class KelvinBer(_Bessel): class KelvinKei(_Bessel): """
-
'KelvinKei[$z$]' +
'KelvinKei[$z$]'
returns the Kelvin function kei($z$). -
'KelvinKei[$n$, $z$]' + +
'KelvinKei[$n$, $z$]'
returns the Kelvin function kei_$n$($z$).
@@ -613,13 +626,61 @@ class KelvinKer(_Bessel): sympy_name = "" +# TODO: +# this "works" but only giving symbolic results, not numeric results. Seems to be a Sympy limitation? + +# class SphericalBesselJ(_Bessel): +# """ + +# Spherical Bessel funciton of the first kind. See href="https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions + +#
+#
'SphericalBesselJ[$n$, $z$]' +#
returns the spherical Bessel function of the first kind Y_$n$($z$). +#
+ +# >> SphericalBesselJ[1, 5.2] +# = -0.122771 + +# >> Plot[SphericalBesselJ[1, x], {x, 0, 20}] +# = -Graphics- +# """ + +# attributes = ("Listable", "NumericFunction", "Protected") + +# summary_text = "spherical Bessel function of the second kind" +# sympy_name = "jn" + + +# class SphericalBesselY(_Bessel): +# """ +# Spherical Bessel funciton of the first kind. See href="https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions + +#
+#
'SphericalBesselY[$n$, $z$]' +#
returns the spherical Bessel function of the second kind Y_$n$($z$). +#
+ +# >> SphericalBesselY[1, 5.5] +# = 0.104853 + +# >> Plot[SphericalBesselY[1, x], {x, 0, 20}] +# = -Graphics- +# """ + +# attributes = ("Listable", "NumericFunction", "Protected") + +# summary_text = "spherical Bessel function of the second kind" +# sympy_name = "yn" + + # Struve and Related Functions class StruveH(_Bessel): """
-
'StruveH[$n$, $z$]' +
'StruveH[$n$, $z$]'
returns the Struve function H_$n$($z$).
@@ -659,7 +720,7 @@ class StruveL(_Bessel): class WeberE(_Bessel): """
-
'WeberE[$n$, $z$]' +
'WeberE[$n$, $z$]'
returns the Weber function E_$n$($z$).
From 49781a5098f64bdf2346e45243826693064fc4ad Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 7 Aug 2021 15:21:28 -0400 Subject: [PATCH 020/193] Add more spherical Bessel Functions --- CHANGES.rst | 7 ++ mathics/builtin/numeric.py | 2 + mathics/builtin/specialfns/bessel.py | 177 ++++++++++++++++++--------- mathics/core/expression.py | 5 + 4 files changed, 136 insertions(+), 55 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 153da3260..8159692fd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,13 @@ Tensor functions: * ``TransformationFunction`` * ``TranslationTransform`` +Spherical Bessel functions: + +* ``SphericalBesselJ`` +* ``SphericalBesselY`` +* ``SphericalHankelH1`` +* ``SphericalHankelH2`` + 4.0.0 ----- diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index e378052c0..0c0923ceb 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -1106,6 +1106,8 @@ def approx_interval_continued_fraction(xmin, xmax): def chop(expr, delta=10.0 ** (-10.0)): if isinstance(expr, Real): + if expr.is_nan(expr): + return expr if -delta < expr.get_float_value() < delta: return Integer0 elif isinstance(expr, Complex) and expr.is_inexact(): diff --git a/mathics/builtin/specialfns/bessel.py b/mathics/builtin/specialfns/bessel.py index a336c63b4..8ca6aa06c 100644 --- a/mathics/builtin/specialfns/bessel.py +++ b/mathics/builtin/specialfns/bessel.py @@ -44,13 +44,14 @@ class AiryAi(_MPMathFunction): # attributes = ("Listable", "NumericFunction") # inherited - mpmath_name = "airyai" - sympy_name = "airyai" - rules = { "Derivative[1][AiryAi]": "AiryAiPrime", } + mpmath_name = "airyai" + summary_text = "Airy function Ai" + sympy_name = "airyai" + class AiryAiPrime(_MPMathFunction): """ @@ -68,13 +69,15 @@ class AiryAiPrime(_MPMathFunction): = -0.224911 """ + mpmath_name = "" + rules = { "Derivative[1][AiryAiPrime]": "(#1 AiryAi[#1])&", } attributes = ("Listable", "NumericFunction") - mpmath_name = "" + summary_text = "Derivative of the Airy function Ai" sympy_name = "airyaiprime" def get_mpmath_function(self, args): @@ -118,6 +121,8 @@ class AiryAiZero(Builtin): "AiryAi[AiryAiZero[k_]]": "0", } + summary_text = "Get kth zero of an Airy function Ai" + def apply_N(self, k, precision, evaluation): "N[AiryAiZero[k_Integer], precision_]" @@ -162,12 +167,14 @@ class AiryBi(_MPMathFunction): attributes = ("Listable", "NumericFunction") mpmath_name = "airybi" - sympy_name = "airybi" rules = { "Derivative[1][AiryBi]": "AiryBiPrime", } + summary_text = "Airy function Bi" + sympy_name = "airybi" + class AiryBiPrime(_MPMathFunction): """ @@ -195,6 +202,8 @@ class AiryBiPrime(_MPMathFunction): "Derivative[1][AiryBiPrime]": "(#1 AiryBi[#1])&", } + summary_text = "Derivative of the Airy function Bi" + def get_mpmath_function(self, args): return lambda x: mpmath.airybi(x, derivative=1) @@ -236,6 +245,8 @@ class AiryBiZero(Builtin): "AiryBi[AiryBiZero[z_]]": "0", } + summary_text = "Get kth zero of an Airy function Bi" + def apply_N(self, k, precision, evaluation): "N[AiryBiZero[k_Integer], precision_]" @@ -275,6 +286,7 @@ class AngerJ(_Bessel): # attributes = ("Listable", "NumericFunction") # inherited mpmath_name = "angerj" + summary_text = "Anger function J" sympy_name = "" @@ -422,13 +434,14 @@ class BesselJZero(_Bessel): >> N[BesselJZero[0, 1]] = 2.40483 - #> N[BesselJZero[0, 1], 20] - = 2.4048255576957727686 + >> N[BesselJZero[0, 1], 10] + = 2.404825558 """ # attributes = ("Listable", "NumericFunction") # inherited mpmath_name = "besseljzero" + summary_text = "Get kth zero of an BesselJ function" sympy_name = "" @@ -442,13 +455,14 @@ class BesselYZero(_Bessel): >> N[BesselYZero[0, 1]] = 0.893577 - #> N[BesselYZero[0, 1]] - = 0.893577 + >> N[BesselYZero[0, 1], 10] + = 0.8935769663 """ attributes = ("Listable", "NumericFunction") mpmath_name = "besselyzero" + summary_text = "Get kth zero of an BesselY function" sympy_name = "" @@ -466,11 +480,13 @@ class HankelH1(_Bessel): = 0.185286 + 0.367112 I """ + mpmath_name = "hankel1" + rules = { "Derivative[0, 1][HankelH1]": "((HankelH1[-1 + #1, #2] - HankelH1[1 + #1, #2])/2)&", } + summary_text = "Hankel function zero of the first kind" sympy_name = "hankel1" - mpmath_name = "hankel1" class HankelH2(_Bessel): @@ -484,12 +500,13 @@ class HankelH2(_Bessel): = 0.185286 - 0.367112 I """ + mpmath_name = "hankel2" rules = { "Derivative[0, 1][HankelH2]": "((HankelH2[-1 + #1, #2] - HankelH2[1 + #1, #2])/2)&", } + summary_text = "Hankel function zero of the second kind" sympy_name = "hankel2" - mpmath_name = "hankel2" # Kelvin Functions @@ -518,14 +535,15 @@ class KelvinBei(_Bessel): = -Graphics- """ + attributes = ("Listable", "NumericFunction") + + mpmath_name = "bei" rules = { "KelvinBei[z_]": "KelvinBei[0, z]", "Derivative[1][KelvinBei]": "((2*KelvinBei[1, #1] - 2*KelvinBer[1, #1])/(2*Sqrt[2]))&", } - attributes = ("Listable", "NumericFunction") - - mpmath_name = "bei" + summary_text = "Kelvin function bei" sympy_name = "" @@ -552,14 +570,15 @@ class KelvinBer(_Bessel): = -Graphics- """ + attributes = ("Listable", "NumericFunction") + + mpmath_name = "ber" rules = { "KelvinBer[z_]": "KelvinBer[0, z]", "Derivative[1][KelvinBer]": "((2*KelvinBei[1, #1] + 2*KelvinBer[1, #1])/(2*Sqrt[2]))&", } - attributes = ("Listable", "NumericFunction") - - mpmath_name = "ber" + summary_text = "Kelvin function ber" sympy_name = "" @@ -586,12 +605,14 @@ class KelvinKei(_Bessel): = -Graphics- """ + mpmath_name = "kei" + rules = { "KelvinKei[z_]": "KelvinKei[0, z]", } + summary_text = "Kelvin function kei" sympy_name = "" - mpmath_name = "kei" class KelvinKer(_Bessel): @@ -616,65 +637,108 @@ class KelvinKer(_Bessel): = -Graphics- """ + attributes = ("Listable", "NumericFunction") + + mpmath_name = "ker" rules = { "KelvinKer[z_]": "KelvinKer[0, z]", } + summary_text = "Kelvin function ker" + sympy_name = "" - attributes = ("Listable", "NumericFunction") - mpmath_name = "ker" - sympy_name = "" +class SphericalBesselJ(_Bessel): + """ + + Spherical Bessel function of the first kind. See href="https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions + +
+
'SphericalBesselJ[$n$, $z$]' +
returns the spherical Bessel function of the first kind Y_$n$($z$). +
+ + >> SphericalBesselJ[1, 5.2] + = -0.122771 + + ## FIXME: should be able to tolerate Plotting at 0. + >> Plot[SphericalBesselJ[1, x], {x, 0.1, 10}] + = -Graphics- + """ + + attributes = ("Listable", "NumericFunction", "Protected") + + rules = {"SphericalBesselJ[n_, z_]": "Sqrt[Pi / 2] / Sqrt[z] BesselJ[n + 0.5, z]"} + + summary_text = "spherical Bessel function of the second kind" + sympy_name = "jn" + + +class SphericalBesselY(_Bessel): + """ + Spherical Bessel function of the first kind. See href="https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions + +
+
'SphericalBesselY[$n$, $z$]' +
returns the spherical Bessel function of the second kind Y_$n$($z$). +
+ + >> SphericalBesselY[1, 5.5] + = 0.104853 + + >> Plot[SphericalBesselY[1, x], {x, 0, 10}] + = -Graphics- + """ + attributes = ("Listable", "NumericFunction", "Protected") -# TODO: -# this "works" but only giving symbolic results, not numeric results. Seems to be a Sympy limitation? + rules = {"SphericalBesselY[n_, z_]": "Sqrt[Pi / 2] / Sqrt[z] BesselY[n + 0.5, z]"} -# class SphericalBesselJ(_Bessel): -# """ + summary_text = "spherical Bessel function of the second kind" + sympy_name = "yn" -# Spherical Bessel funciton of the first kind. See href="https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions -#
-#
'SphericalBesselJ[$n$, $z$]' -#
returns the spherical Bessel function of the first kind Y_$n$($z$). -#
+class SphericalHankelH1(_Bessel): + """ -# >> SphericalBesselJ[1, 5.2] -# = -0.122771 + Spherical Bessel function of the first kind. See href="https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions + +
+
'SphericalHankelH1[$n$, $z$]' +
returns the spherical Hankel function of the first kind h_$n$^(1)($z$). +
-# >> Plot[SphericalBesselJ[1, x], {x, 0, 20}] -# = -Graphics- -# """ + >> SphericalHankelH1[3, 1.5] + = 0.0283246 - 3.78927 I + """ -# attributes = ("Listable", "NumericFunction", "Protected") + attributes = ("Listable", "NumericFunction", "Protected") -# summary_text = "spherical Bessel function of the second kind" -# sympy_name = "jn" + rules = {"SphericalHankelH1[n_, z_]": "Sqrt[Pi / 2] / Sqrt[z] HankelH1[n + 0.5, z]"} + summary_text = "spherical Hankel function of the first kind" + sympy_name = "hankel1" -# class SphericalBesselY(_Bessel): -# """ -# Spherical Bessel funciton of the first kind. See href="https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions -#
-#
'SphericalBesselY[$n$, $z$]' -#
returns the spherical Bessel function of the second kind Y_$n$($z$). -#
+class SphericalHankelH2(_Bessel): + """ -# >> SphericalBesselY[1, 5.5] -# = 0.104853 + Spherical Bessel function of the second kind. See href="https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions -# >> Plot[SphericalBesselY[1, x], {x, 0, 20}] -# = -Graphics- -# """ +
+
'SphericalHankelH1[$n$, $z$]' +
returns the spherical Hankel function of the second kind h_$n$^(2)($z$). +
-# attributes = ("Listable", "NumericFunction", "Protected") + >> SphericalHankelH2[3, 1.5] + = 0.0283246 + 3.78927 I + """ -# summary_text = "spherical Bessel function of the second kind" -# sympy_name = "yn" + attributes = ("Listable", "NumericFunction", "Protected") + rules = {"SphericalHankelH2[n_, z_]": "Sqrt[Pi / 2] / Sqrt[z] HankelH2[n + 0.5, z]"} -# Struve and Related Functions + summary_text = "spherical Hankel function of the second kind" + sympy_name = "hankel2" class StruveH(_Bessel): @@ -687,13 +751,14 @@ class StruveH(_Bessel): >> StruveH[1.5, 3.5] = 1.13192 - >> Plot[StruveH[0, x], {x, 0, 20}] + >> Plot[StruveH[0, x], {x, 0, 10}] = -Graphics- """ attributes = ("Listable", "NumericFunction") mpmath_name = "struveh" + summary_text = "Struvel function H" sympy_name = "" @@ -714,6 +779,7 @@ class StruveL(_Bessel): attributes = ("Listable", "NumericFunction") mpmath_name = "struvel" + summary_text = "Struvel function L" sympy_name = "" @@ -736,4 +802,5 @@ class WeberE(_Bessel): attributes = ("Listable", "NumericFunction") mpmath_name = "webere" + summary_text = "Weber function E" sympy_name = "" diff --git a/mathics/core/expression.py b/mathics/core/expression.py index ec49c97a2..296ea8560 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -593,6 +593,8 @@ def round_to_float(self, evaluation=None, permit_complex=False): """ if evaluation is None: value = self + elif isinstance(evaluation, sympy.core.numbers.NaN): + return None else: value = Expression(SymbolN, self).evaluate(evaluation) if isinstance(value, Number): @@ -2420,6 +2422,9 @@ def get_sort_key(self, pattern_sort=False): return super().get_sort_key(True) return [0, 0, self.value, 0, 1] + def is_nan(self, d=None) -> bool: + return isinstance(self.value, sympy.core.numbers.NaN) + def __eq__(self, other) -> bool: if isinstance(other, Real): # MMA Docs: "Approximate numbers that differ in their last seven From ed4a1d4c1f3af81717343d68ca802670bda7b1ba Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 8 Aug 2021 07:42:30 -0400 Subject: [PATCH 021/193] All ENV settings for DOC paths DOC_USER_TEX_DATA_PATH DOC_SYSTEM_TEX_DATA_PATH --- mathics/settings.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/mathics/settings.py b/mathics/settings.py index 778a3aee4..236e3465d 100644 --- a/mathics/settings.py +++ b/mathics/settings.py @@ -26,20 +26,26 @@ ROOT_DIR = pkg_resources.resource_filename("mathics", "") if sys.platform.startswith("win"): - DATA_DIR = os.environ["APPDATA"].replace(os.sep, "/") + "/Python/Mathics/" + DATA_DIR = osp.join(os.environ["APPDATA"], "Python", "Mathics") else: - DATA_DIR = osp.expanduser("~/.local/var/mathics/") + DATA_DIR = osp.join( + os.environ.get("APPDATA", osp.expanduser("~/.local/var/mathics/")) + ) # Location of internal document data. Currently this is in Python # Pickle form, but storing this in JSON if possible would be preferable and faster # We need two versions, one in the user space which is updated with # local packages installed and is user writable. -DOC_USER_TEX_DATA_PATH = osp.join(DATA_DIR, "doc_tex_data.pcl") +DOC_USER_TEX_DATA_PATH = os.environ.get( + "DOC_USER_TEX_DATA_PATH", osp.join(DATA_DIR, "doc_tex_data.pcl") +) # We need another version as a fallback, and that is distributed with the # package. It is note user writable and not in the user space. -DOC_SYSTEM_TEX_DATA_PATH = osp.join(ROOT_DIR, "data", "doc_tex_data.pcl") +DOC_SYSTEM_TEX_DATA_PATH = os.environ.get( + "DOC_USER_TEX_DATA_PATH", osp.join(ROOT_DIR, "data", "doc_tex_data.pcl") +) DOC_DIR = osp.join(ROOT_DIR, "doc", "documentation") DOC_LATEX_FILE = osp.join(ROOT_DIR, "doc", "tex", "documentation.tex") From 1a54c5ea89512a7617b92fdb8881f2b1fef98044 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 8 Aug 2021 08:01:27 -0400 Subject: [PATCH 022/193] Tweak last commit --- mathics/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/settings.py b/mathics/settings.py index 236e3465d..8ae7848e3 100644 --- a/mathics/settings.py +++ b/mathics/settings.py @@ -44,7 +44,7 @@ # We need another version as a fallback, and that is distributed with the # package. It is note user writable and not in the user space. DOC_SYSTEM_TEX_DATA_PATH = os.environ.get( - "DOC_USER_TEX_DATA_PATH", osp.join(ROOT_DIR, "data", "doc_tex_data.pcl") + "DOC_SYSTEM_TEX_DATA_PATH", osp.join(ROOT_DIR, "data", "doc_tex_data.pcl") ) DOC_DIR = osp.join(ROOT_DIR, "doc", "documentation") From d4242139c1a38713c7f6fc3d9b11804ca7c0328f Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 8 Aug 2021 11:53:35 -0400 Subject: [PATCH 023/193] Useful LaTeX doc tweaks Add ability to print only chapters or parts of a manual Use smaller font in title for Mathics release number --- mathics/doc/common_doc.py | 19 ++++++++++++++----- mathics/doc/tex/doc2latex.py | 32 +++++++++++++++++++++++++++++--- mathics/doc/tex/mathics.tex | 2 +- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py index fbdf15f1a..0c347432d 100644 --- a/mathics/doc/common_doc.py +++ b/mathics/doc/common_doc.py @@ -694,7 +694,9 @@ def get_tests(self): pass return - def latex(self, doc_data: dict, quiet=False) -> str: + def latex( + self, doc_data: dict, quiet=False, filter_parts=None, filter_chapters=None + ) -> str: """Render self as a LaTeX string and return that. `output` is not used here but passed along to the bottom-most @@ -703,7 +705,10 @@ def latex(self, doc_data: dict, quiet=False) -> str: parts = [] appendix = False for part in self.parts: - text = part.latex(doc_data, quiet) + if filter_parts: + if part.title not in filter_parts: + continue + text = part.latex(doc_data, quiet, filter_chapters=filter_chapters) if part.is_appendix and not appendix: appendix = True text = "\n\\appendix\n" + text @@ -1134,14 +1139,18 @@ def __str__(self): "\n".join(str(chapter) for chapter in self.chapters), ) - def latex(self, doc_data: dict, quiet=False) -> str: + def latex(self, doc_data: dict, quiet=False, filter_chapters=None) -> str: """Render this Part object as LaTeX string and return that. `output` is not used here but passed along to the bottom-most level in getting expected test results. """ result = "\n\n\\part{%s}\n\n" % escape_latex(self.title) + ( - "\n\n".join(chapter.latex(doc_data, quiet) for chapter in self.chapters) + "\n\n".join( + chapter.latex(doc_data, quiet) + for chapter in self.chapters + if not filter_chapters or chapter.title in filter_chapters + ) ) if self.is_reference: result = "\n\n\\referencestart" + result @@ -1394,7 +1403,7 @@ def __init__( def __str__(self): return f"=== {self.title} ===\n{self.doc}" - def latex(self, doc_data: dict, quiet=False): + def latex(self, doc_data: dict, quiet=False, chapters=None): """Render this Subsection object as LaTeX string and return that. `output` is not used here but passed along to the bottom-most diff --git a/mathics/doc/tex/doc2latex.py b/mathics/doc/tex/doc2latex.py index bb03e1583..081e9f515 100755 --- a/mathics/doc/tex/doc2latex.py +++ b/mathics/doc/tex/doc2latex.py @@ -86,12 +86,17 @@ def try_cmd(cmd_list: tuple, stdout_or_stderr: str) -> str: return versions -def write_latex(doc_data, quiet=False): +def write_latex(doc_data, quiet=False, filter_parts=None, filter_chapters=None): documentation = MathicsMainDocumentation() if not quiet: print(f"Writing LaTeX document to {DOC_LATEX_FILE}") with open_ensure_dir(DOC_LATEX_FILE, "wb") as doc: - content = documentation.latex(doc_data, quiet=quiet) + content = documentation.latex( + doc_data, + quiet=quiet, + filter_parts=filter_parts, + filter_chapters=filter_chapters, + ) content = content.encode("utf-8") doc.write(content) DOC_VERSION_FILE = osp.join(osp.dirname(DOC_LATEX_FILE), "version-info.tex") @@ -114,6 +119,22 @@ def main(): parser.add_argument( "--version", "-v", action="version", version="%(prog)s " + mathics.__version__ ) + parser.add_argument( + "--chapters", + "-c", + dest="chapters", + metavar="CHAPTER", + help="only test CHAPTER(s). " + "You can list multiple chapters by adding a comma (and no space) in between chapter names.", + ) + parser.add_argument( + "--parts", + "-p", + dest="parts", + metavar="PART", + help="only test PART(s). " + "You can list multiple parts by adding a comma (and no space) in between part names.", + ) parser.add_argument( "--quiet", "-q", @@ -123,7 +144,12 @@ def main(): ) args = parser.parse_args() doc_data = extract_doc_from_source(quiet=args.quiet) - write_latex(doc_data) + write_latex( + doc_data, + quiet=args.quiet, + filter_parts=args.parts, + filter_chapters=args.chapters, + ) if __name__ == "__main__": diff --git a/mathics/doc/tex/mathics.tex b/mathics/doc/tex/mathics.tex index 971d115c3..c1163e86a 100644 --- a/mathics/doc/tex/mathics.tex +++ b/mathics/doc/tex/mathics.tex @@ -69,7 +69,7 @@ \includegraphics[height=0.08125\linewidth]{logo-text-nodrop.pdf} \\[.5em] {\LARGE\color{subtitle}\textit{\textmd{A free, open-source alternative to Mathematica}}} - \par\textmd{Mathics Core Version \MathicsCoreVersion} + \par\textmd{\Large Mathics Core Version \MathicsCoreVersion} } \author{The Mathics Team} From d27d7c0fc9106e9930a3c5906525accd78b80298 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 8 Aug 2021 22:45:24 -0400 Subject: [PATCH 024/193] Add ability to filter doc making by section Builds on what we did before in order to filter by chapter and part. This now gives a us a good ability to QUICKLY iterate over problems in making the doc or in asymptote problem.s --- mathics/doc/common_doc.py | 28 ++++++++++++++++++++++------ mathics/doc/tex/doc2latex.py | 14 +++++++++++++- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py index 0c347432d..87ccedc24 100644 --- a/mathics/doc/common_doc.py +++ b/mathics/doc/common_doc.py @@ -695,7 +695,12 @@ def get_tests(self): return def latex( - self, doc_data: dict, quiet=False, filter_parts=None, filter_chapters=None + self, + doc_data: dict, + quiet=False, + filter_parts=None, + filter_chapters=None, + filter_sections=None, ) -> str: """Render self as a LaTeX string and return that. @@ -708,7 +713,12 @@ def latex( if filter_parts: if part.title not in filter_parts: continue - text = part.latex(doc_data, quiet, filter_chapters=filter_chapters) + text = part.latex( + doc_data, + quiet, + filter_chapters=filter_chapters, + filter_sections=filter_sections, + ) if part.is_appendix and not appendix: appendix = True text = "\n\\appendix\n" + text @@ -1139,7 +1149,9 @@ def __str__(self): "\n".join(str(chapter) for chapter in self.chapters), ) - def latex(self, doc_data: dict, quiet=False, filter_chapters=None) -> str: + def latex( + self, doc_data: dict, quiet=False, filter_chapters=None, filter_sections=None + ) -> str: """Render this Part object as LaTeX string and return that. `output` is not used here but passed along to the bottom-most @@ -1147,7 +1159,7 @@ def latex(self, doc_data: dict, quiet=False, filter_chapters=None) -> str: """ result = "\n\n\\part{%s}\n\n" % escape_latex(self.title) + ( "\n\n".join( - chapter.latex(doc_data, quiet) + chapter.latex(doc_data, quiet, filter_sections=filter_sections) for chapter in self.chapters if not filter_chapters or chapter.title in filter_chapters ) @@ -1176,7 +1188,7 @@ def __str__(self): def all_sections(self): return sorted(self.sections + self.guide_sections) - def latex(self, doc_data: dict, quiet=False) -> str: + def latex(self, doc_data: dict, quiet=False, filter_sections=None) -> str: """Render this Chapter object as LaTeX string and return that. `output` is not used here but passed along to the bottom-most @@ -1196,7 +1208,11 @@ def latex(self, doc_data: dict, quiet=False) -> str: ("\n\n\\chapter{%(title)s}\n\\chapterstart\n\n%(intro)s") % {"title": escape_latex(self.title), "intro": intro}, "\\chaptersections\n", - "\n\n".join(section.latex(doc_data, quiet) for section in self.sections), + "\n\n".join( + section.latex(doc_data, quiet) + for section in self.sections + if not filter_sections or section.title in filter_sections + ), "\n\\chapterend\n", ] return "".join(chapter_sections) diff --git a/mathics/doc/tex/doc2latex.py b/mathics/doc/tex/doc2latex.py index 081e9f515..948621cd4 100755 --- a/mathics/doc/tex/doc2latex.py +++ b/mathics/doc/tex/doc2latex.py @@ -86,7 +86,9 @@ def try_cmd(cmd_list: tuple, stdout_or_stderr: str) -> str: return versions -def write_latex(doc_data, quiet=False, filter_parts=None, filter_chapters=None): +def write_latex( + doc_data, quiet=False, filter_parts=None, filter_chapters=None, filter_sections=None +): documentation = MathicsMainDocumentation() if not quiet: print(f"Writing LaTeX document to {DOC_LATEX_FILE}") @@ -96,6 +98,7 @@ def write_latex(doc_data, quiet=False, filter_parts=None, filter_chapters=None): quiet=quiet, filter_parts=filter_parts, filter_chapters=filter_chapters, + filter_sections=filter_sections, ) content = content.encode("utf-8") doc.write(content) @@ -127,6 +130,14 @@ def main(): help="only test CHAPTER(s). " "You can list multiple chapters by adding a comma (and no space) in between chapter names.", ) + parser.add_argument( + "--sections", + "-s", + dest="sections", + metavar="SECTION", + help="only test SECTION(s). " + "You can list multiple chapters by adding a comma (and no space) in between chapter names.", + ) parser.add_argument( "--parts", "-p", @@ -149,6 +160,7 @@ def main(): quiet=args.quiet, filter_parts=args.parts, filter_chapters=args.chapters, + filter_sections=args.sections, ) From a692711da3287bd4241db4ef215492df8317630c Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 8 Aug 2021 23:58:38 -0400 Subject: [PATCH 025/193] Add GudermannianRules taken from symja --- CHANGES.rst | 2 ++ mathics/autoload/rules/GudermannianRules.m | 16 ++++++++++++++++ test/test_gudermannian.py | 17 +++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 mathics/autoload/rules/GudermannianRules.m create mode 100644 test/test_gudermannian.py diff --git a/CHANGES.rst b/CHANGES.rst index 8159692fd..856a43fef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,8 @@ CHANGES New builtins ++++++++++++ +* ``Guidermannian`` + Tensor functions: * ``RotationTransform`` diff --git a/mathics/autoload/rules/GudermannianRules.m b/mathics/autoload/rules/GudermannianRules.m new file mode 100644 index 000000000..e17b7610b --- /dev/null +++ b/mathics/autoload/rules/GudermannianRules.m @@ -0,0 +1,16 @@ +Begin["System`"] +Gudermannian[Undefined]=Undefined; +Gudermannian[0]=0; +Gudermannian[2*Pi*I]=0; +Gudermannian[6/4*Pi*I]=DirectedInfinity[-I]; +Gudermannian[Infinity]=Pi/2; +Gudermannian[-Infinity]=-Pi/2; +Gudermannian[ComplexInfinity]=Indeterminate; +Gudermannian[z_]=2 ArcTan[Tanh[z / 2]]; +(* +Gudermannian[z_] := Piecewise[{{1/2*[Pi - 4*ArcCot[E^z]], Re[z]>0||(Re[z]==0&&Im[z]>=0 )}}, 1/2 (-Pi + 4 ArcTan[E^z])]; +D[Gudermannian[f_],x_?NotListQ] := Sech[f] D[f,x]; +Derivative[1][InverseGudermannian] := Sec[#] &; +Derivative[1][Gudermannian] := Sech[#] &; +*) +End[] diff --git a/test/test_gudermannian.py b/test/test_gudermannian.py new file mode 100644 index 000000000..8bb0628b2 --- /dev/null +++ b/test/test_gudermannian.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" +Unit tests from autoload/GudermannianRules.m +""" + +from .helper import check_evaluation + + +def test_gudermannian(): + for str_expr, str_expected, message in ( + ( + "Gudermannian[4.2]", + "1.54081", + "https://reference.wolfram.com/language/ref/Gudermannian.html", + ), + ): + check_evaluation(str_expr, str_expected, message) From bcb9c80f0773a5f58a4b98b545a80773ded3643e Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 10 Aug 2021 07:09:56 -0400 Subject: [PATCH 026/193] Was using wrong default for a,b,c,d... Maybe the default was different in an older version of Mathematica? Fixes #1540 --- mathics/autoload/rules/GudermannianRules.m | 2 ++ mathics/builtin/moments/basic.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mathics/autoload/rules/GudermannianRules.m b/mathics/autoload/rules/GudermannianRules.m index e17b7610b..5bbbdb5cd 100644 --- a/mathics/autoload/rules/GudermannianRules.m +++ b/mathics/autoload/rules/GudermannianRules.m @@ -1,4 +1,6 @@ +(* From symja_android_library/symja_android_library/rules/QuantileRules.m *) Begin["System`"] +Gudermannian::usage = "gives the Gudermannian function"; Gudermannian[Undefined]=Undefined; Gudermannian[0]=0; Gudermannian[2*Pi*I]=0; diff --git a/mathics/builtin/moments/basic.py b/mathics/builtin/moments/basic.py index 875a32331..58fe70edf 100644 --- a/mathics/builtin/moments/basic.py +++ b/mathics/builtin/moments/basic.py @@ -75,12 +75,12 @@ class Quantile(Builtin): = 4 >> Quantile[Range[16], 1/4] - = 5 + = 4 """ rules = { "Quantile[list_List, q_, abcd_]": "Quantile[list, {q}, abcd]", - "Quantile[list_List, q_]": "Quantile[list, q, {{0, 1}, {1, 0}}]", + "Quantile[list_List, q_]": "Quantile[list, q, {{0, 0}, {1, 0}}]", } messages = { From e74e3f0709e82b29618bf7092f7bf97e59a811ee Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 10 Aug 2021 07:26:31 -0400 Subject: [PATCH 027/193] Add one more Quantile example --- mathics/builtin/moments/basic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/moments/basic.py b/mathics/builtin/moments/basic.py index 58fe70edf..5148b1044 100644 --- a/mathics/builtin/moments/basic.py +++ b/mathics/builtin/moments/basic.py @@ -74,8 +74,8 @@ class Quantile(Builtin): >> Quantile[Range[11], 1/3] = 4 - >> Quantile[Range[16], 1/4] - = 4 + >> Quantile[{1, 2, 3, 4, 5, 6, 7}, {1/4, 3/4}] + = {2, 6} """ rules = { From 2bb512032b837c45d34bc68e127ebe52da9c5263 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 10 Aug 2021 08:25:01 -0400 Subject: [PATCH 028/193] Beef up Quantile doc --- CHANGES.rst | 5 +++++ mathics/builtin/moments/basic.py | 38 +++++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 856a43fef..35bfe6ad3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -24,6 +24,11 @@ Spherical Bessel functions: * ``SphericalHankelH1`` * ``SphericalHankelH2`` +Bugs +++++ + +Fix and document better behavior of ``Quantile``. + 4.0.0 ----- diff --git a/mathics/builtin/moments/basic.py b/mathics/builtin/moments/basic.py index 5148b1044..b76989fc9 100644 --- a/mathics/builtin/moments/basic.py +++ b/mathics/builtin/moments/basic.py @@ -66,16 +66,38 @@ def apply(self, l, evaluation): class Quantile(Builtin): """ -
-
'Quantile[$list$, $q$]' -
returns the $q$th quantile of $list$. -
+ Quantile is also known as value at risk (VaR) or fractile. +
+
'Quantile[$list$, $q$]' +
returns the $q$th quantile of $list$. + +
'Quantile[$list$, $q$, {{$a$,$b$}, {$c$,$d$}}]' +
uses the quantile definition specified by parameters $a$, $b$, $c$, $d$. +
For a list of length $n$, 'Quantile[list, $q$, {{$a$,$b$}, {$c$,$d$}}]' depends on $x$=$a$+($n$+$b$)$q$. + + If $x$ is an integer, the result is '$s$[[$x$]]', where $s$='Sort[list,Less]'. + + Otherwise, the result is 's[[Floor[x]]]+(s[[Ceiling[x]]]-s[[Floor[x]]])(c+dFractionalPart[x])', with the indices taken to be 1 or n if they are out of range. + + The default choice of parameters is '{{0,0},{1,0}}'. +
+ + Common choices of parameters include: +
    +
  • '{{0, 0}, {1, 0}}' inverse empirical CDF (default) +
  • '{{0, 0}, {0, 1}}' linear interpolation (California method) +
+ + 'Quantile[list,q]' always gives a result equal to an element of list. + + >> Quantile[Range[11], 1/3] + = 4 - >> Quantile[Range[11], 1/3] - = 4 + >> Quantile[Range[16], 1/4] + = 4 - >> Quantile[{1, 2, 3, 4, 5, 6, 7}, {1/4, 3/4}] - = {2, 6} + >> Quantile[{1, 2, 3, 4, 5, 6, 7}, {1/4, 3/4}] + = {2, 6} """ rules = { From 125c723159493cfd0c1b66ad00348a8645f804e4 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 10 Aug 2021 09:06:31 -0400 Subject: [PATCH 029/193] Update Quantile doc again --- mathics/builtin/moments/basic.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/moments/basic.py b/mathics/builtin/moments/basic.py index b76989fc9..8528bde5a 100644 --- a/mathics/builtin/moments/basic.py +++ b/mathics/builtin/moments/basic.py @@ -66,6 +66,8 @@ def apply(self, l, evaluation): class Quantile(Builtin): """ + In statistics and probability, quantiles are cut points dividing the range of a probability distribution into continuous intervals with equal probabilities, or dividing the observations in a sample in the same way. + Quantile is also known as value at risk (VaR) or fractile.
'Quantile[$list$, $q$]' @@ -100,14 +102,16 @@ class Quantile(Builtin): = {2, 6} """ + messages = { + "nquan": "The quantile `1` has to be between 0 and 1.", + } + rules = { "Quantile[list_List, q_, abcd_]": "Quantile[list, {q}, abcd]", "Quantile[list_List, q_]": "Quantile[list, q, {{0, 0}, {1, 0}}]", } - messages = { - "nquan": "The quantile `1` has to be between 0 and 1.", - } + summary_text = "cut points dividing the range of a probability distribution into continuous intervals" def apply(self, l, qs, a, b, c, d, evaluation): """Quantile[l_List, qs_List, {{a_, b_}, {c_, d_}}]""" From e9d14883f9ad04da553407ab86478d1e4513cd7a Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Aug 2021 01:45:25 -0400 Subject: [PATCH 030/193] Tag Asymptote RectangleBox --- mathics/format/asy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mathics/format/asy.py b/mathics/format/asy.py index e4d5cc045..e8b7aabd5 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -516,7 +516,8 @@ def rectanglebox(self, **options) -> str: self.edge_color, self.face_color, line_width, is_face_element=True ) x1, x2, y1, y2 = asy_number(x1), asy_number(x2), asy_number(y1), asy_number(y2) - asy = "filldraw((%s,%s)--(%s,%s)--(%s,%s)--(%s,%s)--cycle, %s);" % ( + asy = "// RectangleBox\n" + asy += "filldraw((%s,%s)--(%s,%s)--(%s,%s)--(%s,%s)--cycle, %s);" % ( x1, y1, x2, From 7c0e222875273ab6acfc70a54874bc99a13e82e2 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Aug 2021 15:33:21 -0400 Subject: [PATCH 031/193] Add PolyGamma and Stieltjes functions --- CHANGES.rst | 7 +++ mathics/builtin/arithmetic.py | 4 +- mathics/builtin/base.py | 14 +++--- mathics/builtin/numbers/numbertheory.py | 4 +- mathics/builtin/specialfns/gamma.py | 60 +++++++++++++++++++++++-- 5 files changed, 75 insertions(+), 14 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 35bfe6ad3..2e794f145 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -24,6 +24,13 @@ Spherical Bessel functions: * ``SphericalHankelH1`` * ``SphericalHankelH2`` +Gamma functions: + +* ``PolyGamma`` +* ``Stieltjes`` + + + Bugs ++++ diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 86d395892..07571d72c 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -482,7 +482,7 @@ def apply(self, z, evaluation, options={}): elif preference == "mpmath": return _MPMathFunction.apply(self, z, evaluation) elif preference == "sympy": - return SympyFunction.apply(self, z) + return SympyFunction.apply(self, z, evaluation) # TODO: add NumpyFunction evaluation.message( "meth", f'Arg Method {preference} not in ("sympy", "mpmath")' @@ -536,7 +536,7 @@ def apply(self, x, evaluation): sympy_x = x.to_sympy() if sympy_x is None: return None - return super().apply(x) + return super().apply(x, evaluation) def apply_error(self, x, seqs, evaluation): "Sign[x_, seqs__]" diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 9344d8e86..77a3087e6 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -535,12 +535,14 @@ def apply(self, expr, evaluation) -> Symbol: class SympyFunction(SympyObject): - def apply(self, *args): - """ - Generic apply method that uses the class sympy_name. - to call the corresponding sympy function. Arguments are - converted to python and the result is converted from sympy - """ + def apply(self, z, evaluation): + # + # Generic apply method that uses the class sympy_name. + # to call the corresponding sympy function. Arguments are + # converted to python and the result is converted from sympy + # + # "%(name)s[z__]" + args = z.numerify(evaluation).get_sequence() sympy_args = [a.to_sympy() for a in args] sympy_fn = getattr(sympy, self.sympy_name) return from_sympy(sympy_fn(*sympy_args)) diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index 5d6e65c3d..c29c90795 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -47,7 +47,7 @@ class ContinuedFraction(SympyFunction): def apply_1(self, x, evaluation): "%(name)s[x_]" - return super().apply(x) + return super().apply(x, evaluation) def apply_2(self, x, n, evaluation): "%(name)s[x_, n_Integer]" @@ -522,7 +522,7 @@ class PartitionsP(SympyFunction): def apply(self, n, evaluation): "PartitionsP[n_Integer]" - return super().apply(n) + return super().apply(n, evaluation) class Prime(SympyFunction): diff --git a/mathics/builtin/specialfns/gamma.py b/mathics/builtin/specialfns/gamma.py index a34579eed..0c05bde36 100644 --- a/mathics/builtin/specialfns/gamma.py +++ b/mathics/builtin/specialfns/gamma.py @@ -66,10 +66,10 @@ class Gamma(_MPMathMultiFunction): """ mpmath_names = { - 1: "gamma", + 1: "gamma", # one argument } sympy_names = { - 1: "gamma", + 1: "gamma", # one argument 2: "uppergamma", } @@ -98,8 +98,8 @@ class Pochhammer(SympyFunction): The Pochhammer symbol or rising factorial often appears in series expansions for hypergeometric functions. The Pochammer symbol has a definie value even when the gamma functions which appear in its definition are infinite.
-
'Pochhammer[$a$, $n$]' -
is the Pochhammer symbol (a)_n. +
'Pochhammer[$a$, $n$]' +
is the Pochhammer symbol (a)_n.
>> Pochhammer[4, 8] @@ -115,3 +115,55 @@ class Pochhammer(SympyFunction): "Derivative[1,0][Pochhammer]": "(Pochhammer[#1, #2]*(-PolyGamma[0, #1] + PolyGamma[0, #1 + #2]))&", "Derivative[0,1][Pochhammer]": "(Pochhammer[#1, #2]*PolyGamma[0, #1 + #2])&", } + + +class PolyGamma(_MPMathMultiFunction): + r""" + PolyGamma is a meromorphic function on the complex numbers and is defined as a derivative of the logarithm of the gamma function. +
+
PolyGamma[z] +
returns the digamma function. + +
PolyGamma[n,z] +
gives the n^(th) derivative of the digamma function. +
+ + >> PolyGamma[5] + = 25 / 12 - EulerGamma + + >> PolyGamma[3, 5] + = -22369 / 3456 + Pi ^ 4 / 15 + """ + + attributes = ("Listable", "NumericFunction", "Protected") + + mpmath_names = { + 1: "digamma", # 1 argument + 2: "psi", + } + + summary_text = "PolyGamma function" + + sympy_names = {1: "digamma", 2: "polygamma"} # 1 argument + + +class StieltjesGamma(SympyFunction): + r""" + PolyGamma is a meromorphic function on the complex numbers and is defined as a derivative of the logarithm of the gamma function. +
+
'StieltjesGamma[$n$]' +
returns the Stieljs contstant for $n$. + +
'StieltjesGamma[$n$, $a$]' +
gives the generalized Stieltjes constant of its parameters +
+ + ## Todo... + ## >> N[StieltjesGamma[1], 50] + ## = ... + """ + + attributes = ("Listable", "NumericFunction", "Protected") + + summary_text = "Stieltjes function" + sympy_name = "stieltjes" From f2214c8782d4bdf88e801eb7b6c245c5e6b2c0d7 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 12 Aug 2021 08:41:39 -0400 Subject: [PATCH 032/193] WIP - redo BezierCurveBox --- mathics/builtin/box/graphics.py | 11 +++++++++++ mathics/format/asy.py | 31 ++++++++++++++++++++++++------- mathics/format/asy_fns.py | 23 +++++++++++++++++++++++ mathics/format/svg.py | 7 +++++-- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index 4fc62e255..4002fb979 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -651,6 +651,9 @@ def boxes_to_mathml(self, leaves=None, **options) -> str: return mathml def boxes_to_svg(self, leaves=None, **options) -> str: + """This is the top-level function that converts a Mathics Expression + in to something suitable for SVG rendering. + """ if not leaves: leaves = self._leaves @@ -664,6 +667,14 @@ def boxes_to_svg(self, leaves=None, **options) -> str: return svg_body def boxes_to_tex(self, leaves=None, **options) -> str: + """This is the top-level function that converts a Mathics Expression + in to something suitable for LaTeX. (Yes, the name "tex" is + perhaps misleading of vague.) + + However right now the only LaTeX support for graphics is via Asymptote and + that seems to be the package of choice in general for LaTeX. + """ + if not leaves: leaves = self._leaves fields = self._prepare_elements(leaves, options, max_width=450) diff --git a/mathics/format/asy.py b/mathics/format/asy.py index e8b7aabd5..fab2fc3b0 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - """ Format a Mathics object as an Asymptote string """ @@ -40,7 +39,14 @@ from mathics.core.formatter import lookup_method, add_conversion_fn -from mathics.format.asy_fns import asy_bezier, asy_color, asy_create_pens, asy_number +from mathics.format.asy_fns import ( + asy_add_bezier_fn, + asy_add_graph_import, + asy_bezier, + asy_color, + asy_create_pens, + asy_number, +) class _ASYTransform: @@ -63,6 +69,8 @@ def matrix(self, a, b, c, d, e, f): # b d f # 0 0 1 # see http://asymptote.sourceforge.net/doc/Transforms.html#Transforms + # Note that the values a..f go down the rows and then across the columns + # and not across the columns and then down the rows self.transforms.append("(%f, %f, %f, %f, %f, %f)" % (e, f, a, c, b, d)) def translate(self, x, y): @@ -186,15 +194,24 @@ def arrow3dbox(self, **options) -> str: def bezier_curve_box(self, **options) -> str: + """ + Asymptote formatter for BezerCurveBox. + """ line_width = self.style.get_line_width(face_element=False) pen = asy_create_pens(edge_color=self.edge_color, stroke_width=line_width) asy = "// BezierCurveBox\n" - for line in self.lines: - for path in asy_bezier((self.spline_degree, [xy.pos() for xy in line])): - if path[:2] == "..": - path = "(0.,0.)" + path - asy += "draw(%s, %s);" % (path, pen) + asy += asy_add_graph_import(self) + asy += asy_add_bezier_fn(self) + for i, line in enumerate(self.lines): + scaled_pts = [] + for xy in line: + x, y = xy.pos() + scaled_pts.append(str((x / 100.0, y / 100.0))) + asy += """pair[] P%d={%s};\n""" % (i, ", ".join(scaled_pts)) + asy += """pair G%d(real t){return Bezier(P%d,t);}\n""" % (i, i) + asy += """draw(shift(0, -2)*graph(G%d,0,1,350), %s);\n""" % (i, pen) + # print("BezierCurveBox: " asy) return asy diff --git a/mathics/format/asy_fns.py b/mathics/format/asy_fns.py index 297ba1189..90acd51e0 100644 --- a/mathics/format/asy_fns.py +++ b/mathics/format/asy_fns.py @@ -4,6 +4,29 @@ from itertools import chain +def asy_add_bezier_fn(self) -> str: + if hasattr(self.graphics, "bezier_fn_added") and self.graphics.bezier_fn_added: + return "" + self.graphics.graph_import_added = True + return """ +pair Bezier(pair P[], real t) +{ // https://tex.stackexchange.com/a/554290/236162 + pair Bezi; + for (int k=0; k <= P.length-1; ++k) + { + Bezi=Bezi+choose(P.length-1,k)*(1-t)^(P.length-1-k)*t^k*P[k]; + } + return Bezi; +} + +""" + + +def asy_add_graph_import(self) -> str: + if hasattr(self.graphics, "bezier_import_added") and self.graph_import_added: + return "" + self.graphics.graph_import_added = True + return 'import graph;\n\n' def asy_bezier(*segments): # see http://asymptote.sourceforge.net/doc/Bezier-curves.html#Bezier-curves diff --git a/mathics/format/svg.py b/mathics/format/svg.py index 483ca0a9b..4abc0635f 100644 --- a/mathics/format/svg.py +++ b/mathics/format/svg.py @@ -135,7 +135,10 @@ def polygon(points): add_conversion_fn(ArrowBox, arrow_box) -def beziercurvebox(self, **options) -> str: +def bezier_curve_box(self, **options) -> str: + """ + Asymptote formatter for BezerCurveBox. + """ line_width = self.style.get_line_width(face_element=False) style = create_css(edge_color=self.edge_color, stroke_width=line_width) @@ -147,7 +150,7 @@ def beziercurvebox(self, **options) -> str: return svg -add_conversion_fn(BezierCurveBox) +add_conversion_fn(BezierCurveBox, bezier_curve_box) def density_plot_box(self, **options): From 29d00e3905724269a3331600ac1e8348e2d02003 Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 13 Aug 2021 06:10:29 -0400 Subject: [PATCH 033/193] Fixes to BezierCurve and tweaks to examples * No need to translate points in BezierCurve. * Use blue lines for control-points in examples * Add BezierCurve summary text --- CHANGES.rst | 3 ++- mathics/builtin/drawing/splines.py | 22 ++++++++++++++++------ mathics/format/asy.py | 5 +---- mathics/format/asy_fns.py | 4 +++- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2e794f145..8ef75c3aa 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -34,7 +34,8 @@ Gamma functions: Bugs ++++ -Fix and document better behavior of ``Quantile``. +* Fix and document better behavior of ``Quantile`` +* Improve ``BezierCurve`` 4.0.0 ----- diff --git a/mathics/builtin/drawing/splines.py b/mathics/builtin/drawing/splines.py index 70cf82b0e..c74e0a058 100644 --- a/mathics/builtin/drawing/splines.py +++ b/mathics/builtin/drawing/splines.py @@ -68,8 +68,8 @@ class BezierFunction(Builtin): class BezierCurve(Builtin): """
-
'BezierCurve[{$pt_1$, $pt_2$ ...}]' -
represents a Bézier curve with control points $p_i$. +
'BezierCurve[{$pt_1$, $pt_2$ ...}]' +
represents a Bézier curve with control points $p_i$.
Option: @@ -78,12 +78,20 @@ class BezierCurve(Builtin): - Set up some points... - >> pts = {{0, 0}, {1, 1}, {2, -1}, {3, 0}, {5, 2}, {6, -1}, {7, 3}}; + Set up some points to form a simple zig-zag... + >> pts = {{0, 0}, {1, 1}, {2, -1}, {3, 0}}; = - A composite Bézier curve and its control points: - >> Graphics[{BezierCurve[pts], Green, Line[pts], Red, Point[pts]}] + A composite Bézier curve and its control points: + >> Graphics[{BezierCurve[pts], Blue, Line[pts], Red, Point[pts]}] + = -Graphics- + + Extend points... + >> pts = {{0, 0}, {1, 1}, {2, -1}, {3, 0} {5, 2}, {6, -1}, {7, 3}}; + = + + A longer composite Bézier curve and its control points: + >> Graphics[{BezierCurve[pts], Blue, Line[pts], Red, Point[pts]}] = -Graphics- #> Clear[pts]; @@ -91,3 +99,5 @@ class BezierCurve(Builtin): """ options = {"SplineDegree": "3"} + + summary_text = "composite Bézier curve" diff --git a/mathics/format/asy.py b/mathics/format/asy.py index fab2fc3b0..3ad1e2cec 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -204,10 +204,7 @@ def bezier_curve_box(self, **options) -> str: asy += asy_add_graph_import(self) asy += asy_add_bezier_fn(self) for i, line in enumerate(self.lines): - scaled_pts = [] - for xy in line: - x, y = xy.pos() - scaled_pts.append(str((x / 100.0, y / 100.0))) + scaled_pts = [str(xy.pos()) for xy in line] asy += """pair[] P%d={%s};\n""" % (i, ", ".join(scaled_pts)) asy += """pair G%d(real t){return Bezier(P%d,t);}\n""" % (i, i) asy += """draw(shift(0, -2)*graph(G%d,0,1,350), %s);\n""" % (i, pen) diff --git a/mathics/format/asy_fns.py b/mathics/format/asy_fns.py index 90acd51e0..44f81dce6 100644 --- a/mathics/format/asy_fns.py +++ b/mathics/format/asy_fns.py @@ -4,6 +4,7 @@ from itertools import chain + def asy_add_bezier_fn(self) -> str: if hasattr(self.graphics, "bezier_fn_added") and self.graphics.bezier_fn_added: return "" @@ -26,7 +27,8 @@ def asy_add_graph_import(self) -> str: if hasattr(self.graphics, "bezier_import_added") and self.graph_import_added: return "" self.graphics.graph_import_added = True - return 'import graph;\n\n' + return "import graph;\n\n" + def asy_bezier(*segments): # see http://asymptote.sourceforge.net/doc/Bezier-curves.html#Bezier-curves From 2ff44a37c78ce46814e0b89ce252591467d0ef58 Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 13 Aug 2021 06:10:29 -0400 Subject: [PATCH 034/193] Fixes to BezierCurve and tweaks to examples * No need to translate points in BezierCurve. * Use blue lines for control-points in examples * Add BezierCurve summary text --- CHANGES.rst | 3 ++- mathics/builtin/drawing/splines.py | 22 ++++++++++++++++------ mathics/format/asy.py | 5 +---- mathics/format/asy_fns.py | 4 +++- test/test_formatter/test_asy.py | 2 +- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2e794f145..8ef75c3aa 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -34,7 +34,8 @@ Gamma functions: Bugs ++++ -Fix and document better behavior of ``Quantile``. +* Fix and document better behavior of ``Quantile`` +* Improve ``BezierCurve`` 4.0.0 ----- diff --git a/mathics/builtin/drawing/splines.py b/mathics/builtin/drawing/splines.py index 70cf82b0e..c74e0a058 100644 --- a/mathics/builtin/drawing/splines.py +++ b/mathics/builtin/drawing/splines.py @@ -68,8 +68,8 @@ class BezierFunction(Builtin): class BezierCurve(Builtin): """
-
'BezierCurve[{$pt_1$, $pt_2$ ...}]' -
represents a Bézier curve with control points $p_i$. +
'BezierCurve[{$pt_1$, $pt_2$ ...}]' +
represents a Bézier curve with control points $p_i$.
Option: @@ -78,12 +78,20 @@ class BezierCurve(Builtin): - Set up some points... - >> pts = {{0, 0}, {1, 1}, {2, -1}, {3, 0}, {5, 2}, {6, -1}, {7, 3}}; + Set up some points to form a simple zig-zag... + >> pts = {{0, 0}, {1, 1}, {2, -1}, {3, 0}}; = - A composite Bézier curve and its control points: - >> Graphics[{BezierCurve[pts], Green, Line[pts], Red, Point[pts]}] + A composite Bézier curve and its control points: + >> Graphics[{BezierCurve[pts], Blue, Line[pts], Red, Point[pts]}] + = -Graphics- + + Extend points... + >> pts = {{0, 0}, {1, 1}, {2, -1}, {3, 0} {5, 2}, {6, -1}, {7, 3}}; + = + + A longer composite Bézier curve and its control points: + >> Graphics[{BezierCurve[pts], Blue, Line[pts], Red, Point[pts]}] = -Graphics- #> Clear[pts]; @@ -91,3 +99,5 @@ class BezierCurve(Builtin): """ options = {"SplineDegree": "3"} + + summary_text = "composite Bézier curve" diff --git a/mathics/format/asy.py b/mathics/format/asy.py index fab2fc3b0..3ad1e2cec 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -204,10 +204,7 @@ def bezier_curve_box(self, **options) -> str: asy += asy_add_graph_import(self) asy += asy_add_bezier_fn(self) for i, line in enumerate(self.lines): - scaled_pts = [] - for xy in line: - x, y = xy.pos() - scaled_pts.append(str((x / 100.0, y / 100.0))) + scaled_pts = [str(xy.pos()) for xy in line] asy += """pair[] P%d={%s};\n""" % (i, ", ".join(scaled_pts)) asy += """pair G%d(real t){return Bezier(P%d,t);}\n""" % (i, i) asy += """draw(shift(0, -2)*graph(G%d,0,1,350), %s);\n""" % (i, pen) diff --git a/mathics/format/asy_fns.py b/mathics/format/asy_fns.py index 90acd51e0..44f81dce6 100644 --- a/mathics/format/asy_fns.py +++ b/mathics/format/asy_fns.py @@ -4,6 +4,7 @@ from itertools import chain + def asy_add_bezier_fn(self) -> str: if hasattr(self.graphics, "bezier_fn_added") and self.graphics.bezier_fn_added: return "" @@ -26,7 +27,8 @@ def asy_add_graph_import(self) -> str: if hasattr(self.graphics, "bezier_import_added") and self.graph_import_added: return "" self.graphics.graph_import_added = True - return 'import graph;\n\n' + return "import graph;\n\n" + def asy_bezier(*segments): # see http://asymptote.sourceforge.net/doc/Bezier-curves.html#Bezier-curves diff --git a/test/test_formatter/test_asy.py b/test/test_formatter/test_asy.py index d69b739f7..e3b2e81cb 100644 --- a/test/test_formatter/test_asy.py +++ b/test/test_formatter/test_asy.py @@ -105,7 +105,7 @@ def test_asy_bezier_curve(): asy = get_asy(expression) inner_asy = extract_asy_body(asy) - matches = re.match(r"// BezierCurveBox\ndraw\(.*\)", inner_asy) + matches = re.match(r"// BezierCurveBox\nimport graph;", inner_asy) # TODO: Match line and arrowbox assert matches From 93896ad5028ae26e750e12f68390bb6a0c2d08b4 Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 13 Aug 2021 17:45:34 -0400 Subject: [PATCH 035/193] BezierCurve for Asy is closer to MMA spec Add summary text for all splines. --- mathics/builtin/drawing/splines.py | 59 +++++++++++++++++------------- mathics/format/asy.py | 10 +++-- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/mathics/builtin/drawing/splines.py b/mathics/builtin/drawing/splines.py index c74e0a058..7ec6346d3 100644 --- a/mathics/builtin/drawing/splines.py +++ b/mathics/builtin/drawing/splines.py @@ -2,7 +2,7 @@ """ Splines -Splines are used both in graphics and computations. +A Spline is a mathematical function used for interpolation or smoothing. Splines are used both in graphics and computations """ from mathics.builtin.base import ( @@ -21,7 +21,7 @@ class BernsteinBasis(Builtin):
returns the $n$th Bernstein basis of degree $d$ at $x$.
- A Bernstein polynomial is a polynomial that is a linear combination of Bernstein basis polynomials. + A Bernstein polynomial https://en.wikipedia.org/wiki/Bernstein_polynomial is a polynomial that is a linear combination of Bernstein basis polynomials. With the advent of computer graphics, Bernstein polynomials, restricted to the interval [0, 1], became important in the form of Bézier curves. @@ -36,6 +36,8 @@ class BernsteinBasis(Builtin): "BernsteinBasis[d_, n_, x_]": "Piecewise[{{Binomial[d, n] * x ^ n * (1 - x) ^ (d - n), 0 < x < 1}}, 0]" } + summary_text = "The basis of a Bernstein polynomial used in Bézier curves." + class BezierFunction(Builtin): """ @@ -64,38 +66,45 @@ class BezierFunction(Builtin): "BezierFunction[p_]": "Function[x, Total[p * BernsteinBasis[Length[p] - 1, Range[0, Length[p] - 1], x]]]" } + summary_text = "underlying function used in a Bézier curve" + class BezierCurve(Builtin): - """ -
-
'BezierCurve[{$pt_1$, $pt_2$ ...}]' -
represents a Bézier curve with control points $p_i$. -
+ u""" +
+
'BezierCurve[{$pt_1$, $pt_2$ ...}]' +
represents a Bézier curve with control points $p_i$. +
The result is a curve by combining the Bézier curves when points are taken triples at a time. +
- Option: -
    -
  • 'SplineDegree->$d$' specifies that the underlying polynomial basis should have maximal degree d. -
+ Option: +
    +
  • 'SplineDegree->$d$' specifies that the underlying polynomial basis should have maximal degree d. +
- Set up some points to form a simple zig-zag... - >> pts = {{0, 0}, {1, 1}, {2, -1}, {3, 0}}; - = + Set up some points to form a simple zig-zag... + >> pts = {{0, 0}, {1, 1}, {2, -1}, {3, 0}}; + = - A composite Bézier curve and its control points: - >> Graphics[{BezierCurve[pts], Blue, Line[pts], Red, Point[pts]}] - = -Graphics- + >> Graphics[{Line[pts], Red, Point[pts]}] + = -Graphics- - Extend points... - >> pts = {{0, 0}, {1, 1}, {2, -1}, {3, 0} {5, 2}, {6, -1}, {7, 3}}; - = + A composite Bézier curve, shown in blue, smooths the zig zag. Control points are shown in red: + >> Graphics[{BezierCurve[pts], Blue, Line[pts], Red, Point[pts]}] + = -Graphics- - A longer composite Bézier curve and its control points: - >> Graphics[{BezierCurve[pts], Blue, Line[pts], Red, Point[pts]}] - = -Graphics- + Extend points... + >> pts = {{0, 0}, {1, 1}, {2, -1}, {3, 0}, {5, 2}, {6, -1}, {7, 3}}; + = - #> Clear[pts]; - = + A longer composite Bézier curve and its control points: + >> Graphics[{BezierCurve[pts], Blue, Line[pts], Red, Point[pts]}] + = -Graphics- + + Notice how the curve from the first to third point is not changed by any points outside the interval. The same is true for points three to five, and so on. + + #> Clear[pts]; """ options = {"SplineDegree": "3"} diff --git a/mathics/format/asy.py b/mathics/format/asy.py index 3ad1e2cec..5e966dd45 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -204,10 +204,12 @@ def bezier_curve_box(self, **options) -> str: asy += asy_add_graph_import(self) asy += asy_add_bezier_fn(self) for i, line in enumerate(self.lines): - scaled_pts = [str(xy.pos()) for xy in line] - asy += """pair[] P%d={%s};\n""" % (i, ", ".join(scaled_pts)) - asy += """pair G%d(real t){return Bezier(P%d,t);}\n""" % (i, i) - asy += """draw(shift(0, -2)*graph(G%d,0,1,350), %s);\n""" % (i, pen) + pts = [str(xy.pos()) for xy in line] + for j in range(1, len(pts) - 1, 3): + triple = ", ".join(pts[j - 1 : j + 3]) + asy += """pair[] P%d_%d={%s};\n""" % (i, j, triple) + asy += """pair G%d_%d(real t){return Bezier(P%d_%d,t);}\n""" % (i, j, i, j) + asy += """draw(shift(0, -2)*graph(G%d_%d,0,1,350), %s);\n""" % (i, j, pen) # print("BezierCurveBox: " asy) return asy From 6a4cbee5338507d17cf6f4e05df3e7cdc550ce63 Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 13 Aug 2021 17:52:44 -0400 Subject: [PATCH 036/193] Update CHANGES.rst --- CHANGES.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 8ef75c3aa..0f6f94c90 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -30,12 +30,11 @@ Gamma functions: * ``Stieltjes`` - Bugs ++++ * Fix and document better behavior of ``Quantile`` -* Improve ``BezierCurve`` +* Improve Asymptote ``BezierCurve``implementation 4.0.0 ----- From e6052216680054c71136f2da5aa98b4cc7f985a2 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 29 Jul 2021 03:41:03 -0400 Subject: [PATCH 037/193] WIP - first cut at UniformPolyhedra --- mathics/builtin/box/uniform_polyhedra.py | 13 +++++ mathics/builtin/drawing/graphics3d.py | 17 +++---- mathics/builtin/drawing/uniform_polyhedra.py | 52 ++++++++++++++++++++ mathics/format/json.py | 18 +++++++ 4 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 mathics/builtin/box/uniform_polyhedra.py create mode 100644 mathics/builtin/drawing/uniform_polyhedra.py diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py new file mode 100644 index 000000000..b8d644601 --- /dev/null +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -0,0 +1,13 @@ +from mathics.builtin.box.graphics3d import _Graphics3DElement + +from mathics.builtin.drawing.graphics_internals import GLOBALS3D + +class UniformPolyhedron3DBox(_Graphics3DElement): + pass + +# FIXME: GLOBALS3D is a horrible name. +GLOBALS3D.update( + { + "System`UniformPolyhedron3DBox": UniformPolyhedron3DBox, + } +) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index c0711da83..665d18709 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -63,6 +63,14 @@ def get_default_face_color(self): return RGBColor(components=(1, 1, 1, 1)) +class _Graphics3DElement(InstanceableBuiltin): + def init(self, graphics, item=None, style=None): + if item is not None and not item.has_form(self.get_name(), None): + raise BoxConstructError + self.graphics = graphics + self.style = style + self.is_completely_visible = False # True for axis elements + class Graphics3D(Graphics): r"""
@@ -303,12 +311,3 @@ def apply_check(self, positions, radius, evaluation): evaluation.error("Cylinder", "oddn", positions) return Expression("Cylinder", positions, radius) - - -class _Graphics3DElement(InstanceableBuiltin): - def init(self, graphics, item=None, style=None): - if item is not None and not item.has_form(self.get_name(), None): - raise BoxConstructError - self.graphics = graphics - self.style = style - self.is_completely_visible = False # True for axis elements diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py new file mode 100644 index 000000000..c07f9b9de --- /dev/null +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +""" +Uniform Polyhedra + +Uniform polyhedra is the grouping of platonic solids, Archimedean solids, and regular star polyhedra. +""" + +from mathics.version import __version__ # noqa used in loading to check consistency. + +from mathics.builtin.base import Builtin +from mathics.core.expression import Expression + +uniform_polyhedra_names = "tetrahedron, octahedron, dodecahedron, icosahedron" +uniform_polyhedra_set = frozenset(uniform_polyhedra_names.split(", ")) + +class UniformPolyhedron(Builtin): + """ +
+
'UniformPolyhedron["name"]' +
is a uniform polyhedron with the given name. +
+ + >> Graphics3D[UniformPolyhedron["tetrahedron"] + = -Graphics3D- + + >> Graphics3D[UniformPolyhedron["octahedron"] + = -Graphics3D- + """ + + messages = { + "argtype": f"Argument `1` is not one of: {uniform_polyhedra_names}", + } + + + def apply_with_name(self, name, evaluation): + "UniformPolyhedron[name_String]" + + return Expression("UniformPolyhedron", name) + + +class Tetrahedron(Builtin): + """ +
+
'Tetrahedron[]' +
a regular tetrahedron centered at the origin with unit edge length. +
+ + >> Graphics3D[Tetrahedraon[]] + = -Graphics3D- + """ + rules = {"Tetrahedron[]": """UniformPolyhedron["tetrahedron"]"""} diff --git a/mathics/format/json.py b/mathics/format/json.py index c5aab3fb4..7e3ecfb8d 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -19,6 +19,8 @@ Sphere3DBox, ) +from mathics.builtin.box.uniform_polyhedra import UniformPolyhedron3DBox + # FIXME # Add 2D elements like DensityPlot @@ -208,3 +210,19 @@ def sphere_3d_box(self) -> list: add_conversion_fn(Sphere3DBox, sphere_3d_box) + + +def uniform_polyhedron_3d_box(self) -> list: + face_color = self.face_color + if face_color is not None: + face_color = face_color.to_js() + data = convert_coord_collection( + [0, 0, 0], + "sphere", # + face_color, + {"faceColor": face_color, "radius": 1}, + ) + print("### json UniformPolyhedron3DBox", data) + return data + +add_conversion_fn(UniformPolyhedron3DBox, uniform_polyhedron_3d_box) From 45643c21711675bc0b487a5e717776eb146107c4 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 14 Aug 2021 07:04:28 -0400 Subject: [PATCH 038/193] First working version. Work remains to sets center point and "radius". Also, UniformPolyhedron doesn't take the above parameters directly while the named versions, e.g. Octahedron do allow these parameters - work out how we want to represent this then. --- CHANGES.rst | 6 ++++ mathics/builtin/box/uniform_polyhedra.py | 20 ++++++++++- mathics/builtin/drawing/graphics3d.py | 7 +++- mathics/builtin/drawing/uniform_polyhedra.py | 38 +++++++++++++++++--- mathics/builtin/graphics.py | 1 + mathics/format/json.py | 18 ++++++---- 6 files changed, 77 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0f6f94c90..6f066a344 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,6 +29,12 @@ Gamma functions: * ``PolyGamma`` * ``Stieltjes`` +Uniform Polyhedron +* ``UniformPolyedron`` +* ``Dodecahedron`` +* ``Octahedron`` +* ``TetraHedron`` + Bugs ++++ diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py index b8d644601..fce2c1507 100644 --- a/mathics/builtin/box/uniform_polyhedra.py +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -1,9 +1,27 @@ from mathics.builtin.box.graphics3d import _Graphics3DElement from mathics.builtin.drawing.graphics_internals import GLOBALS3D +from mathics.builtin.colors.color_directives import _Color + class UniformPolyhedron3DBox(_Graphics3DElement): - pass + def init(self, graphics, style, item): + super(UniformPolyhedron3DBox, self).init(graphics, item, style) + self.edge_color, self.face_color = style.get_style(_Color, face_element=True) + if len(item.leaves) != 1: + raise BoxConstructError + self.sub_type = item.leaves[0].to_python(string_quotes=False) + + def extent(self): + # FIXME: figure this out. + min_point = [0, 0, 0] + max_point = [100, 100, 100] + return [min_point, max_point] + + def _apply_boxscaling(self, boxscale): + # No box scaling for now + return + # FIXME: GLOBALS3D is a horrible name. GLOBALS3D.update( diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 665d18709..219e5566f 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -65,12 +65,17 @@ def get_default_face_color(self): class _Graphics3DElement(InstanceableBuiltin): def init(self, graphics, item=None, style=None): - if item is not None and not item.has_form(self.get_name(), None): + if ( + item is not None + and hasattr(item, "has_form") + and not item.has_form(self.get_name(), None) + ): raise BoxConstructError self.graphics = graphics self.style = style self.is_completely_visible = False # True for axis elements + class Graphics3D(Graphics): r"""
diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py index c07f9b9de..63f4aa2a3 100644 --- a/mathics/builtin/drawing/uniform_polyhedra.py +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -14,17 +14,19 @@ uniform_polyhedra_names = "tetrahedron, octahedron, dodecahedron, icosahedron" uniform_polyhedra_set = frozenset(uniform_polyhedra_names.split(", ")) + class UniformPolyhedron(Builtin): """
'UniformPolyhedron["name"]' -
is a uniform polyhedron with the given name. +
return a uniform polyhedron with the given name. +
Names are "tetrahedron", "octahedron", "dodecahedron", or "icosahedron".
- >> Graphics3D[UniformPolyhedron["tetrahedron"] + >> Graphics3D[UniformPolyhedron["tetrahedron"]] = -Graphics3D- - >> Graphics3D[UniformPolyhedron["octahedron"] + >> Graphics3D[UniformPolyhedron["octahedron"]] = -Graphics3D- """ @@ -32,13 +34,40 @@ class UniformPolyhedron(Builtin): "argtype": f"Argument `1` is not one of: {uniform_polyhedra_names}", } - def apply_with_name(self, name, evaluation): "UniformPolyhedron[name_String]" return Expression("UniformPolyhedron", name) +class Dodecahedron(Builtin): + """ +
+
'Dodecahedron[]' +
a regular dodecahedron centered at the origin with unit edge length. +
+ + >> Graphics3D[Dodecahedron[]] + = -Graphics3D- + """ + + rules = {"Dodecahedron[]": """UniformPolyhedron["dodecahedron"]"""} + + +class Octahedron(Builtin): + """ +
+
'Octahedron[]' +
a regular octahedron centered at the origin with unit edge length. +
+ + >> Graphics3D[Octahedron[]] + = -Graphics3D- + """ + + rules = {"Octahedron[]": """UniformPolyhedron["octahedron"]"""} + + class Tetrahedron(Builtin): """
@@ -49,4 +78,5 @@ class Tetrahedron(Builtin): >> Graphics3D[Tetrahedraon[]] = -Graphics3D- """ + rules = {"Tetrahedron[]": """UniformPolyhedron["tetrahedron"]"""} diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index d0d51d192..79fcb5687 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -1388,6 +1388,7 @@ class Large(Builtin): "Sphere", "Style", "Text", + "UniformPolyhedron", ) ) diff --git a/mathics/format/json.py b/mathics/format/json.py index 7e3ecfb8d..7c8b7e7ee 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -216,13 +216,17 @@ def uniform_polyhedron_3d_box(self) -> list: face_color = self.face_color if face_color is not None: face_color = face_color.to_js() - data = convert_coord_collection( - [0, 0, 0], - "sphere", # - face_color, - {"faceColor": face_color, "radius": 1}, - ) - print("### json UniformPolyhedron3DBox", data) + data = [ + { + "type": "uniformPolyhedron", + "color": face_color[:3], + "faceColor": face_color, + "coords": [[[0, 0, 0]]], + "subType": self.sub_type, + } + ] + # print("### json UniformPolyhedron3DBox", data) return data + add_conversion_fn(UniformPolyhedron3DBox, uniform_polyhedron_3d_box) From ae3c7ec8eca5da9b7a1ae18e1995d4afb5d37557 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 14 Aug 2021 12:31:51 -0400 Subject: [PATCH 039/193] Closer, but still far no edge lengths and centers --- CHANGES.rst | 3 ++- mathics/builtin/box/uniform_polyhedra.py | 9 ++++++++- mathics/builtin/drawing/uniform_polyhedra.py | 19 ++++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6f066a344..ddcd5a3d5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -30,10 +30,11 @@ Gamma functions: * ``Stieltjes`` Uniform Polyhedron -* ``UniformPolyedron`` * ``Dodecahedron`` +* ``Isohedron`` * ``Octahedron`` * ``TetraHedron`` +* ``UniformPolyedron`` Bugs diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py index fce2c1507..67dda28ee 100644 --- a/mathics/builtin/box/uniform_polyhedra.py +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -1,15 +1,22 @@ from mathics.builtin.box.graphics3d import _Graphics3DElement +from mathics.builtin.base import BoxConstructError from mathics.builtin.drawing.graphics_internals import GLOBALS3D from mathics.builtin.colors.color_directives import _Color +from mathics.builtin.drawing.uniform_polyhedra import uniform_polyhedra_set + class UniformPolyhedron3DBox(_Graphics3DElement): def init(self, graphics, style, item): super(UniformPolyhedron3DBox, self).init(graphics, item, style) self.edge_color, self.face_color = style.get_style(_Color, face_element=True) if len(item.leaves) != 1: - raise BoxConstructError + raise BoxConstructError("Expecting a Polyhedron name") + sub_type = item.leaves[0].to_python(string_quotes=False) + if sub_type not in uniform_polyhedra_set: + raise BoxConstructError(f"Polyhedron name {sub_type} is not one know") + self.sub_type = item.leaves[0].to_python(string_quotes=False) def extent(self): diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py index 63f4aa2a3..98fb16017 100644 --- a/mathics/builtin/drawing/uniform_polyhedra.py +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -54,6 +54,20 @@ class Dodecahedron(Builtin): rules = {"Dodecahedron[]": """UniformPolyhedron["dodecahedron"]"""} +class Icosohedron(Builtin): + """ +
+
'Icosohedron[]' +
a regular Icosohedron centered at the origin with unit edge length. +
+ + >> Graphics3D[Icosohedron[]] + = -Graphics3D- + """ + + rules = {"Iscosohedron[]": """UniformPolyhedron["icosohedron"]"""} + + class Octahedron(Builtin): """
@@ -75,8 +89,11 @@ class Tetrahedron(Builtin):
a regular tetrahedron centered at the origin with unit edge length.
- >> Graphics3D[Tetrahedraon[]] + >> Graphics3D[Tetrahedron[]] = -Graphics3D- """ rules = {"Tetrahedron[]": """UniformPolyhedron["tetrahedron"]"""} + + def apply_with_length(self, length, evaluation): + "Tetrahedron[l_?Numeric]" From c47a6f06711179aa0de5eef355bf9caff9d789ce Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 14 Aug 2021 12:41:15 -0400 Subject: [PATCH 040/193] "faceColor" -> "color" --- mathics/format/json.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mathics/format/json.py b/mathics/format/json.py index 7c8b7e7ee..356928a25 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -116,7 +116,7 @@ def cylinder_3d_box(self): [self.points], "cylinder", face_color, - {"faceColor": face_color, "radius": self.radius}, + {"color": face_color, "radius": self.radius}, ) # print("### json Cylinder3DBox", data) return data @@ -186,7 +186,7 @@ def polygon_3d_box(self) -> list: self.lines, "polygon", face_color, - {"faceColor": face_color}, + {"color": face_color}, ) # print("### json Polygon3DBox", data) return data @@ -203,7 +203,7 @@ def sphere_3d_box(self) -> list: [self.points], "sphere", face_color, - {"faceColor": face_color, "radius": self.radius}, + {"color": face_color, "radius": self.radius}, ) # print("### json Sphere3DBox", data) return data @@ -220,7 +220,6 @@ def uniform_polyhedron_3d_box(self) -> list: { "type": "uniformPolyhedron", "color": face_color[:3], - "faceColor": face_color, "coords": [[[0, 0, 0]]], "subType": self.sub_type, } From 7ed66d4eac7ec863f91ddb7e152d82eb4cb591ee Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 14 Aug 2021 12:48:02 -0400 Subject: [PATCH 041/193] typo: Isohedron -> Icosohedron --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index ddcd5a3d5..4df61c7ec 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -31,7 +31,7 @@ Gamma functions: Uniform Polyhedron * ``Dodecahedron`` -* ``Isohedron`` +* ``Icosohedron`` * ``Octahedron`` * ``TetraHedron`` * ``UniformPolyedron`` From 93a43825aeadc5fc90b8c681de8fba098575fbc9 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 14 Aug 2021 15:22:35 -0300 Subject: [PATCH 042/193] Rename `rgb_color` to `color` --- mathics/format/json.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/mathics/format/json.py b/mathics/format/json.py index 356928a25..64383e7f4 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -44,7 +44,7 @@ def convert_coord_collection( "type": object_type, "coords": [coords.pos() for coords in items], "opacity": opacity, - "rgb_color": color[:3], + "color": color[:3], }, } ) @@ -77,7 +77,7 @@ def arrow_3d_box(self): """ # TODO: account for arrow widths and style color = self.edge_color.to_rgba() - data = convert_coord_collection(self.lines, "arrow", color, {"color": color}) + data = convert_coord_collection(self.lines, "arrow", color) # print("### json Arrow3DBox", data) return data @@ -96,7 +96,6 @@ def cuboid_3d_box(self): [self.points], "cuboid", face_color, - {"color": face_color}, ) # print("### json Cuboid3DBox", data) return data @@ -116,7 +115,7 @@ def cylinder_3d_box(self): [self.points], "cylinder", face_color, - {"color": face_color, "radius": self.radius}, + {"radius": self.radius}, ) # print("### json Cylinder3DBox", data) return data @@ -132,7 +131,7 @@ def line_3d_box(self): # TODO: account for line widths and style data = [] color = self.edge_color.to_rgba() - data = convert_coord_collection(self.lines, "line", color, {"color": color}) + data = convert_coord_collection(self.lines, "line", color) # print("### json Line3DBox", data) return data @@ -159,7 +158,7 @@ def point_3d_box(self) -> list: self.lines, "point", face_color, - {"color": face_color, "pointSize": relative_point_size * 0.5}, + {"pointSize": relative_point_size * 0.5}, ) # print("### json Point3DBox", data) @@ -186,7 +185,6 @@ def polygon_3d_box(self) -> list: self.lines, "polygon", face_color, - {"color": face_color}, ) # print("### json Polygon3DBox", data) return data @@ -203,7 +201,7 @@ def sphere_3d_box(self) -> list: [self.points], "sphere", face_color, - {"color": face_color, "radius": self.radius}, + {"radius": self.radius}, ) # print("### json Sphere3DBox", data) return data From b89535a6ce3199e9740a22414f301ce39976c80a Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 14 Aug 2021 15:45:37 -0300 Subject: [PATCH 043/193] Convert 3d primitives's for loop to nested list --- mathics/builtin/box/graphics3d.py | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index fe48c562c..88a2cf277 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -701,11 +701,7 @@ def process_option(self, name, value): super(Arrow3DBox, self).process_option(name, value) def extent(self): - result = [] - for line in self.lines: - for c in line: - p, d = c.pos() - result.append(p) + result = [coordinate.pos()[0] for line in self.lines for coordinate in line] return result def _apply_boxscaling(self, boxscale): @@ -798,11 +794,7 @@ def process_option(self, name, value): super(Line3DBox, self).process_option(name, value) def extent(self): - result = [] - for line in self.lines: - for c in line: - p, d = c.pos() - result.append(p) + result = [coordinate.pos()[0] for line in self.lines for coordinate in line] return result def _apply_boxscaling(self, boxscale): @@ -819,11 +811,7 @@ def process_option(self, name, value): super(Point3DBox, self).process_option(name, value) def extent(self): - result = [] - for line in self.lines: - for c in line: - p, d = c.pos() - result.append(p) + result = [coordinate.pos()[0] for line in self.lines for coordinate in line] return result def _apply_boxscaling(self, boxscale): @@ -845,11 +833,7 @@ def process_option(self, name, value): super(Polygon3DBox, self).process_option(name, value) def extent(self): - result = [] - for line in self.lines: - for c in line: - p, d = c.pos() - result.append(p) + result = [coordinate.pos()[0] for line in self.lines for coordinate in line] return result def _apply_boxscaling(self, boxscale): From 417330e9b82d17e576da2c9040512cff73381530 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 14 Aug 2021 15:56:10 -0300 Subject: [PATCH 044/193] Convert `convert_coord_collection`'s for loop to nested list --- mathics/format/json.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/mathics/format/json.py b/mathics/format/json.py index 64383e7f4..09f907d1c 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -34,20 +34,19 @@ def convert_coord_collection( """Convert collection into a list of dictionary items where each item is some sort of lower-level JSON object. """ - data = [] opacity = 1 if len(color) < 4 else color[3] - for items in collection: - data.append( - { - **default_values, - **{ - "type": object_type, - "coords": [coords.pos() for coords in items], - "opacity": opacity, - "color": color[:3], - }, - } - ) + data = [ + { + **default_values, + **{ + "type": object_type, + "coords": [coords.pos() for coords in items], + "opacity": opacity, + "color": color[:3], + }, + } for items in collection + ] + # print(data) return data From 31c03ed736e8d8c93c8f74a5b9646ab8adf8f5f1 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 14 Aug 2021 15:58:13 -0300 Subject: [PATCH 045/193] Remove the use of the old to_json --- mathics/format/json.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mathics/format/json.py b/mathics/format/json.py index 09f907d1c..49c143416 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -58,10 +58,7 @@ def graphics_3D_elements(self, **options) -> list: result = [] for element in self.elements: format_fn = lookup_method(element, "json") - if format_fn is None: - result += element.to_json() - else: - result += format_fn(element) + result += format_fn(element) # print("### json Graphics3DElements", result) return result From 4983537cd2b14f1813d8e093b9e7bfd88dcb7462 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 14 Aug 2021 16:00:25 -0300 Subject: [PATCH 046/193] Remove unnecessary variable declaration --- mathics/format/json.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mathics/format/json.py b/mathics/format/json.py index 49c143416..612a929f2 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -44,7 +44,8 @@ def convert_coord_collection( "opacity": opacity, "color": color[:3], }, - } for items in collection + } + for items in collection ] # print(data) @@ -125,7 +126,6 @@ def line_3d_box(self): Compact (lower-level) JSON formatting of a Line3DBox. """ # TODO: account for line widths and style - data = [] color = self.edge_color.to_rgba() data = convert_coord_collection(self.lines, "line", color) # print("### json Line3DBox", data) @@ -140,7 +140,6 @@ def point_3d_box(self) -> list: Compact (lower-level) JSON formatting of a Point3DBox. """ # TODO: account for point size - data = [] # Tempoary bug fix: default Point color should be black not white face_color = self.face_color.to_rgba() From cfdb1b5fdb20c3e16a5db0cbc3c2b63158abca6c Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 14 Aug 2021 16:00:25 -0300 Subject: [PATCH 047/193] Remove unnecessary variable declaration --- mathics/format/json.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mathics/format/json.py b/mathics/format/json.py index 49c143416..612a929f2 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -44,7 +44,8 @@ def convert_coord_collection( "opacity": opacity, "color": color[:3], }, - } for items in collection + } + for items in collection ] # print(data) @@ -125,7 +126,6 @@ def line_3d_box(self): Compact (lower-level) JSON formatting of a Line3DBox. """ # TODO: account for line widths and style - data = [] color = self.edge_color.to_rgba() data = convert_coord_collection(self.lines, "line", color) # print("### json Line3DBox", data) @@ -140,7 +140,6 @@ def point_3d_box(self) -> list: Compact (lower-level) JSON formatting of a Point3DBox. """ # TODO: account for point size - data = [] # Tempoary bug fix: default Point color should be black not white face_color = self.face_color.to_rgba() From 4f51893986bee56b56cb612b8e69ceaa44a3c2c5 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 14 Aug 2021 18:08:24 -0300 Subject: [PATCH 048/193] Use `convert_coord_collection` in `uniform_polyhedron_3d_box` --- mathics/format/json.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/mathics/format/json.py b/mathics/format/json.py index 612a929f2..a1c734477 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -209,14 +209,12 @@ def uniform_polyhedron_3d_box(self) -> list: face_color = self.face_color if face_color is not None: face_color = face_color.to_js() - data = [ - { - "type": "uniformPolyhedron", - "color": face_color[:3], - "coords": [[[0, 0, 0]]], - "subType": self.sub_type, - } - ] + data = convert_coord_collection( + [self.points], + "uniformPolyhedron", + face_color, + {"subType": self.sub_type}, + ) # print("### json UniformPolyhedron3DBox", data) return data From 4bd0c55ff9b125a8b460a7b866889027ccf96852 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 14 Aug 2021 18:14:56 -0300 Subject: [PATCH 049/193] Move polyhedron's name check to right place --- mathics/builtin/box/uniform_polyhedra.py | 5 ++--- mathics/builtin/drawing/uniform_polyhedra.py | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py index 67dda28ee..ff1e781a9 100644 --- a/mathics/builtin/box/uniform_polyhedra.py +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -13,12 +13,11 @@ def init(self, graphics, style, item): self.edge_color, self.face_color = style.get_style(_Color, face_element=True) if len(item.leaves) != 1: raise BoxConstructError("Expecting a Polyhedron name") - sub_type = item.leaves[0].to_python(string_quotes=False) - if sub_type not in uniform_polyhedra_set: - raise BoxConstructError(f"Polyhedron name {sub_type} is not one know") self.sub_type = item.leaves[0].to_python(string_quotes=False) + + def extent(self): # FIXME: figure this out. min_point = [0, 0, 0] diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py index 98fb16017..06ba9acaf 100644 --- a/mathics/builtin/drawing/uniform_polyhedra.py +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -37,7 +37,10 @@ class UniformPolyhedron(Builtin): def apply_with_name(self, name, evaluation): "UniformPolyhedron[name_String]" - return Expression("UniformPolyhedron", name) + if name.to_python(string_quotes=False) not in uniform_polyhedra_set: + evaluation.error("UniformPolyhedron", "argtype", name) + + return Expression("UniformPolyhedron", name, positions, edgelength) class Dodecahedron(Builtin): From f797828cb6d0577995be85e45926df4430f98c2d Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 14 Aug 2021 18:25:11 -0300 Subject: [PATCH 050/193] Fix typo --- mathics/builtin/drawing/uniform_polyhedra.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py index 06ba9acaf..e619d9326 100644 --- a/mathics/builtin/drawing/uniform_polyhedra.py +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -57,18 +57,18 @@ class Dodecahedron(Builtin): rules = {"Dodecahedron[]": """UniformPolyhedron["dodecahedron"]"""} -class Icosohedron(Builtin): +class Icosahedron(Builtin): """
-
'Icosohedron[]' -
a regular Icosohedron centered at the origin with unit edge length. +
'Icosahedron[]' +
a regular Icosahedron centered at the origin with unit edge length.
- >> Graphics3D[Icosohedron[]] + >> Graphics3D[Icosahedron[]] = -Graphics3D- """ - rules = {"Iscosohedron[]": """UniformPolyhedron["icosohedron"]"""} + rules = {"Icosahedron[]": """UniformPolyhedron["icosahedron"]"""} class Octahedron(Builtin): From 008bd95bad0bfac87fdb7b4a34b24804c688c441 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 14 Aug 2021 18:25:50 -0300 Subject: [PATCH 051/193] Do `UniformPolyhedron` works --- mathics/builtin/box/uniform_polyhedra.py | 42 ++++++++++++++++---- mathics/builtin/drawing/uniform_polyhedra.py | 8 +++- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py index ff1e781a9..6e69cebbf 100644 --- a/mathics/builtin/box/uniform_polyhedra.py +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -1,4 +1,4 @@ -from mathics.builtin.box.graphics3d import _Graphics3DElement +from mathics.builtin.box.graphics3d import _Graphics3DElement, Coords3D from mathics.builtin.base import BoxConstructError from mathics.builtin.drawing.graphics_internals import GLOBALS3D @@ -6,23 +6,49 @@ from mathics.builtin.drawing.uniform_polyhedra import uniform_polyhedra_set +import numbers + class UniformPolyhedron3DBox(_Graphics3DElement): def init(self, graphics, style, item): super(UniformPolyhedron3DBox, self).init(graphics, item, style) self.edge_color, self.face_color = style.get_style(_Color, face_element=True) - if len(item.leaves) != 1: - raise BoxConstructError("Expecting a Polyhedron name") - self.sub_type = item.leaves[0].to_python(string_quotes=False) + if len(item.leaves) != 3: + raise BoxConstructError + points = item.leaves[1].to_python() + if not all( + len(point) == 3 and all(isinstance(p, numbers.Real) for p in point) + for point in points + ): + raise BoxConstructError + self.points = [Coords3D(graphics, pos=point) for point in points] + self.edge_length = item.leaves[2].to_python() + self.sub_type = item.leaves[0].to_python(string_quotes=False) def extent(self): - # FIXME: figure this out. - min_point = [0, 0, 0] - max_point = [100, 100, 100] - return [min_point, max_point] + result = [] + + # TODO: correct extent calculation, the current one is approximated + result.extend( + [ + coords.add(self.edge_length, self.edge_length, self.edge_length).pos()[ + 0 + ] + for coords in self.points + ] + ) + result.extend( + [ + coords.add( + -self.edge_length, -self.edge_length, -self.edge_length + ).pos()[0] + for coords in self.points + ] + ) + return result def _apply_boxscaling(self, boxscale): # No box scaling for now diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py index e619d9326..ec8dca189 100644 --- a/mathics/builtin/drawing/uniform_polyhedra.py +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -34,8 +34,12 @@ class UniformPolyhedron(Builtin): "argtype": f"Argument `1` is not one of: {uniform_polyhedra_names}", } - def apply_with_name(self, name, evaluation): - "UniformPolyhedron[name_String]" + rules = { + "UniformPolyhedron[name_String]": "UniformPolyhedron[name, {{0, 0, 0}}, 1]" + } + + def apply(self, name, positions, edgelength, evaluation): + "UniformPolyhedron[name_String, positions_List, edgelength_?NumericQ]" if name.to_python(string_quotes=False) not in uniform_polyhedra_set: evaluation.error("UniformPolyhedron", "argtype", name) From edca3c0daee46b618b653ac6b56c5ab10436943d Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 14 Aug 2021 18:23:26 -0400 Subject: [PATCH 052/193] lint vis flake8. --- mathics/builtin/graphics.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 79fcb5687..94da7ca0c 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -6,7 +6,7 @@ """ -from math import floor, sqrt +from math import sqrt from mathics.version import __version__ # noqa used in loading to check consistency. @@ -23,7 +23,6 @@ from mathics.builtin.colors.color_directives import ( _Color, CMYKColor, - ColorError, GrayLevel, Hue, LABColor, From 2dd5a2a3caf37e63305f12b219ab12473d151ef8 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 14 Aug 2021 19:43:17 -0400 Subject: [PATCH 053/193] Add rules for edge length. More varied examples. --- mathics/builtin/drawing/uniform_polyhedra.py | 37 +++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py index ec8dca189..7665decac 100644 --- a/mathics/builtin/drawing/uniform_polyhedra.py +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -23,10 +23,13 @@ class UniformPolyhedron(Builtin):
Names are "tetrahedron", "octahedron", "dodecahedron", or "icosahedron".
- >> Graphics3D[UniformPolyhedron["tetrahedron"]] + >> Graphics3D[UniformPolyhedron["octahedron"]] = -Graphics3D- - >> Graphics3D[UniformPolyhedron["octahedron"]] + >> Graphics3D[UniformPolyhedron["dodecadron"]] + = -Graphics3D- + + >> Graphics3D[{"Brown", UniformPolyhedron["tetrahedron"]}] = -Graphics3D- """ @@ -35,7 +38,7 @@ class UniformPolyhedron(Builtin): } rules = { - "UniformPolyhedron[name_String]": "UniformPolyhedron[name, {{0, 0, 0}}, 1]" + "UniformPolyhedron[name_String]": "UniformPolyhedron[name, {{0, 0, 0}}, 1]", } def apply(self, name, positions, edgelength, evaluation): @@ -58,7 +61,11 @@ class Dodecahedron(Builtin): = -Graphics3D- """ - rules = {"Dodecahedron[]": """UniformPolyhedron["dodecahedron"]"""} + rules = { + "Dodecahedron[]": """UniformPolyhedron["dodecahedron"]""", + "Dodecahedron[l_?NumericQ]": """UniformPolyhedron["dodecahedron", {{0, 0, 0}}, l]""", + "Dodecahedron[positions_List, l_?NumericQ]": """UniformPolyhedron["dodecahedron", positions, l]""", + } class Icosahedron(Builtin): @@ -72,7 +79,11 @@ class Icosahedron(Builtin): = -Graphics3D- """ - rules = {"Icosahedron[]": """UniformPolyhedron["icosahedron"]"""} + rules = { + "Icosahedron[]": """UniformPolyhedron["icosahedron"]""", + "Icosahedron[l_?NumericQ]": """UniformPolyhedron["icosahedron", {{0, 0, 0}}, l]""", + "Icosahedron[positions_List, l_?NumericQ]": """UniformPolyhedron["icosahedron", positions, l]""", + } class Octahedron(Builtin): @@ -82,11 +93,15 @@ class Octahedron(Builtin):
a regular octahedron centered at the origin with unit edge length.
- >> Graphics3D[Octahedron[]] + >> Graphics3D[{Red, Octahedron[]}] = -Graphics3D- """ - rules = {"Octahedron[]": """UniformPolyhedron["octahedron"]"""} + rules = { + "Octahedron[]": """UniformPolyhedron["octahedron"]""", + "Octahedron[l_?NumericQ]": """UniformPolyhedron["octahedron", {{0, 0, 0}}, l]""", + "Octahedron[positions_List, l_?NumericQ]": """UniformPolyhedron["octahedron", positions, l]""", + } class Tetrahedron(Builtin): @@ -96,11 +111,15 @@ class Tetrahedron(Builtin):
a regular tetrahedron centered at the origin with unit edge length.
- >> Graphics3D[Tetrahedron[]] + >> Graphics3D[Tetrahedron[{{0,0,0}, {1,1,1}}, 2]] = -Graphics3D- """ - rules = {"Tetrahedron[]": """UniformPolyhedron["tetrahedron"]"""} + rules = { + "Tetrahedron[]": """UniformPolyhedron["tetrahedron"]""", + "Tetrahedron[l_?NumericQ]": """UniformPolyhedron["tetrahedron", {{0, 0, 0}}, l]""", + "Tetrahedron[positions_List, l_?NumericQ]": """UniformPolyhedron["tetrahedron", positions, l]""", + } def apply_with_length(self, length, evaluation): "Tetrahedron[l_?Numeric]" From 4e62c4c7c6d105a214261d2157f59e8c9eb2c95a Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 14 Aug 2021 19:51:57 -0400 Subject: [PATCH 054/193] Add axes and correct spelling typo --- mathics/builtin/drawing/uniform_polyhedra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py index 7665decac..80cb355f0 100644 --- a/mathics/builtin/drawing/uniform_polyhedra.py +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -26,7 +26,7 @@ class UniformPolyhedron(Builtin): >> Graphics3D[UniformPolyhedron["octahedron"]] = -Graphics3D- - >> Graphics3D[UniformPolyhedron["dodecadron"]] + >> Graphics3D[UniformPolyhedron["dodecahedron"]] = -Graphics3D- >> Graphics3D[{"Brown", UniformPolyhedron["tetrahedron"]}] @@ -111,7 +111,7 @@ class Tetrahedron(Builtin):
a regular tetrahedron centered at the origin with unit edge length.
- >> Graphics3D[Tetrahedron[{{0,0,0}, {1,1,1}}, 2]] + >> Graphics3D[Tetrahedron[{{0,0,0}, {1,1,1}}, 2], Axes->True] = -Graphics3D- """ From 62d6213a2c05232a1247aec99f5f7dafd43f0e99 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 14 Aug 2021 19:55:02 -0400 Subject: [PATCH 055/193] Another typo --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4df61c7ec..218237cdd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -31,7 +31,7 @@ Gamma functions: Uniform Polyhedron * ``Dodecahedron`` -* ``Icosohedron`` +* ``Icosahedron`` * ``Octahedron`` * ``TetraHedron`` * ``UniformPolyedron`` From 0c111c8a9b5e59b6d68971eee8824310e7dfd212 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 14 Aug 2021 22:09:21 -0400 Subject: [PATCH 056/193] Mark modules that don't get documented. Other small changes to wording. --- mathics/builtin/drawing/__init__.py | 24 +++++-------------- mathics/builtin/drawing/graphics_internals.py | 3 +++ mathics/builtin/drawing/image.py | 2 +- mathics/builtin/drawing/image_internals.py | 3 +++ 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/mathics/builtin/drawing/__init__.py b/mathics/builtin/drawing/__init__.py index ee12289c7..81289d668 100644 --- a/mathics/builtin/drawing/__init__.py +++ b/mathics/builtin/drawing/__init__.py @@ -1,25 +1,13 @@ """ Graphics, Drawing, and Images -Functions like 'Plot' and 'ListPlot' can be used to draw graphs of functions and data. - -Graphics is implemented as a collection of graphics primitives. Primatives are objects like 'Point', 'Line', and 'Polygon' and become elements of a graphics object. - -A graphics object can have directives as well such as 'RGBColor', and 'Thickness'. - -There are several kinds of graphics objects; each kind has a head which identifies its type. - ->> ListPlot[ Table[Prime[n], {n, 20} ]] - = -Graphics- ->> Head[%] - = Graphics ->> Graphics3D[Sphere[]] - = -Graphics3D- ->> Head[%] - = Graphics3D ->> - +Showing something visually can be don in a number of ways: +
    +
  • Starting with complete images and modifiying them. The 'Image' function is in this category. +
  • Use pre-defined 2D or 3D objects like 'Circle' and 'Cuboid' and place them in a coordiate space. +
  • Compute the points of the space using a function. This is done using functions like 'Plot' and 'ListPlot'. +
""" from mathics.version import __version__ # noqa used in loading to check consistency. diff --git a/mathics/builtin/drawing/graphics_internals.py b/mathics/builtin/drawing/graphics_internals.py index 3024fb478..313b266e9 100644 --- a/mathics/builtin/drawing/graphics_internals.py +++ b/mathics/builtin/drawing/graphics_internals.py @@ -10,6 +10,9 @@ BoxConstructError, ) +# Signals to Mathics doc processing not to include this module in its documentation. +no_doc = True + from mathics.core.expression import system_symbols_dict diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index b73de6339..7039afa79 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Image[] and image related functions. +Image[] and image related functions Note that you (currently) need scikit-image installed in order for this module to work. """ diff --git a/mathics/builtin/drawing/image_internals.py b/mathics/builtin/drawing/image_internals.py index 7504eca69..8f44f1075 100644 --- a/mathics/builtin/drawing/image_internals.py +++ b/mathics/builtin/drawing/image_internals.py @@ -3,6 +3,9 @@ """helper functions for images """ +# Signals to Mathics doc processing not to include this module in its documentation. +no_doc = True + from mathics.version import __version__ # noqa used in loading to check consistency. import numpy From b2885a01eeb516d0dc382d0ef272ad41ac37801d Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 15 Aug 2021 12:33:28 -0400 Subject: [PATCH 057/193] Add timing decorator that can be used in profiling --- mathics/core/util.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/mathics/core/util.py b/mathics/core/util.py index f7c808620..17ed8afce 100644 --- a/mathics/core/util.py +++ b/mathics/core/util.py @@ -1,12 +1,46 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import os import re import sys from itertools import chain FORMAT_RE = re.compile(r"\`(\d*)\`") +import time + +# A small, simple timing tool +MIN_ELAPSE_REPORT = int(os.environ.get("MIN_ELAPSE_REPORT", "0")) + + +def timeit(method): + """Add this as a decorator to time parts of the code. + + For example: + @timit + def long_running_function(): + ... + """ + + def timed(*args, **kw): + method_name = method.__name__ + # print(f"{date.today()} {method_name} starts") + ts = time.time() + result = method(*args, **kw) + te = time.time() + elapsed = int((te - ts) * 1000) + if elapsed > MIN_ELAPSE_REPORT: + if "log_time" in kw: + name = kw.get("log_name", method.__name__.upper()) + kw["log_time"][name] = elapsed + else: + print("%r %2.2f ms" % (method_name, elapsed)) + # print(f"{date.today()} {method_name} ends") + return result + + return timed + def interpolate_string(text, get_param) -> str: index = [1] From 0fea8b6d9a49f5ec6caff5abfee4bc6c54153a33 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 21 Aug 2021 10:34:24 -0400 Subject: [PATCH 058/193] Mostly cosmetic revision of numeric.py * Put builtins in alphabetic order * Add summary text for some built ins * No hard line breaks on doc text * Move non-showing test code in Rationalize out to a pytest --- mathics/builtin/numeric.py | 1761 ++++++++++++++++++------------------ test/test_numeric.py | 48 + 2 files changed, 928 insertions(+), 881 deletions(-) diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 0c0923ceb..cd2d0778b 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -26,14 +26,8 @@ from mathics.builtin.base import Builtin, Predefined -from mathics.core.numbers import ( - dps, - convert_int_to_digit_list, - machine_precision, - machine_epsilon, - get_precision, - PrecisionValueError, -) +from mathics.core.convert import from_sympy + from mathics.core.expression import ( Complex, Expression, @@ -51,7 +45,15 @@ SymbolN, from_python, ) -from mathics.core.convert import from_sympy + +from mathics.core.numbers import ( + dps, + convert_int_to_digit_list, + machine_precision, + machine_epsilon, + get_precision, + PrecisionValueError, +) @lru_cache(maxsize=1024) @@ -59,180 +61,6 @@ def log_n_b(py_n, py_b) -> int: return int(mpmath.ceil(mpmath.log(py_n, py_b))) if py_n != 0 and py_n != 1 else 1 -class N(Builtin): - """ -
-
'N[$expr$, $prec$]' -
evaluates $expr$ numerically with a precision of $prec$ digits. -
- >> N[Pi, 50] - = 3.1415926535897932384626433832795028841971693993751 - - >> N[1/7] - = 0.142857 - - >> N[1/7, 5] - = 0.14286 - - You can manually assign numerical values to symbols. - When you do not specify a precision, 'MachinePrecision' is taken. - >> N[a] = 10.9 - = 10.9 - >> a - = a - - 'N' automatically threads over expressions, except when a symbol has - attributes 'NHoldAll', 'NHoldFirst', or 'NHoldRest'. - >> N[a + b] - = 10.9 + b - >> N[a, 20] - = a - >> N[a, 20] = 11; - >> N[a + b, 20] - = 11.000000000000000000 + b - >> N[f[a, b]] - = f[10.9, b] - >> SetAttributes[f, NHoldAll] - >> N[f[a, b]] - = f[a, b] - - The precision can be a pattern: - >> N[c, p_?(#>10&)] := p - >> N[c, 3] - = c - >> N[c, 11] - = 11.000000000 - - You can also use 'UpSet' or 'TagSet' to specify values for 'N': - >> N[d] ^= 5; - However, the value will not be stored in 'UpValues', but - in 'NValues' (as for 'Set'): - >> UpValues[d] - = {} - >> NValues[d] - = {HoldPattern[N[d, MachinePrecision]] :> 5} - >> e /: N[e] = 6; - >> N[e] - = 6. - - Values for 'N[$expr$]' must be associated with the head of $expr$: - >> f /: N[e[f]] = 7; - : Tag f not found or too deep for an assigned rule. - - You can use 'Condition': - >> N[g[x_, y_], p_] := x + y * Pi /; x + y > 3 - >> SetAttributes[g, NHoldRest] - >> N[g[1, 1]] - = g[1., 1] - >> N[g[2, 2]] // InputForm - = 8.283185307179586 - - The precision of the result is no higher than the precision of the input - >> N[Exp[0.1], 100] - = 1.10517 - >> % // Precision - = MachinePrecision - >> N[Exp[1/10], 100] - = 1.105170918075647624811707826490246668224547194737518718792863289440967966747654302989143318970748654 - >> % // Precision - = 100. - >> N[Exp[1.0`20], 100] - = 2.7182818284590452354 - >> % // Precision - = 20. - - #> p=N[Pi,100] - = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 - #> ToString[p] - = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 - - #> N[1.012345678901234567890123, 20] - = 1.0123456789012345679 - - #> N[I, 30] - = 1.00000000000000000000000000000 I - - #> N[1.012345678901234567890123, 50] - = 1.01234567890123456789012 - #> % // Precision - = 24. - """ - - messages = { - "precbd": ("Requested precision `1` is not a " + "machine-sized real number."), - "preclg": ( - "Requested precision `1` is larger than $MaxPrecision. " - + "Using current $MaxPrecision of `2` instead. " - + "$MaxPrecision = Infinity specifies that any precision " - + "should be allowed." - ), - "precsm": ( - "Requested precision `1` is smaller than " - + "$MinPrecision. Using current $MinPrecision of " - + "`2` instead." - ), - } - - rules = { - "N[expr_]": "N[expr, MachinePrecision]", - } - - def apply_with_prec(self, expr, prec, evaluation): - "N[expr_, prec_]" - - try: - d = get_precision(prec, evaluation) - except PrecisionValueError: - return - - if expr.get_head_name() in ("System`List", "System`Rule"): - return Expression( - expr.head, - *[self.apply_with_prec(leaf, prec, evaluation) for leaf in expr.leaves], - ) - - # Special case for the Root builtin - if expr.has_form("Root", 2): - return from_sympy(sympy.N(expr.to_sympy(), d)) - - if isinstance(expr, Number): - return expr.round(d) - - name = expr.get_lookup_name() - if name != "": - nexpr = Expression(SymbolN, expr, prec) - result = evaluation.definitions.get_value( - name, "System`NValues", nexpr, evaluation - ) - if result is not None: - if not result.sameQ(nexpr): - result = Expression(SymbolN, result, prec).evaluate(evaluation) - return result - - if expr.is_atom(): - return expr - else: - attributes = expr.head.get_attributes(evaluation.definitions) - if "System`NHoldAll" in attributes: - eval_range = () - elif "System`NHoldFirst" in attributes: - eval_range = range(1, len(expr.leaves)) - elif "System`NHoldRest" in attributes: - if len(expr.leaves) > 0: - eval_range = (0,) - else: - eval_range = () - else: - eval_range = range(len(expr.leaves)) - head = Expression(SymbolN, expr.head, prec).evaluate(evaluation) - leaves = expr.get_mutable_leaves() - for index in eval_range: - leaves[index] = Expression(SymbolN, leaves[index], prec).evaluate( - evaluation - ) - return Expression(head, *leaves) - - def _scipy_interface(integrator, options_map, mandatory=None, adapt_func=None): """ This function provides a proxy for scipy.integrate @@ -379,47 +207,745 @@ def ff(*z): return val -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. -
+def check_finite_decimal(denominator): + # The rational number is finite decimal if the denominator has form 2^a * 5^b + while denominator % 5 == 0: + denominator = denominator / 5 - >> NIntegrate[Exp[-x],{x,0,Infinity},Tolerance->1*^-6] - = 1. - >> NIntegrate[Exp[x],{x,-Infinity, 0},Tolerance->1*^-6] - = 1. - >> NIntegrate[Exp[-x^2/2.],{x,-Infinity, Infinity},Tolerance->1*^-6] - = 2.50663 + while denominator % 2 == 0: + denominator = denominator / 2 - >> 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.} + return True if denominator == 1 else False - >> 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 - 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.} +def chop(expr, delta=10.0 ** (-10.0)): + if isinstance(expr, Real): + if expr.is_nan(expr): + return expr + if -delta < expr.get_float_value() < delta: + return Integer0 + elif isinstance(expr, Complex) and expr.is_inexact(): + real, imag = expr.real, expr.imag + if -delta < real.get_float_value() < delta: + real = Integer0 + if -delta < imag.get_float_value() < delta: + imag = Integer0 + return Complex(real, imag) + elif isinstance(expr, Expression): + return Expression(chop(expr.head), *[chop(leaf) for leaf in expr.leaves]) + return expr - Mutiple Integrals : - >> NIntegrate[x * y,{x, 0, 1}, {y, 0, 1}] - = 0.25 - """ +def convert_repeating_decimal(numerator, denominator, base): + head = [x for x in str(numerator // denominator)] + tails = [] + subresults = [numerator % denominator] + numerator %= denominator - messages = { - "bdmtd": "The Method option should be a built-in method name.", - "inumr": ( - "The integrand `1` has evaluated to non-numerical " - + "values for all sampling points in the region " + while numerator != 0: # only rational input can go to this case + numerator *= base + result_digit, numerator = divmod(numerator, denominator) + tails.append(str(result_digit)) + if numerator not in subresults: + subresults.append(numerator) + else: + break + + for i in range(len(head) - 1, -1, -1): + j = len(tails) - 1 + if head[i] != tails[j]: + break + else: + del tails[j] + tails.insert(0, head[i]) + del head[i] + j = j - 1 + + # truncate all leading 0's + if all(elem == "0" for elem in head): + for i in range(0, len(tails)): + if tails[0] == "0": + tails = tails[1:] + [str(0)] + else: + break + return (head, tails) + + +def convert_float_base(x, base, precision=10): + + length_of_int = 0 if x == 0 else int(mpmath.log(x, base)) + # iexps = list(range(length_of_int, -1, -1)) + + def convert_int(x, base, exponents): + out = [] + for e in range(0, exponents + 1): + d = x % base + out.append(d) + x = x / base + if x == 0: + break + out.reverse() + return out + + def convert_float(x, base, exponents): + out = [] + for e in range(0, exponents): + d = int(x * base) + out.append(d) + x = (x * base) - d + if x == 0: + break + return out + + int_part = convert_int(int(x), base, length_of_int) + if isinstance(x, (float, sympy.Float)): + # fexps = list(range(-1, -int(precision + 1), -1)) + real_part = convert_float(x - int(x), base, precision + 1) + return int_part + real_part + elif isinstance(x, int): + return int_part + else: + raise TypeError(x) + + +class Chop(Builtin): + """ +
+
'Chop[$expr$]' +
replaces floating point numbers close to 0 by 0. + +
'Chop[$expr$, $delta$]' +
uses a tolerance of $delta$. The default tolerance is '10^-10'. +
+ + >> Chop[10.0 ^ -16] + = 0 + >> Chop[10.0 ^ -9] + = 1.*^-9 + >> Chop[10 ^ -11 I] + = I / 100000000000 + >> Chop[0. + 10 ^ -11 I] + = 0 + """ + + messages = { + "tolnn": "Tolerance specification a must be a non-negative number.", + } + + rules = { + "Chop[expr_]": "Chop[expr, 10^-10]", + } + + summary_text = "set sufficiently small numbers or imaginary parts to zero" + + def apply(self, expr, delta, evaluation): + "Chop[expr_, delta_:(10^-10)]" + + delta = delta.round_to_float(evaluation) + if delta is None or delta < 0: + return evaluation.message("Chop", "tolnn") + + return chop(expr, delta=delta) + + +class Fold(object): + # allows inherited classes to specify a single algorithm implementation that + # can be called with machine precision, arbitrary precision or symbolically. + + ComputationFunctions = namedtuple("ComputationFunctions", ("sin", "cos")) + + FLOAT = 0 + MPMATH = 1 + SYMBOLIC = 2 + + math = { + FLOAT: ComputationFunctions( + cos=math.cos, + sin=math.sin, + ), + MPMATH: ComputationFunctions( + cos=mpmath.cos, + sin=mpmath.sin, + ), + SYMBOLIC: ComputationFunctions( + cos=lambda x: Expression("Cos", x), + sin=lambda x: Expression("Sin", x), + ), + } + + operands = { + FLOAT: lambda x: None if x is None else x.round_to_float(), + MPMATH: lambda x: None if x is None else x.to_mpmath(), + SYMBOLIC: lambda x: x, + } + + def _operands(self, state, steps): + raise NotImplementedError + + def _fold(self, state, steps, math): + raise NotImplementedError + + def _spans(self, operands): + spans = {} + k = 0 + j = 0 + + for mode in (self.FLOAT, self.MPMATH): + for i, operand in enumerate(operands[k:]): + if operand[0] > mode: + break + j = i + k + 1 + + if k == 0 and j == 1: # only init state? then ignore. + j = 0 + + spans[mode] = slice(k, j) + k = j + + spans[self.SYMBOLIC] = slice(k, len(operands)) + + return spans + + def fold(self, x, ll): + # computes fold(x, ll) with the internal _fold function. will start + # its evaluation machine precision, and will escalate to arbitrary + # precision if or symbolical evaluation only if necessary. folded + # items already computed are carried over to new evaluation modes. + + yield x # initial state + + init = None + operands = list(self._operands(x, ll)) + spans = self._spans(operands) + + for mode in (self.FLOAT, self.MPMATH, self.SYMBOLIC): + s_operands = [y[1:] for y in operands[spans[mode]]] + + if not s_operands: + continue + + if mode == self.MPMATH: + from mathics.core.numbers import min_prec + + precision = min_prec(*[t for t in chain(*s_operands) if t is not None]) + working_precision = mpmath.workprec + else: + + @contextmanager + def working_precision(_): + yield + + precision = None + + if mode == self.FLOAT: + + def out(z): + return Real(z) + + elif mode == self.MPMATH: + + def out(z): + return Real(z, precision) + + else: + + def out(z): + return z + + as_operand = self.operands.get(mode) + + def converted_operands(): + for y in s_operands: + yield tuple(as_operand(t) for t in y) + + with working_precision(precision): + c_operands = converted_operands() + + if init is not None: + c_init = tuple( + (None if t is None else as_operand(from_python(t))) + for t in init + ) + else: + c_init = next(c_operands) + init = tuple((None if t is None else out(t)) for t in c_init) + + generator = self._fold(c_init, c_operands, self.math.get(mode)) + + for y in generator: + y = tuple(out(t) for t in y) + yield y + init = y + + +class IntegerDigits(Builtin): + """ +
+
'IntegerDigits[$n$]' +
returns a list of the base-10 digits in the integer $n$. +
'IntegerDigits[$n$, $base$]' +
returns a list of the base-$base$ digits in $n$. +
'IntegerDigits[$n$, $base$, $length$]' +
returns a list of length $length$, truncating or padding + with zeroes on the left as necessary. +
+ + >> IntegerDigits[76543] + = {7, 6, 5, 4, 3} + + The sign of $n$ is discarded: + >> IntegerDigits[-76543] + = {7, 6, 5, 4, 3} + + >> IntegerDigits[15, 16] + = {15} + >> IntegerDigits[1234, 16] + = {4, 13, 2} + >> IntegerDigits[1234, 10, 5] + = {0, 1, 2, 3, 4} + + #> IntegerDigits[1000, 10] + = {1, 0, 0, 0} + + #> IntegerDigits[0] + = {0} + """ + + attributes = ("Listable",) + + messages = { + "int": "Integer expected at position 1 in `1`", + "ibase": "Base `1` is not an integer greater than 1.", + } + + rules = { + "IntegerDigits[n_]": "IntegerDigits[n, 10]", + } + + def apply_len(self, n, base, length, evaluation): + "IntegerDigits[n_, base_, length_]" + + if not (isinstance(length, Integer) and length.get_int_value() >= 0): + return evaluation.message("IntegerDigits", "intnn") + + return self.apply(n, base, evaluation, nr_elements=length.get_int_value()) + + def apply(self, n, base, evaluation, nr_elements=None): + "IntegerDigits[n_, base_]" + + if not (isinstance(n, Integer)): + return evaluation.message( + "IntegerDigits", "int", Expression("IntegerDigits", n, base) + ) + + if not (isinstance(base, Integer) and base.get_int_value() > 1): + return evaluation.message("IntegerDigits", "ibase", base) + + if nr_elements == 0: + # trivial case: we don't want any digits + return Expression(SymbolList) + + digits = convert_int_to_digit_list(n.get_int_value(), base.get_int_value()) + + if nr_elements is not None: + if len(digits) >= nr_elements: + # Truncate, preserving the digits on the right + digits = digits[-nr_elements:] + else: + # Pad with zeroes + digits = [0] * (nr_elements - len(digits)) + digits + + return Expression(SymbolList, *digits) + + +class MaxPrecision(Predefined): + """ +
+
'$MaxPrecision' +
represents the maximum number of digits of precision permitted in abitrary-precision numbers. +
+ + >> $MaxPrecision + = Infinity + + >> $MaxPrecision = 10; + + >> N[Pi, 11] + : Requested precision 11 is larger than $MaxPrecision. Using current $MaxPrecision of 10. instead. $MaxPrecision = Infinity specifies that any precision should be allowed. + = 3.141592654 + + #> N[Pi, 10] + = 3.141592654 + + #> $MaxPrecision = x + : Cannot set $MaxPrecision to x; value must be a positive number or Infinity. + = x + #> $MaxPrecision = -Infinity + : Cannot set $MaxPrecision to -Infinity; value must be a positive number or Infinity. + = -Infinity + #> $MaxPrecision = 0 + : Cannot set $MaxPrecision to 0; value must be a positive number or Infinity. + = 0 + #> $MaxPrecision = Infinity; + + #> $MinPrecision = 15; + #> $MaxPrecision = 10 + : Cannot set $MaxPrecision such that $MaxPrecision < $MinPrecision. + = 10 + #> $MaxPrecision + = Infinity + #> $MinPrecision = 0; + """ + + messages = { + "precset": "Cannot set `1` to `2`; value must be a positive number or Infinity.", + "preccon": "Cannot set `1` such that $MaxPrecision < $MinPrecision.", + } + + name = "$MaxPrecision" + + rules = { + "$MaxPrecision": "Infinity", + } + + summary_text = "settable global maximum precision bound" + + +class MachineEpsilon_(Predefined): + """ +
+
'$MachineEpsilon' +
is the distance between '1.0' and the next + nearest representable machine-precision number. +
+ + >> $MachineEpsilon + = 2.22045*^-16 + + >> x = 1.0 + {0.4, 0.5, 0.6} $MachineEpsilon; + >> x - 1 + = {0., 0., 2.22045*^-16} + """ + + name = "$MachineEpsilon" + + def evaluate(self, evaluation): + return MachineReal(machine_epsilon) + + +class MachinePrecision_(Predefined): + """ +
+
'$MachinePrecision' +
is the number of decimal digits of precision for + machine-precision numbers. +
+ + >> $MachinePrecision + = 15.9546 + """ + + name = "$MachinePrecision" + + rules = { + "$MachinePrecision": "N[MachinePrecision]", + } + + +class MachinePrecision(Predefined): + """ +
+
'MachinePrecision' +
represents the precision of machine precision numbers. +
+ + >> N[MachinePrecision] + = 15.9546 + >> N[MachinePrecision, 30] + = 15.9545897701910033463281614204 + + #> N[E, MachinePrecision] + = 2.71828 + + #> Round[MachinePrecision] + = 16 + """ + + rules = { + "N[MachinePrecision, prec_]": ("N[Log[10, 2] * %i, prec]" % machine_precision), + } + + +class MinPrecision(Builtin): + """ +
+
'$MinPrecision' +
represents the minimum number of digits of precision permitted in abitrary-precision numbers. +
+ + >> $MinPrecision + = 0 + + >> $MinPrecision = 10; + + >> N[Pi, 9] + : Requested precision 9 is smaller than $MinPrecision. Using current $MinPrecision of 10. instead. + = 3.141592654 + + #> N[Pi, 10] + = 3.141592654 + + #> $MinPrecision = x + : Cannot set $MinPrecision to x; value must be a non-negative number. + = x + #> $MinPrecision = -Infinity + : Cannot set $MinPrecision to -Infinity; value must be a non-negative number. + = -Infinity + #> $MinPrecision = -1 + : Cannot set $MinPrecision to -1; value must be a non-negative number. + = -1 + #> $MinPrecision = 0; + + #> $MaxPrecision = 10; + #> $MinPrecision = 15 + : Cannot set $MinPrecision such that $MaxPrecision < $MinPrecision. + = 15 + #> $MinPrecision + = 0 + #> $MaxPrecision = Infinity; + """ + + messages = { + "precset": "Cannot set `1` to `2`; value must be a non-negative number.", + "preccon": "Cannot set `1` such that $MaxPrecision < $MinPrecision.", + } + + name = "$MinPrecision" + + rules = { + "$MinPrecision": "0", + } + + summary_text = "settable global minimum precision bound" + + +class N(Builtin): + """ +
+
'N[$expr$, $prec$]' +
evaluates $expr$ numerically with a precision of $prec$ digits. +
+ >> N[Pi, 50] + = 3.1415926535897932384626433832795028841971693993751 + + >> N[1/7] + = 0.142857 + + >> N[1/7, 5] + = 0.14286 + + You can manually assign numerical values to symbols. + When you do not specify a precision, 'MachinePrecision' is taken. + >> N[a] = 10.9 + = 10.9 + >> a + = a + + 'N' automatically threads over expressions, except when a symbol has + attributes 'NHoldAll', 'NHoldFirst', or 'NHoldRest'. + >> N[a + b] + = 10.9 + b + >> N[a, 20] + = a + >> N[a, 20] = 11; + >> N[a + b, 20] + = 11.000000000000000000 + b + >> N[f[a, b]] + = f[10.9, b] + >> SetAttributes[f, NHoldAll] + >> N[f[a, b]] + = f[a, b] + + The precision can be a pattern: + >> N[c, p_?(#>10&)] := p + >> N[c, 3] + = c + >> N[c, 11] + = 11.000000000 + + You can also use 'UpSet' or 'TagSet' to specify values for 'N': + >> N[d] ^= 5; + However, the value will not be stored in 'UpValues', but + in 'NValues' (as for 'Set'): + >> UpValues[d] + = {} + >> NValues[d] + = {HoldPattern[N[d, MachinePrecision]] :> 5} + >> e /: N[e] = 6; + >> N[e] + = 6. + + Values for 'N[$expr$]' must be associated with the head of $expr$: + >> f /: N[e[f]] = 7; + : Tag f not found or too deep for an assigned rule. + + You can use 'Condition': + >> N[g[x_, y_], p_] := x + y * Pi /; x + y > 3 + >> SetAttributes[g, NHoldRest] + >> N[g[1, 1]] + = g[1., 1] + >> N[g[2, 2]] // InputForm + = 8.283185307179586 + + The precision of the result is no higher than the precision of the input + >> N[Exp[0.1], 100] + = 1.10517 + >> % // Precision + = MachinePrecision + >> N[Exp[1/10], 100] + = 1.105170918075647624811707826490246668224547194737518718792863289440967966747654302989143318970748654 + >> % // Precision + = 100. + >> N[Exp[1.0`20], 100] + = 2.7182818284590452354 + >> % // Precision + = 20. + + #> p=N[Pi,100] + = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 + #> ToString[p] + = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 + + #> N[1.012345678901234567890123, 20] + = 1.0123456789012345679 + + #> N[I, 30] + = 1.00000000000000000000000000000 I + + #> N[1.012345678901234567890123, 50] + = 1.01234567890123456789012 + #> % // Precision + = 24. + """ + + messages = { + "precbd": ("Requested precision `1` is not a " + "machine-sized real number."), + "preclg": ( + "Requested precision `1` is larger than $MaxPrecision. " + + "Using current $MaxPrecision of `2` instead. " + + "$MaxPrecision = Infinity specifies that any precision " + + "should be allowed." + ), + "precsm": ( + "Requested precision `1` is smaller than " + + "$MinPrecision. Using current $MinPrecision of " + + "`2` instead." + ), + } + + rules = { + "N[expr_]": "N[expr, MachinePrecision]", + } + + summary_text = "numerical evaluation to specified precision and accuracy" + + def apply_with_prec(self, expr, prec, evaluation): + "N[expr_, prec_]" + + try: + d = get_precision(prec, evaluation) + except PrecisionValueError: + return + + if expr.get_head_name() in ("System`List", "System`Rule"): + return Expression( + expr.head, + *[self.apply_with_prec(leaf, prec, evaluation) for leaf in expr.leaves], + ) + + # Special case for the Root builtin + if expr.has_form("Root", 2): + return from_sympy(sympy.N(expr.to_sympy(), d)) + + if isinstance(expr, Number): + return expr.round(d) + + name = expr.get_lookup_name() + if name != "": + nexpr = Expression(SymbolN, expr, prec) + result = evaluation.definitions.get_value( + name, "System`NValues", nexpr, evaluation + ) + if result is not None: + if not result.sameQ(nexpr): + result = Expression(SymbolN, result, prec).evaluate(evaluation) + return result + + if expr.is_atom(): + return expr + else: + attributes = expr.head.get_attributes(evaluation.definitions) + if "System`NHoldAll" in attributes: + eval_range = () + elif "System`NHoldFirst" in attributes: + eval_range = range(1, len(expr.leaves)) + elif "System`NHoldRest" in attributes: + if len(expr.leaves) > 0: + eval_range = (0,) + else: + eval_range = () + else: + eval_range = range(len(expr.leaves)) + head = Expression(SymbolN, expr.head, prec).evaluate(evaluation) + leaves = expr.get_mutable_leaves() + for index in eval_range: + leaves[index] = Expression(SymbolN, leaves[index], prec).evaluate( + evaluation + ) + return Expression(head, *leaves) + + +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[Exp[-x],{x,0,Infinity},Tolerance->1*^-6] + = 1. + >> NIntegrate[Exp[x],{x,-Infinity, 0},Tolerance->1*^-6] + = 1. + >> NIntegrate[Exp[-x^2/2.],{x,-Infinity, Infinity},Tolerance->1*^-6] + = 2.50663 + + >> 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 + + 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 + + """ + + messages = { + "bdmtd": "The Method option should be a built-in method name.", + "inumr": ( + "The integrand `1` has evaluated to non-numerical " + + "values for all sampling points in the region " + "with boundaries `2`" ), "nlim": "`1` = `2` is not a valid limit of integration.", @@ -685,69 +1211,34 @@ def apply_with_func_domain(self, func, domain, evaluation, options): return from_python(result) -class MachinePrecision(Predefined): - """ -
-
'MachinePrecision' -
represents the precision of machine precision numbers. -
- - >> N[MachinePrecision] - = 15.9546 - >> N[MachinePrecision, 30] - = 15.9545897701910033463281614204 - - #> N[E, MachinePrecision] - = 2.71828 - - #> Round[MachinePrecision] - = 16 - """ - - rules = { - "N[MachinePrecision, prec_]": ("N[Log[10, 2] * %i, prec]" % machine_precision), - } - - -class MachineEpsilon_(Predefined): +class NumericQ(Builtin): """
-
'$MachineEpsilon' -
is the distance between '1.0' and the next - nearest representable machine-precision number. +
'NumericQ[$expr$]' +
tests whether $expr$ represents a numeric quantity.
- >> $MachineEpsilon - = 2.22045*^-16 - - >> x = 1.0 + {0.4, 0.5, 0.6} $MachineEpsilon; - >> x - 1 - = {0., 0., 2.22045*^-16} - """ - - name = "$MachineEpsilon" - - def evaluate(self, evaluation): - return MachineReal(machine_epsilon) - - -class MachinePrecision_(Predefined): + >> NumericQ[2] + = True + >> NumericQ[Sqrt[Pi]] + = True + >> NumberQ[Sqrt[Pi]] + = False """ -
-
'$MachinePrecision' -
is the number of decimal digits of precision for - machine-precision numbers. -
- >> $MachinePrecision - = 15.9546 - """ + def apply(self, expr, evaluation): + "NumericQ[expr_]" - name = "$MachinePrecision" + def test(expr): + if isinstance(expr, Expression): + attr = evaluation.definitions.get_attributes(expr.head.get_name()) + return "System`NumericFunction" in attr and all( + test(leaf) for leaf in expr.leaves + ) + else: + return expr.is_numeric() - rules = { - "$MachinePrecision": "N[MachinePrecision]", - } + return SymbolTrue if test(expr) else SymbolFalse class Precision(Builtin): @@ -755,234 +1246,64 @@ class Precision(Builtin):
'Precision[$expr$]'
examines the number of significant digits of $expr$. -
- This is rather a proof-of-concept than a full implementation. - Precision of compound expression is not supported yet. - >> Precision[1] - = Infinity - >> Precision[1/2] - = Infinity - >> Precision[0.5] - = MachinePrecision - - #> Precision[0.0] - = MachinePrecision - #> Precision[0.000000000000000000000000000000000000] - = 0. - #> Precision[-0.0] - = MachinePrecision - #> Precision[-0.000000000000000000000000000000000000] - = 0. - - #> 1.0000000000000000 // Precision - = MachinePrecision - #> 1.00000000000000000 // Precision - = 17. - - #> 0.4 + 2.4 I // Precision - = MachinePrecision - #> Precision[2 + 3 I] - = Infinity - - #> Precision["abc"] - = Infinity - """ - - rules = { - "Precision[z_?MachineNumberQ]": "MachinePrecision", - } - - def apply(self, z, evaluation): - "Precision[z_]" - - if not z.is_inexact(): - return Symbol("Infinity") - elif z.to_sympy().is_zero: - return Real(0) - else: - return Real(dps(z.get_precision())) - - -class MinPrecision(Builtin): - """ -
-
'$MinPrecision' -
represents the minimum number of digits of precision - permitted in abitrary-precision numbers. -
- - >> $MinPrecision - = 0 - - >> $MinPrecision = 10; - - >> N[Pi, 9] - : Requested precision 9 is smaller than $MinPrecision. Using current $MinPrecision of 10. instead. - = 3.141592654 - - #> N[Pi, 10] - = 3.141592654 - - #> $MinPrecision = x - : Cannot set $MinPrecision to x; value must be a non-negative number. - = x - #> $MinPrecision = -Infinity - : Cannot set $MinPrecision to -Infinity; value must be a non-negative number. - = -Infinity - #> $MinPrecision = -1 - : Cannot set $MinPrecision to -1; value must be a non-negative number. - = -1 - #> $MinPrecision = 0; - - #> $MaxPrecision = 10; - #> $MinPrecision = 15 - : Cannot set $MinPrecision such that $MaxPrecision < $MinPrecision. - = 15 - #> $MinPrecision - = 0 - #> $MaxPrecision = Infinity; - """ - - name = "$MinPrecision" - rules = { - "$MinPrecision": "0", - } - - messages = { - "precset": "Cannot set `1` to `2`; value must be a non-negative number.", - "preccon": "Cannot set `1` such that $MaxPrecision < $MinPrecision.", - } - - -class MaxPrecision(Predefined): - """ -
-
'$MaxPrecision' -
represents the maximum number of digits of precision - permitted in abitrary-precision numbers. -
- - >> $MaxPrecision - = Infinity - - >> $MaxPrecision = 10; - - >> N[Pi, 11] - : Requested precision 11 is larger than $MaxPrecision. Using current $MaxPrecision of 10. instead. $MaxPrecision = Infinity specifies that any precision should be allowed. - = 3.141592654 - - #> N[Pi, 10] - = 3.141592654 - - #> $MaxPrecision = x - : Cannot set $MaxPrecision to x; value must be a positive number or Infinity. - = x - #> $MaxPrecision = -Infinity - : Cannot set $MaxPrecision to -Infinity; value must be a positive number or Infinity. - = -Infinity - #> $MaxPrecision = 0 - : Cannot set $MaxPrecision to 0; value must be a positive number or Infinity. - = 0 - #> $MaxPrecision = Infinity; - - #> $MinPrecision = 15; - #> $MaxPrecision = 10 - : Cannot set $MaxPrecision such that $MaxPrecision < $MinPrecision. - = 10 - #> $MaxPrecision - = Infinity - #> $MinPrecision = 0; - """ - - name = "$MaxPrecision" - - rules = { - "$MaxPrecision": "Infinity", - } - - messages = { - "precset": "Cannot set `1` to `2`; value must be a positive number or Infinity.", - "preccon": "Cannot set `1` such that $MaxPrecision < $MinPrecision.", - } - - -class Round(Builtin): - """ -
-
'Round[$expr$]' -
rounds $expr$ to the nearest integer. -
'Round[$expr$, $k$]' -
rounds $expr$ to the closest multiple of $k$. -
- - >> Round[10.6] - = 11 - >> Round[0.06, 0.1] - = 0.1 - >> Round[0.04, 0.1] - = 0. - - Constants can be rounded too - >> Round[Pi, .5] - = 3. - >> Round[Pi^2] - = 10 +
+ This is rather a proof-of-concept than a full implementation. + Precision of compound expression is not supported yet. + >> Precision[1] + = Infinity + >> Precision[1/2] + = Infinity + >> Precision[0.5] + = MachinePrecision - Round to exact value - >> Round[2.6, 1/3] - = 8 / 3 - >> Round[10, Pi] - = 3 Pi + #> Precision[0.0] + = MachinePrecision + #> Precision[0.000000000000000000000000000000000000] + = 0. + #> Precision[-0.0] + = MachinePrecision + #> Precision[-0.000000000000000000000000000000000000] + = 0. - Round complex numbers - >> Round[6/(2 + 3 I)] - = 1 - I - >> Round[1 + 2 I, 2 I] - = 2 I + #> 1.0000000000000000 // Precision + = MachinePrecision + #> 1.00000000000000000 // Precision + = 17. - Round Negative numbers too - >> Round[-1.4] - = -1 + #> 0.4 + 2.4 I // Precision + = MachinePrecision + #> Precision[2 + 3 I] + = Infinity - Expressions other than numbers remain unevaluated: - >> Round[x] - = Round[x] - >> Round[1.5, k] - = Round[1.5, k] + #> Precision["abc"] + = Infinity """ - attributes = ("Listable", "NumericFunction") - rules = { - "Round[expr_?NumericQ]": "Round[Re[expr], 1] + I * Round[Im[expr], 1]", - "Round[expr_Complex, k_?RealNumberQ]": ( - "Round[Re[expr], k] + I * Round[Im[expr], k]" - ), + "Precision[z_?MachineNumberQ]": "MachinePrecision", } - def apply(self, expr, k, evaluation): - "Round[expr_?NumericQ, k_?NumericQ]" + summary_text = "find the precision of a number" - n = Expression("Divide", expr, k).round_to_float( - evaluation, permit_complex=True - ) - if n is None: - return - elif isinstance(n, complex): - n = round(n.real) + def apply(self, z, evaluation): + "Precision[z_]" + + if not z.is_inexact(): + return Symbol("Infinity") + elif z.to_sympy().is_zero: + return Real(0) else: - n = round(n) - n = int(n) - return Expression("Times", Integer(n), k) + return Real(dps(z.get_precision())) class Rationalize(Builtin): """
-
'Rationalize[$x$]' -
converts a real number $x$ to a nearby rational number. -
'Rationalize[$x$, $dx$]' -
finds the rational number within $dx$ of $x$ with the smallest denominator. +
'Rationalize[$x$]' +
converts a real number $x$ to a nearby rational number with small denominator. + +
'Rationalize[$x$, $dx$]' +
finds the rational number lies within $dx$ of $x$.
>> Rationalize[2.2] @@ -996,12 +1317,6 @@ class Rationalize(Builtin): >> Rationalize[N[Pi], 0] = 245850922 / 78256779 - #> Rationalize[1.6 + 0.8 I] - = 8 / 5 + 4 I / 5 - - #> Rationalize[N[Pi] + 0.8 I, 1*^-6] - = 355 / 113 + 4 I / 5 - #> Rationalize[N[Pi] + 0.8 I, x] : Tolerance specification x must be a non-negative number. = Rationalize[3.14159 + 0.8 I, x] @@ -1010,18 +1325,6 @@ class Rationalize(Builtin): : Tolerance specification -1 must be a non-negative number. = Rationalize[3.14159 + 0.8 I, -1] - #> Rationalize[N[Pi] + 0.8 I, 0] - = 245850922 / 78256779 + 4 I / 5 - - #> Rationalize[17 / 7] - = 17 / 7 - - #> Rationalize[x] - = x - - #> Table[Rationalize[E, 0.1^n], {n, 1, 10}] - = {8 / 3, 19 / 7, 87 / 32, 193 / 71, 1071 / 394, 2721 / 1001, 15062 / 5541, 23225 / 8544, 49171 / 18089, 419314 / 154257} - #> Rationalize[x, y] : Tolerance specification y must be a non-negative number. = Rationalize[x, y] @@ -1036,6 +1339,8 @@ class Rationalize(Builtin): "Rationalize[z_Complex, dx_?Internal`RealValuedNumberQ]/;dx >= 0": "Rationalize[Re[z], dx] + I Rationalize[Im[z], dx]", } + summary_text = "find a rational approximation" + def apply(self, x, evaluation): "Rationalize[x_]" @@ -1104,91 +1409,6 @@ def approx_interval_continued_fraction(xmin, xmax): return result -def chop(expr, delta=10.0 ** (-10.0)): - if isinstance(expr, Real): - if expr.is_nan(expr): - return expr - if -delta < expr.get_float_value() < delta: - return Integer0 - elif isinstance(expr, Complex) and expr.is_inexact(): - real, imag = expr.real, expr.imag - if -delta < real.get_float_value() < delta: - real = Integer0 - if -delta < imag.get_float_value() < delta: - imag = Integer0 - return Complex(real, imag) - elif isinstance(expr, Expression): - return Expression(chop(expr.head), *[chop(leaf) for leaf in expr.leaves]) - return expr - - -class Chop(Builtin): - """ -
-
'Chop[$expr$]' -
replaces floating point numbers close to 0 by 0. -
'Chop[$expr$, $delta$]' -
uses a tolerance of $delta$. The default tolerance is '10^-10'. -
- - >> Chop[10.0 ^ -16] - = 0 - >> Chop[10.0 ^ -9] - = 1.*^-9 - >> Chop[10 ^ -11 I] - = I / 100000000000 - >> Chop[0. + 10 ^ -11 I] - = 0 - """ - - messages = { - "tolnn": "Tolerance specification a must be a non-negative number.", - } - - rules = { - "Chop[expr_]": "Chop[expr, 10^-10]", - } - - def apply(self, expr, delta, evaluation): - "Chop[expr_, delta_:(10^-10)]" - - delta = delta.round_to_float(evaluation) - if delta is None or delta < 0: - return evaluation.message("Chop", "tolnn") - - return chop(expr, delta=delta) - - -class NumericQ(Builtin): - """ -
-
'NumericQ[$expr$]' -
tests whether $expr$ represents a numeric quantity. -
- - >> NumericQ[2] - = True - >> NumericQ[Sqrt[Pi]] - = True - >> NumberQ[Sqrt[Pi]] - = False - """ - - def apply(self, expr, evaluation): - "NumericQ[expr_]" - - def test(expr): - if isinstance(expr, Expression): - attr = evaluation.definitions.get_attributes(expr.head.get_name()) - return "System`NumericFunction" in attr and all( - test(leaf) for leaf in expr.leaves - ) - else: - return expr.is_numeric() - - return SymbolTrue if test(expr) else SymbolFalse - - class RealValuedNumericQ(Builtin): # No docstring since this is internal and it will mess up documentation. # FIXME: Perhaps in future we will have a more explicite way to indicate not @@ -1207,190 +1427,97 @@ class RealValuedNumberQ(Builtin): context = "Internal`" rules = { - "Internal`RealValuedNumberQ[x_Real]": "True", - "Internal`RealValuedNumberQ[x_Integer]": "True", - "Internal`RealValuedNumberQ[x_Rational]": "True", - "Internal`RealValuedNumberQ[x_]": "False", - } - - -class IntegerDigits(Builtin): - """ -
-
'IntegerDigits[$n$]' -
returns a list of the base-10 digits in the integer $n$. -
'IntegerDigits[$n$, $base$]' -
returns a list of the base-$base$ digits in $n$. -
'IntegerDigits[$n$, $base$, $length$]' -
returns a list of length $length$, truncating or padding - with zeroes on the left as necessary. -
- - >> IntegerDigits[76543] - = {7, 6, 5, 4, 3} - - The sign of $n$ is discarded: - >> IntegerDigits[-76543] - = {7, 6, 5, 4, 3} - - >> IntegerDigits[15, 16] - = {15} - >> IntegerDigits[1234, 16] - = {4, 13, 2} - >> IntegerDigits[1234, 10, 5] - = {0, 1, 2, 3, 4} - - #> IntegerDigits[1000, 10] - = {1, 0, 0, 0} - - #> IntegerDigits[0] - = {0} - """ - - attributes = ("Listable",) - - messages = { - "int": "Integer expected at position 1 in `1`", - "ibase": "Base `1` is not an integer greater than 1.", - } - - rules = { - "IntegerDigits[n_]": "IntegerDigits[n, 10]", - } - - def apply_len(self, n, base, length, evaluation): - "IntegerDigits[n_, base_, length_]" - - if not (isinstance(length, Integer) and length.get_int_value() >= 0): - return evaluation.message("IntegerDigits", "intnn") - - return self.apply(n, base, evaluation, nr_elements=length.get_int_value()) - - def apply(self, n, base, evaluation, nr_elements=None): - "IntegerDigits[n_, base_]" - - if not (isinstance(n, Integer)): - return evaluation.message( - "IntegerDigits", "int", Expression("IntegerDigits", n, base) - ) - - if not (isinstance(base, Integer) and base.get_int_value() > 1): - return evaluation.message("IntegerDigits", "ibase", base) - - if nr_elements == 0: - # trivial case: we don't want any digits - return Expression(SymbolList) - - digits = convert_int_to_digit_list(n.get_int_value(), base.get_int_value()) - - if nr_elements is not None: - if len(digits) >= nr_elements: - # Truncate, preserving the digits on the right - digits = digits[-nr_elements:] - else: - # Pad with zeroes - digits = [0] * (nr_elements - len(digits)) + digits - - return Expression(SymbolList, *digits) - - -def check_finite_decimal(denominator): - # The rational number is finite decimal if the denominator has form 2^a * 5^b - while denominator % 5 == 0: - denominator = denominator / 5 - - while denominator % 2 == 0: - denominator = denominator / 2 + "Internal`RealValuedNumberQ[x_Real]": "True", + "Internal`RealValuedNumberQ[x_Integer]": "True", + "Internal`RealValuedNumberQ[x_Rational]": "True", + "Internal`RealValuedNumberQ[x_]": "False", + } - return True if denominator == 1 else False +class Round(Builtin): + """ +
+
'Round[$expr$]' +
rounds $expr$ to the nearest integer. +
'Round[$expr$, $k$]' +
rounds $expr$ to the closest multiple of $k$. +
-def convert_repeating_decimal(numerator, denominator, base): - head = [x for x in str(numerator // denominator)] - tails = [] - subresults = [numerator % denominator] - numerator %= denominator + >> Round[10.6] + = 11 + >> Round[0.06, 0.1] + = 0.1 + >> Round[0.04, 0.1] + = 0. - while numerator != 0: # only rational input can go to this case - numerator *= base - result_digit, numerator = divmod(numerator, denominator) - tails.append(str(result_digit)) - if numerator not in subresults: - subresults.append(numerator) - else: - break + Constants can be rounded too + >> Round[Pi, .5] + = 3. + >> Round[Pi^2] + = 10 - for i in range(len(head) - 1, -1, -1): - j = len(tails) - 1 - if head[i] != tails[j]: - break - else: - del tails[j] - tails.insert(0, head[i]) - del head[i] - j = j - 1 + Round to exact value + >> Round[2.6, 1/3] + = 8 / 3 + >> Round[10, Pi] + = 3 Pi - # truncate all leading 0's - if all(elem == "0" for elem in head): - for i in range(0, len(tails)): - if tails[0] == "0": - tails = tails[1:] + [str(0)] - else: - break - return (head, tails) + Round complex numbers + >> Round[6/(2 + 3 I)] + = 1 - I + >> Round[1 + 2 I, 2 I] + = 2 I + Round Negative numbers too + >> Round[-1.4] + = -1 -def convert_float_base(x, base, precision=10): + Expressions other than numbers remain unevaluated: + >> Round[x] + = Round[x] + >> Round[1.5, k] + = Round[1.5, k] + """ - length_of_int = 0 if x == 0 else int(mpmath.log(x, base)) - # iexps = list(range(length_of_int, -1, -1)) + attributes = ("Listable", "NumericFunction") - def convert_int(x, base, exponents): - out = [] - for e in range(0, exponents + 1): - d = x % base - out.append(d) - x = x / base - if x == 0: - break - out.reverse() - return out + rules = { + "Round[expr_?NumericQ]": "Round[Re[expr], 1] + I * Round[Im[expr], 1]", + "Round[expr_Complex, k_?RealNumberQ]": ( + "Round[Re[expr], k] + I * Round[Im[expr], k]" + ), + } - def convert_float(x, base, exponents): - out = [] - for e in range(0, exponents): - d = int(x * base) - out.append(d) - x = (x * base) - d - if x == 0: - break - return out + def apply(self, expr, k, evaluation): + "Round[expr_?NumericQ, k_?NumericQ]" - int_part = convert_int(int(x), base, length_of_int) - if isinstance(x, (float, sympy.Float)): - # fexps = list(range(-1, -int(precision + 1), -1)) - real_part = convert_float(x - int(x), base, precision + 1) - return int_part + real_part - elif isinstance(x, int): - return int_part - else: - raise TypeError(x) + n = Expression("Divide", expr, k).round_to_float( + evaluation, permit_complex=True + ) + if n is None: + return + elif isinstance(n, complex): + n = round(n.real) + else: + n = round(n) + n = int(n) + return Expression("Times", Integer(n), k) class RealDigits(Builtin): """
-
'RealDigits[$n$]' -
returns the decimal representation of the real number $n$ as list of digits, together with the number of digits that are to the left of the decimal point. +
'RealDigits[$n$]' +
returns the decimal representation of the real number $n$ as list of digits, together with the number of digits that are to the left of the decimal point. -
'RealDigits[$n$, $b$]' -
returns a list of base_$b$ representation of the real number $n$. +
'RealDigits[$n$, $b$]' +
returns a list of base_$b$ representation of the real number $n$. -
'RealDigits[$n$, $b$, $len$]' -
returns a list of $len$ digits. +
'RealDigits[$n$, $b$, $len$]' +
returns a list of $len$ digits. -
'RealDigits[$n$, $b$, $len$, $p$]' -
return $len$ digits starting with the coefficient of $b$^$p$ +
'RealDigits[$n$, $b$, $len$, $p$]' +
return $len$ digits starting with the coefficient of $b$^$p$
Return the list of digits and exponent: @@ -1458,6 +1585,8 @@ class RealDigits(Builtin): "intm": "Machine-sized integer expected at position 4 in `1`.", } + summary_text = "digits of a real number" + def apply_complex(self, n, var, evaluation): "%(name)s[n_Complex, var___]" return evaluation.message("RealDigits", "realx", n) @@ -1722,133 +1851,3 @@ def apply(self, expr, hashtype, outformat, evaluation): class TypeEscalation(Exception): def __init__(self, mode): self.mode = mode - - -class Fold(object): - # allows inherited classes to specify a single algorithm implementation that - # can be called with machine precision, arbitrary precision or symbolically. - - ComputationFunctions = namedtuple("ComputationFunctions", ("sin", "cos")) - - FLOAT = 0 - MPMATH = 1 - SYMBOLIC = 2 - - math = { - FLOAT: ComputationFunctions( - cos=math.cos, - sin=math.sin, - ), - MPMATH: ComputationFunctions( - cos=mpmath.cos, - sin=mpmath.sin, - ), - SYMBOLIC: ComputationFunctions( - cos=lambda x: Expression("Cos", x), - sin=lambda x: Expression("Sin", x), - ), - } - - operands = { - FLOAT: lambda x: None if x is None else x.round_to_float(), - MPMATH: lambda x: None if x is None else x.to_mpmath(), - SYMBOLIC: lambda x: x, - } - - def _operands(self, state, steps): - raise NotImplementedError - - def _fold(self, state, steps, math): - raise NotImplementedError - - def _spans(self, operands): - spans = {} - k = 0 - j = 0 - - for mode in (self.FLOAT, self.MPMATH): - for i, operand in enumerate(operands[k:]): - if operand[0] > mode: - break - j = i + k + 1 - - if k == 0 and j == 1: # only init state? then ignore. - j = 0 - - spans[mode] = slice(k, j) - k = j - - spans[self.SYMBOLIC] = slice(k, len(operands)) - - return spans - - def fold(self, x, ll): - # computes fold(x, ll) with the internal _fold function. will start - # its evaluation machine precision, and will escalate to arbitrary - # precision if or symbolical evaluation only if necessary. folded - # items already computed are carried over to new evaluation modes. - - yield x # initial state - - init = None - operands = list(self._operands(x, ll)) - spans = self._spans(operands) - - for mode in (self.FLOAT, self.MPMATH, self.SYMBOLIC): - s_operands = [y[1:] for y in operands[spans[mode]]] - - if not s_operands: - continue - - if mode == self.MPMATH: - from mathics.core.numbers import min_prec - - precision = min_prec(*[t for t in chain(*s_operands) if t is not None]) - working_precision = mpmath.workprec - else: - - @contextmanager - def working_precision(_): - yield - - precision = None - - if mode == self.FLOAT: - - def out(z): - return Real(z) - - elif mode == self.MPMATH: - - def out(z): - return Real(z, precision) - - else: - - def out(z): - return z - - as_operand = self.operands.get(mode) - - def converted_operands(): - for y in s_operands: - yield tuple(as_operand(t) for t in y) - - with working_precision(precision): - c_operands = converted_operands() - - if init is not None: - c_init = tuple( - (None if t is None else as_operand(from_python(t))) - for t in init - ) - else: - c_init = next(c_operands) - init = tuple((None if t is None else out(t)) for t in c_init) - - generator = self._fold(c_init, c_operands, self.math.get(mode)) - - for y in generator: - y = tuple(out(t) for t in y) - yield y - init = y diff --git a/test/test_numeric.py b/test/test_numeric.py index 5c85a49f6..bde402acc 100644 --- a/test/test_numeric.py +++ b/test/test_numeric.py @@ -2,6 +2,54 @@ from .helper import check_evaluation +def test_rationalize(): + # Some of the Rationalize tests were taken from Symja's tests and docs + for str_expr, str_expected in ( + ( + "Rationalize[42]", + "42", + ), + # We get 3 / 1 instead of 3 + # ( + # "Rationalize[3, 1]", + # "3", + # ), + ( + "Rationalize[N[Pi] + 0.8 I, 0]", + "245850922 / 78256779 + 4 I / 5", + ), + ( + "Rationalize[1.6 + 0.8 I]", + "8 / 5 + 4 I / 5", + ), + ( + "Rationalize[17 / 7]", + "17 / 7", + ), + ( + "Rationalize[6.75]", + "27 / 4", + ), + ( + "Rationalize[0.25+I*0.33333]", + "1 / 4 + I / 3", + ), + ( + "Rationalize[N[Pi] + 0.8 I, 1*^-6]", + "355 / 113 + 4 I / 5", + ), + ( + "Rationalize[x]", + "x", + ), + ( + "Table[Rationalize[E, 0.1^n], {n, 1, 10}]", + "{8 / 3, 19 / 7, 87 / 32, 193 / 71, 1071 / 394, 2721 / 1001, 15062 / 5541, 23225 / 8544, 49171 / 18089, 419314 / 154257}", + ), + ): + check_evaluation(str_expr, str_expected) + + def test_realvalued(): for str_expr, str_expected in ( ( From 815835919f7da2eeb438098cf855f1a40c2396cd Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 21 Aug 2021 17:55:00 -0400 Subject: [PATCH 059/193] Symmetric Rationalize See https://mathematica.stackexchange.com/questions/253637/how-to-think-about-the-answer-to-rationlize-11-5-1 --- mathics/builtin/comparison.py | 8 ++++---- mathics/builtin/numeric.py | 32 ++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/comparison.py b/mathics/builtin/comparison.py index 7b225fb25..95fe1aa0a 100644 --- a/mathics/builtin/comparison.py +++ b/mathics/builtin/comparison.py @@ -801,8 +801,8 @@ class GreaterEqual(_ComparisonOperator, SympyComparison): class Positive(Builtin): """
-
'Positive[$x$]' -
returns 'True' if $x$ is a positive real number. +
'Positive[$x$]' +
returns 'True' if $x$ is a positive real number.
>> Positive[1] @@ -861,8 +861,8 @@ class Negative(Builtin): class NonNegative(Builtin): """
-
'NonNegative[$x$]' -
returns 'True' if $x$ is a positive real number or zero. +
'NonNegative[$x$]' +
returns 'True' if $x$ is a positive real number or zero.
>> {Positive[0], NonNegative[0]} diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index cd2d0778b..1559320a6 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -1309,6 +1309,10 @@ class Rationalize(Builtin): >> Rationalize[2.2] = 11 / 5 + For negative $x$, '-Rationalize[-$x$] == Rationalize[$x$]' which gives symmetric results: + >> Rationalize[-11.5, 1] + = -11 / 1 + Not all numbers can be well approximated. >> Rationalize[N[Pi]] = 3.14159 @@ -1347,7 +1351,16 @@ def apply(self, x, evaluation): py_x = x.to_sympy() if py_x is None or (not py_x.is_number) or (not py_x.is_real): return x - return from_sympy(self.find_approximant(py_x)) + + # For negative x, MMA treads Rationalize[x] as -Rationalize[-x]. + # Whether this is an implementation choice or not, it has been + # expressed that having this give symmetric results for +/- + # is nice. + # See https://mathematica.stackexchange.com/questions/253637/how-to-think-about-the-answer-to-rationlize-11-5-1 + if py_x.is_positive: + return from_sympy(self.find_approximant(py_x)) + else: + return -from_sympy(self.find_approximant(-py_x)) @staticmethod def find_approximant(x): @@ -1390,9 +1403,20 @@ def apply_dx(self, x, dx, evaluation): return evaluation.message("Rationalize", "tolnn", dx) elif py_dx == 0: return from_sympy(self.find_exact(py_x)) - a = self.approx_interval_continued_fraction(py_x - py_dx, py_x + py_dx) - sym_x = sympy.ntheory.continued_fraction_reduce(a) - return Rational(sym_x) + + # For negative x, MMA treads Rationalize[x] as -Rationalize[-x]. + # Whether this is an implementation choice or not, it has been + # expressed that having this give symmetric results for +/- + # is nice. + # See https://mathematica.stackexchange.com/questions/253637/how-to-think-about-the-answer-to-rationlize-11-5-1 + if py_x.is_positive: + a = self.approx_interval_continued_fraction(py_x - py_dx, py_x + py_dx) + sym_x = sympy.ntheory.continued_fraction_reduce(a) + return Rational(sym_x) + else: + a = self.approx_interval_continued_fraction(-py_x - py_dx, -py_x + py_dx) + sym_x = sympy.ntheory.continued_fraction_reduce(a) + return Rational(-sym_x) @staticmethod def approx_interval_continued_fraction(xmin, xmax): From f6188b0513cf20b7bde42fcc457b785d82ee8c99 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 21 Aug 2021 21:18:48 -0400 Subject: [PATCH 060/193] Rationalize result that are an integer stay int --- CHANGES.rst | 2 ++ mathics/builtin/numeric.py | 8 ++++---- test/test_numeric.py | 9 ++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 218237cdd..b69e1be59 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -42,6 +42,8 @@ Bugs * Fix and document better behavior of ``Quantile`` * Improve Asymptote ``BezierCurve``implementation +* ``Rationalize`` gives symmetric results for +/- like MMA does. If + the result is an integer, it stays that way. 4.0.0 ----- diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 1559320a6..c651551f5 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -1311,7 +1311,7 @@ class Rationalize(Builtin): For negative $x$, '-Rationalize[-$x$] == Rationalize[$x$]' which gives symmetric results: >> Rationalize[-11.5, 1] - = -11 / 1 + = -11 Not all numbers can be well approximated. >> Rationalize[N[Pi]] @@ -1412,11 +1412,11 @@ def apply_dx(self, x, dx, evaluation): if py_x.is_positive: a = self.approx_interval_continued_fraction(py_x - py_dx, py_x + py_dx) sym_x = sympy.ntheory.continued_fraction_reduce(a) - return Rational(sym_x) else: a = self.approx_interval_continued_fraction(-py_x - py_dx, -py_x + py_dx) - sym_x = sympy.ntheory.continued_fraction_reduce(a) - return Rational(-sym_x) + sym_x = -sympy.ntheory.continued_fraction_reduce(a) + + return Integer(sym_x) if sym_x.is_integer else Rational(sym_x) @staticmethod def approx_interval_continued_fraction(xmin, xmax): diff --git a/test/test_numeric.py b/test/test_numeric.py index bde402acc..72fa7556f 100644 --- a/test/test_numeric.py +++ b/test/test_numeric.py @@ -9,11 +9,10 @@ def test_rationalize(): "Rationalize[42]", "42", ), - # We get 3 / 1 instead of 3 - # ( - # "Rationalize[3, 1]", - # "3", - # ), + ( + "Rationalize[3, 1]", + "3", + ), ( "Rationalize[N[Pi] + 0.8 I, 0]", "245850922 / 78256779 + 4 I / 5", From cfd20545e8f8e4d29c9e198915792a47826029e0 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 8 May 2021 05:52:12 -0400 Subject: [PATCH 061/193] Pull out read_check_options --- mathics/builtin/files_io/files.py | 132 +++++++++++++++--------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 7d6bff361..2cc6bd007 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -123,6 +123,71 @@ def __exit__(self, type, value, traceback): super().__exit__(type, value, traceback) +def read_check_options(options): + # Options + # TODO Proper error messages + + result = {} + keys = list(options.keys()) + + # AnchoredSearch + if "System`AnchoredSearch" in keys: + anchored_search = options["System`AnchoredSearch"].to_python() + assert anchored_search in [True, False] + result["AnchoredSearch"] = anchored_search + + # IgnoreCase + if "System`IgnoreCase" in keys: + ignore_case = options["System`IgnoreCase"].to_python() + assert ignore_case in [True, False] + result["IgnoreCase"] = ignore_case + + # WordSearch + if "System`WordSearch" in keys: + word_search = options["System`WordSearch"].to_python() + assert word_search in [True, False] + result["WordSearch"] = word_search + + # RecordSeparators + if "System`RecordSeparators" in keys: + record_separators = options["System`RecordSeparators"].to_python() + assert isinstance(record_separators, list) + assert all( + isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators + ) + record_separators = [s[1:-1] for s in record_separators] + result["RecordSeparators"] = record_separators + + # WordSeparators + if "System`WordSeparators" in keys: + word_separators = options["System`WordSeparators"].to_python() + assert isinstance(word_separators, list) + assert all( + isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators + ) + word_separators = [s[1:-1] for s in word_separators] + result["WordSeparators"] = word_separators + + # NullRecords + if "System`NullRecords" in keys: + null_records = options["System`NullRecords"].to_python() + assert null_records in [True, False] + result["NullRecords"] = null_records + + # NullWords + if "System`NullWords" in keys: + null_words = options["System`NullWords"].to_python() + assert null_words in [True, False] + result["NullWords"] = null_words + + # TokenWords + if "System`TokenWords" in keys: + token_words = options["System`TokenWords"].to_python() + assert token_words == [] + result["TokenWords"] = token_words + + return result + class Input(Predefined): """
@@ -409,71 +474,6 @@ class Read(Builtin): attributes = "Protected" - def check_options(self, options): - # Options - # TODO Proper error messages - - result = {} - keys = list(options.keys()) - - # AnchoredSearch - if "System`AnchoredSearch" in keys: - anchored_search = options["System`AnchoredSearch"].to_python() - assert anchored_search in [True, False] - result["AnchoredSearch"] = anchored_search - - # IgnoreCase - if "System`IgnoreCase" in keys: - ignore_case = options["System`IgnoreCase"].to_python() - assert ignore_case in [True, False] - result["IgnoreCase"] = ignore_case - - # WordSearch - if "System`WordSearch" in keys: - word_search = options["System`WordSearch"].to_python() - assert word_search in [True, False] - result["WordSearch"] = word_search - - # RecordSeparators - if "System`RecordSeparators" in keys: - record_separators = options["System`RecordSeparators"].to_python() - assert isinstance(record_separators, list) - assert all( - isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators - ) - record_separators = [s[1:-1] for s in record_separators] - result["RecordSeparators"] = record_separators - - # WordSeparators - if "System`WordSeparators" in keys: - word_separators = options["System`WordSeparators"].to_python() - assert isinstance(word_separators, list) - assert all( - isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators - ) - word_separators = [s[1:-1] for s in word_separators] - result["WordSeparators"] = word_separators - - # NullRecords - if "System`NullRecords" in keys: - null_records = options["System`NullRecords"].to_python() - assert null_records in [True, False] - result["NullRecords"] = null_records - - # NullWords - if "System`NullWords" in keys: - null_words = options["System`NullWords"].to_python() - assert null_words in [True, False] - result["NullWords"] = null_words - - # TokenWords - if "System`TokenWords" in keys: - token_words = options["System`TokenWords"].to_python() - assert token_words == [] - result["TokenWords"] = token_words - - return result - def apply(self, channel, types, evaluation, options): "Read[channel_, types_, OptionsPattern[Read]]" @@ -540,7 +540,7 @@ def apply(self, channel, types, evaluation, options): # Options # TODO Implement extra options - py_options = self.check_options(options) + py_options = read_check_options(options) # null_records = py_options['NullRecords'] # null_words = py_options['NullWords'] record_separators = py_options["RecordSeparators"] From 6da7febf7765cac0346efa9cb26ea77fa6208da8 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 8 May 2021 06:07:38 -0400 Subject: [PATCH 062/193] WIP - pull out another fn --- mathics/builtin/files_io/files.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 2cc6bd007..5318a2bc4 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -188,6 +188,19 @@ def read_check_options(options): return result +def read_preamble(options, name): + # Options + # TODO Implement extra options + py_options = read_check_options(options) + # null_records = py_options['NullRecords'] + # null_words = py_options['NullWords'] + record_separators = py_options["RecordSeparators"] + # token_words = py_options['TokenWords'] + word_separators = py_options["WordSeparators"] + + py_name = name.to_python() + return record_separators, word_separators, py_name + class Input(Predefined): """
@@ -492,6 +505,7 @@ def apply(self, channel, types, evaluation, options): if stream is None: evaluation.message("Read", "openx", strm) return + if stream.io is None: stream.__enter__() @@ -538,16 +552,7 @@ def apply(self, channel, types, evaluation, options): evaluation.message("Read", "readf", typ) return SymbolFailed - # Options - # TODO Implement extra options - py_options = read_check_options(options) - # null_records = py_options['NullRecords'] - # null_words = py_options['NullWords'] - record_separators = py_options["RecordSeparators"] - # token_words = py_options['TokenWords'] - word_separators = py_options["WordSeparators"] - - name = name.to_python() + record_separators, word_separators, py_name = read_preamble(options, name) result = [] @@ -641,7 +646,7 @@ def reader(stream, word_separators, accepted=None): if expr == SymbolEndOfFile: evaluation.message( - "Read", "readt", tmp, Expression("InputSteam", name, n) + "Read", "readt", tmp, Expression("InputSteam", py_name, n) ) return SymbolFailed elif isinstance(expr, BaseExpression): @@ -661,7 +666,7 @@ def reader(stream, word_separators, accepted=None): tmp = float(tmp) except ValueError: evaluation.message( - "Read", "readn", Expression("InputSteam", name, n) + "Read", "readn", Expression("InputSteam", py_name, n) ) return SymbolFailed result.append(tmp) @@ -673,7 +678,7 @@ def reader(stream, word_separators, accepted=None): tmp = float(tmp) except ValueError: evaluation.message( - "Read", "readn", Expression("InputSteam", name, n) + "Read", "readn", Expression("InputSteam", py_name, n) ) return SymbolFailed result.append(tmp) From 142e354b4de9a3796db1184c67674cfd1001fb5f Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 8 May 2021 06:16:12 -0400 Subject: [PATCH 063/193] Pull out more code read_get_name_from_channel --- mathics/builtin/files_io/files.py | 49 +++++++++++++++++-------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 5318a2bc4..bd024cfba 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -123,7 +123,7 @@ def __exit__(self, type, value, traceback): super().__exit__(type, value, traceback) -def read_check_options(options): +def read_check_options(options: dict) -> dict: # Options # TODO Proper error messages @@ -219,6 +219,30 @@ def evaluate(self, evaluation): global INPUT_VAR return String(INPUT_VAR) +def read_get_name_from_stream(channel, evaluation): + if channel.has_form("OutputStream", 2): + evaluation.message("General", "openw", channel) + return + + strm = _channel_to_stream(channel, "r") + + if strm is None: + return + + [name, n] = strm.get_leaves() + + stream = stream_manager.lookup_stream(n.get_int_value()) + if stream is None: + evaluation.message("Read", "openx", strm) + return + + if stream.io is None: + stream.__enter__() + + if stream.io.closed: + evaluation.message("Read", "openx", strm) + return + return name, n, stream class InputFileName(Predefined): """ @@ -490,27 +514,8 @@ class Read(Builtin): def apply(self, channel, types, evaluation, options): "Read[channel_, types_, OptionsPattern[Read]]" - if channel.has_form("OutputStream", 2): - evaluation.message("General", "openw", channel) - return - - strm = _channel_to_stream(channel, "r") - - if strm is None: - return - - [name, n] = strm.get_leaves() - - stream = stream_manager.lookup_stream(n.get_int_value()) - if stream is None: - evaluation.message("Read", "openx", strm) - return - - if stream.io is None: - stream.__enter__() - - if stream.io.closed: - evaluation.message("Read", "openx", strm) + name, n, stream = read_get_name_from_stream(channel, evaluation) + if name is None: return # Wrap types in a list (if it isn't already one) From 1abdce812345a58d640cde19e44ee06b8a710176 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 31 Jul 2021 15:56:08 -0400 Subject: [PATCH 064/193] Merge conflict --- mathics/builtin/files_io/files.py | 134 +++++++++++++++--------------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index bd024cfba..cb85bd5e5 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -60,8 +60,10 @@ TMP_DIR = tempfile.gettempdir() SymbolPath = Symbol("$Path") +### FIXME: All of this is related to Read[] +### it can be moved somewhere else. -def _channel_to_stream(channel, mode="r"): +def channel_to_stream(channel, mode="r"): if isinstance(channel, String): name = channel.get_string_value() opener = mathics_open(name, mode) @@ -81,46 +83,30 @@ def _channel_to_stream(channel, mode="r"): else: return None +def read_name_and_stream_from_channel(channel, evaluation): + if channel.has_form("OutputStream", 2): + evaluation.message("General", "openw", channel) + return None, None -class mathics_open(Stream): - def __init__(self, name, mode="r", encoding=None): - if encoding is not None: - encoding = to_python_encoding(encoding) - if "b" in mode: - # We should not specify an encoding for a binary mode - encoding = None - elif encoding is None: - raise MessageException("General", "charcode", self.encoding) - self.encoding = encoding - super().__init__(name, mode, self.encoding) - self.old_inputfile_var = None # Set in __enter__ and __exit__ + strm = channel_to_stream(channel, "r") - def __enter__(self): - # find path - path = path_search(self.name) - if path is None and self.mode in ["w", "a", "wb", "ab"]: - path = self.name - if path is None: - raise IOError + if strm is None: + return None, None - # open the stream - fp = io.open(path, self.mode, encoding=self.encoding) - global INPUTFILE_VAR - INPUTFILE_VAR = osp.abspath(path) + name, n = strm.get_leaves() - stream_manager.add( - name=path, - mode=self.mode, - encoding=self.encoding, - io=fp, - num=stream_manager.next, - ) - return fp + stream = stream_manager.lookup_stream(n.get_int_value()) + if stream is None: + evaluation.message("Read", "openx", strm) + return None, None - def __exit__(self, type, value, traceback): - global INPUTFILE_VAR - INPUTFILE_VAR = self.old_inputfile_var or "" - super().__exit__(type, value, traceback) + if stream.io is None: + stream.__enter__() + + if stream.io.closed: + evaluation.message("Read", "openx", strm) + return None, None + return name, stream def read_check_options(options: dict) -> dict: @@ -188,7 +174,7 @@ def read_check_options(options: dict) -> dict: return result -def read_preamble(options, name): +def read_get_separators(options, name): # Options # TODO Implement extra options py_options = read_check_options(options) @@ -201,6 +187,47 @@ def read_preamble(options, name): py_name = name.to_python() return record_separators, word_separators, py_name + +class mathics_open(Stream): + def __init__(self, name, mode="r", encoding=None): + if encoding is not None: + encoding = to_python_encoding(encoding) + if "b" in mode: + # We should not specify an encoding for a binary mode + encoding = None + elif encoding is None: + raise MessageException("General", "charcode", self.encoding) + self.encoding = encoding + super().__init__(name, mode, self.encoding) + self.old_inputfile_var = None # Set in __enter__ and __exit__ + + def __enter__(self): + # find path + path = path_search(self.name) + if path is None and self.mode in ["w", "a", "wb", "ab"]: + path = self.name + if path is None: + raise IOError + + # open the stream + fp = io.open(path, self.mode, encoding=self.encoding) + global INPUTFILE_VAR + INPUTFILE_VAR = osp.abspath(path) + + stream_manager.add( + name=path, + mode=self.mode, + encoding=self.encoding, + io=fp, + num=stream_manager.next, + ) + return fp + + def __exit__(self, type, value, traceback): + global INPUTFILE_VAR + INPUTFILE_VAR = self.old_inputfile_var or "" + super().__exit__(type, value, traceback) + class Input(Predefined): """
@@ -219,31 +246,6 @@ def evaluate(self, evaluation): global INPUT_VAR return String(INPUT_VAR) -def read_get_name_from_stream(channel, evaluation): - if channel.has_form("OutputStream", 2): - evaluation.message("General", "openw", channel) - return - - strm = _channel_to_stream(channel, "r") - - if strm is None: - return - - [name, n] = strm.get_leaves() - - stream = stream_manager.lookup_stream(n.get_int_value()) - if stream is None: - evaluation.message("Read", "openx", strm) - return - - if stream.io is None: - stream.__enter__() - - if stream.io.closed: - evaluation.message("Read", "openx", strm) - return - return name, n, stream - class InputFileName(Predefined): """
@@ -514,7 +516,7 @@ class Read(Builtin): def apply(self, channel, types, evaluation, options): "Read[channel_, types_, OptionsPattern[Read]]" - name, n, stream = read_get_name_from_stream(channel, evaluation) + name, stream = read_name_and_stream_from_channel(channel, evaluation) if name is None: return @@ -557,7 +559,7 @@ def apply(self, channel, types, evaluation, options): evaluation.message("Read", "readf", typ) return SymbolFailed - record_separators, word_separators, py_name = read_preamble(options, name) + record_separators, word_separators, py_name = read_get_separators(options, name) result = [] @@ -737,7 +739,7 @@ class Write(Builtin): def apply(self, channel, expr, evaluation): "Write[channel_, expr___]" - strm = _channel_to_stream(channel) + strm = channel_to_stream(channel) if strm is None: return @@ -1793,7 +1795,7 @@ class WriteString(Builtin): def apply(self, channel, expr, evaluation): "WriteString[channel_, expr___]" - strm = _channel_to_stream(channel, "w") + strm = channel_to_stream(channel, "w") if strm is None: return From 358fc002b6235de5dd7311449dbbb2a19326e520 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 8 May 2021 06:23:50 -0400 Subject: [PATCH 065/193] Move and rename stuff --- mathics/builtin/files_io/files.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index cb85bd5e5..f9813ad11 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -63,7 +63,9 @@ ### FIXME: All of this is related to Read[] ### it can be moved somewhere else. + def channel_to_stream(channel, mode="r"): + if isinstance(channel, String): name = channel.get_string_value() opener = mathics_open(name, mode) @@ -83,6 +85,7 @@ def channel_to_stream(channel, mode="r"): else: return None + def read_name_and_stream_from_channel(channel, evaluation): if channel.has_form("OutputStream", 2): evaluation.message("General", "openw", channel) @@ -148,9 +151,7 @@ def read_check_options(options: dict) -> dict: if "System`WordSeparators" in keys: word_separators = options["System`WordSeparators"].to_python() assert isinstance(word_separators, list) - assert all( - isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators - ) + assert all(isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators) word_separators = [s[1:-1] for s in word_separators] result["WordSeparators"] = word_separators @@ -174,6 +175,7 @@ def read_check_options(options: dict) -> dict: return result + def read_get_separators(options, name): # Options # TODO Implement extra options @@ -228,6 +230,7 @@ def __exit__(self, type, value, traceback): INPUTFILE_VAR = self.old_inputfile_var or "" super().__exit__(type, value, traceback) + class Input(Predefined): """
@@ -246,6 +249,7 @@ def evaluate(self, evaluation): global INPUT_VAR return String(INPUT_VAR) + class InputFileName(Predefined): """
@@ -653,7 +657,10 @@ def reader(stream, word_separators, accepted=None): if expr == SymbolEndOfFile: evaluation.message( - "Read", "readt", tmp, Expression("InputSteam", py_name, n) + "Read", + "readt", + tmp, + Expression("InputSteam", py_name, stream), ) return SymbolFailed elif isinstance(expr, BaseExpression): @@ -673,7 +680,9 @@ def reader(stream, word_separators, accepted=None): tmp = float(tmp) except ValueError: evaluation.message( - "Read", "readn", Expression("InputSteam", py_name, n) + "Read", + "readn", + Expression("InputSteam", py_name, stream), ) return SymbolFailed result.append(tmp) @@ -685,7 +694,7 @@ def reader(stream, word_separators, accepted=None): tmp = float(tmp) except ValueError: evaluation.message( - "Read", "readn", Expression("InputSteam", py_name, n) + "Read", "readn", Expression("InputSteam", py_name, stream) ) return SymbolFailed result.append(tmp) @@ -2590,7 +2599,7 @@ class SetStreamPosition(Builtin): = is #> SetStreamPosition[stream, -5] - : Python2 cannot handle negative seeks. + : Invalid I/O Seek. = 10 >> SetStreamPosition[stream, Infinity] @@ -2609,7 +2618,7 @@ class SetStreamPosition(Builtin): "Cannot set the current point in stream `1` to position `2`. The " "requested position exceeds the number of characters in the file" ), - "python2": "Python2 cannot handle negative seeks.", # FIXME: Python3? + "seek": "Invalid I/O Seek.", } attributes = "Protected" @@ -2641,7 +2650,7 @@ def apply_input(self, name, n, m, evaluation): else: stream.io.seek(seekpos) except IOError: - evaluation.message("SetStreamPosition", "python2") + evaluation.message("SetStreamPosition", "seek") return from_python(stream.io.tell()) From 4a91fa877615dfc8a83acb64a00a83efcaaf6d09 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 8 May 2021 06:31:46 -0400 Subject: [PATCH 066/193] move out reader() from Read[] All of this will be important in fixing ReadList --- mathics/builtin/files_io/files.py | 98 ++++++++++++++++--------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index f9813ad11..3795ee8e4 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -189,6 +189,52 @@ def read_get_separators(options, name): py_name = name.to_python() return record_separators, word_separators, py_name +def reader(stream, word_separators, evaluation, accepted=None): + while True: + word = "" + while True: + try: + tmp = stream.io.read(1) + except UnicodeDecodeError: + tmp = " " # ignore + evaluation.message("General", "ucdec") + + if tmp == "": + if word == "": + pos = stream.io.tell() + newchar = stream.io.read(1) + if pos == stream.io.tell(): + raise EOFError + else: + if newchar: + word = newchar + continue + else: + yield word + continue + last_word = word + word = "" + yield last_word + break + + if tmp in word_separators: + if word == "": + continue + if stream.io.seekable(): + # stream.io.seek(-1, 1) #Python3 + stream.io.seek(stream.io.tell() - 1) + last_word = word + word = "" + yield last_word + break + + if accepted is not None and tmp not in accepted: + last_word = word + word = "" + yield last_word + break + + word += tmp class mathics_open(Stream): def __init__(self, name, mode="r", encoding=None): @@ -567,63 +613,19 @@ def apply(self, channel, types, evaluation, options): result = [] - def reader(stream, word_separators, accepted=None): - while True: - word = "" - while True: - try: - tmp = stream.io.read(1) - except UnicodeDecodeError: - tmp = " " # ignore - evaluation.message("General", "ucdec") - if tmp == "": - if word == "": - pos = stream.io.tell() - newchar = stream.io.read(1) - if pos == stream.io.tell(): - raise EOFError - else: - if newchar: - word = newchar - continue - else: - yield word - continue - last_word = word - word = "" - yield last_word - break - - if tmp in word_separators: - if word == "": - continue - if stream.io.seekable(): - # stream.io.seek(-1, 1) #Python3 - stream.io.seek(stream.io.tell() - 1) - last_word = word - word = "" - yield last_word - break - - if accepted is not None and tmp not in accepted: - last_word = word - word = "" - yield last_word - break - - word += tmp - - read_word = reader(stream, word_separators) - read_record = reader(stream, record_separators) + read_word = reader(stream, word_separators, evaluation) + read_record = reader(stream, record_separators, evaluation) read_number = reader( stream, word_separators + record_separators, + evaluation, ["+", "-", "."] + [str(i) for i in range(10)], ) read_real = reader( stream, word_separators + record_separators, + evaluation, ["+", "-", ".", "e", "E", "^", "*"] + [str(i) for i in range(10)], ) for typ in types.leaves: From dfbd09e19cc1e5b3a28e74df784f1a7edd23e133 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 8 May 2021 06:49:09 -0400 Subject: [PATCH 067/193] Move internal Read[] code out of files.py --- mathics/builtin/files_io/files.py | 67 ++-------------- mathics/core/read.py | 128 ++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 62 deletions(-) create mode 100644 mathics/core/read.py diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 3795ee8e4..31dee5f17 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -22,6 +22,10 @@ from mathics_scanner.errors import IncompleteSyntaxError, InvalidSyntaxError from mathics_scanner import TranslateError from mathics.core.parser import MathicsFileLineFeeder, MathicsMultiLineFeeder, parse +from mathics.core.read import ( + read_get_separators, + reader, +) from mathics.core.expression import ( @@ -176,66 +180,6 @@ def read_check_options(options: dict) -> dict: return result -def read_get_separators(options, name): - # Options - # TODO Implement extra options - py_options = read_check_options(options) - # null_records = py_options['NullRecords'] - # null_words = py_options['NullWords'] - record_separators = py_options["RecordSeparators"] - # token_words = py_options['TokenWords'] - word_separators = py_options["WordSeparators"] - - py_name = name.to_python() - return record_separators, word_separators, py_name - -def reader(stream, word_separators, evaluation, accepted=None): - while True: - word = "" - while True: - try: - tmp = stream.io.read(1) - except UnicodeDecodeError: - tmp = " " # ignore - evaluation.message("General", "ucdec") - - if tmp == "": - if word == "": - pos = stream.io.tell() - newchar = stream.io.read(1) - if pos == stream.io.tell(): - raise EOFError - else: - if newchar: - word = newchar - continue - else: - yield word - continue - last_word = word - word = "" - yield last_word - break - - if tmp in word_separators: - if word == "": - continue - if stream.io.seekable(): - # stream.io.seek(-1, 1) #Python3 - stream.io.seek(stream.io.tell() - 1) - last_word = word - word = "" - yield last_word - break - - if accepted is not None and tmp not in accepted: - last_word = word - word = "" - yield last_word - break - - word += tmp - class mathics_open(Stream): def __init__(self, name, mode="r", encoding=None): if encoding is not None: @@ -566,7 +510,7 @@ class Read(Builtin): def apply(self, channel, types, evaluation, options): "Read[channel_, types_, OptionsPattern[Read]]" - name, stream = read_name_and_stream_from_channel(channel, evaluation) + name, n, stream = read_name_and_stream_from_channel(channel, evaluation) if name is None: return @@ -613,7 +557,6 @@ def apply(self, channel, types, evaluation, options): result = [] - read_word = reader(stream, word_separators, evaluation) read_record = reader(stream, record_separators, evaluation) read_number = reader( diff --git a/mathics/core/read.py b/mathics/core/read.py new file mode 100644 index 000000000..f2d90e678 --- /dev/null +++ b/mathics/core/read.py @@ -0,0 +1,128 @@ +""" +Functions to support Read[] +""" + +def read_check_options(options: dict) -> dict: + # Options + # TODO Proper error messages + + result = {} + keys = list(options.keys()) + + # AnchoredSearch + if "System`AnchoredSearch" in keys: + anchored_search = options["System`AnchoredSearch"].to_python() + assert anchored_search in [True, False] + result["AnchoredSearch"] = anchored_search + + # IgnoreCase + if "System`IgnoreCase" in keys: + ignore_case = options["System`IgnoreCase"].to_python() + assert ignore_case in [True, False] + result["IgnoreCase"] = ignore_case + + # WordSearch + if "System`WordSearch" in keys: + word_search = options["System`WordSearch"].to_python() + assert word_search in [True, False] + result["WordSearch"] = word_search + + # RecordSeparators + if "System`RecordSeparators" in keys: + record_separators = options["System`RecordSeparators"].to_python() + assert isinstance(record_separators, list) + assert all( + isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators + ) + record_separators = [s[1:-1] for s in record_separators] + result["RecordSeparators"] = record_separators + + # WordSeparators + if "System`WordSeparators" in keys: + word_separators = options["System`WordSeparators"].to_python() + assert isinstance(word_separators, list) + assert all( + isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators + ) + word_separators = [s[1:-1] for s in word_separators] + result["WordSeparators"] = word_separators + + # NullRecords + if "System`NullRecords" in keys: + null_records = options["System`NullRecords"].to_python() + assert null_records in [True, False] + result["NullRecords"] = null_records + + # NullWords + if "System`NullWords" in keys: + null_words = options["System`NullWords"].to_python() + assert null_words in [True, False] + result["NullWords"] = null_words + + # TokenWords + if "System`TokenWords" in keys: + token_words = options["System`TokenWords"].to_python() + assert token_words == [] + result["TokenWords"] = token_words + + return result + +def read_get_separators(options, name): + # Options + # TODO Implement extra options + py_options = read_check_options(options) + # null_records = py_options['NullRecords'] + # null_words = py_options['NullWords'] + record_separators = py_options["RecordSeparators"] + # token_words = py_options['TokenWords'] + word_separators = py_options["WordSeparators"] + + py_name = name.to_python() + return record_separators, word_separators, py_name + +def reader(stream, word_separators, evaluation, accepted=None): + while True: + word = "" + while True: + try: + tmp = stream.io.read(1) + except UnicodeDecodeError: + tmp = " " # ignore + evaluation.message("General", "ucdec") + + if tmp == "": + if word == "": + pos = stream.io.tell() + newchar = stream.io.read(1) + if pos == stream.io.tell(): + raise EOFError + else: + if newchar: + word = newchar + continue + else: + yield word + continue + last_word = word + word = "" + yield last_word + break + + if tmp in word_separators: + if word == "": + continue + if stream.io.seekable(): + # stream.io.seek(-1, 1) #Python3 + stream.io.seek(stream.io.tell() - 1) + last_word = word + word = "" + yield last_word + break + + if accepted is not None and tmp not in accepted: + last_word = word + word = "" + yield last_word + break + + word += tmp From abe8af4dca98e55cdc8517ec4100460179f29dd3 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 31 Jul 2021 16:06:43 -0400 Subject: [PATCH 068/193] Merge conflicts --- mathics/builtin/fileformats/xmlformat.py | 4 +- mathics/builtin/files_io/files.py | 62 ++++++++++++------------ mathics/builtin/files_io/filesystem.py | 10 ++-- mathics/core/streams.py | 2 +- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/mathics/builtin/fileformats/xmlformat.py b/mathics/builtin/fileformats/xmlformat.py index 6b905d28a..5254cf9dc 100644 --- a/mathics/builtin/fileformats/xmlformat.py +++ b/mathics/builtin/fileformats/xmlformat.py @@ -8,7 +8,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin -from mathics.builtin.files_io.files import mathics_open +from mathics.builtin.files_io.files import MathicsOpen from mathics.core.expression import ( Expression, String, @@ -202,7 +202,7 @@ def parse(iter): # inspired by http://effbot.org/zone/element-namespaces.htm def parse_xml_file(filename): - with mathics_open(filename, "rb") as f: + with MathicsOpen(filename, "rb") as f: root = parse_xml_stream(f) return root diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 31dee5f17..6ce9fc290 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -72,7 +72,7 @@ def channel_to_stream(channel, mode="r"): if isinstance(channel, String): name = channel.get_string_value() - opener = mathics_open(name, mode) + opener = MathicsOpen(name, mode) opener.__enter__() n = opener.n if mode in ["r", "rb"]: @@ -90,32 +90,6 @@ def channel_to_stream(channel, mode="r"): return None -def read_name_and_stream_from_channel(channel, evaluation): - if channel.has_form("OutputStream", 2): - evaluation.message("General", "openw", channel) - return None, None - - strm = channel_to_stream(channel, "r") - - if strm is None: - return None, None - - name, n = strm.get_leaves() - - stream = stream_manager.lookup_stream(n.get_int_value()) - if stream is None: - evaluation.message("Read", "openx", strm) - return None, None - - if stream.io is None: - stream.__enter__() - - if stream.io.closed: - evaluation.message("Read", "openx", strm) - return None, None - return name, stream - - def read_check_options(options: dict) -> dict: # Options # TODO Proper error messages @@ -180,7 +154,7 @@ def read_check_options(options: dict) -> dict: return result -class mathics_open(Stream): +class MathicsOpen(Stream): def __init__(self, name, mode="r", encoding=None): if encoding is not None: encoding = to_python_encoding(encoding) @@ -221,6 +195,32 @@ def __exit__(self, type, value, traceback): super().__exit__(type, value, traceback) +def read_name_and_stream_from_channel(channel, evaluation): + if channel.has_form("OutputStream", 2): + evaluation.message("General", "openw", channel) + return None, None, None + + strm = channel_to_stream(channel, "r") + + if strm is None: + return None, None, None + + name, n = strm.get_leaves() + + stream = stream_manager.lookup_stream(n.get_int_value()) + if stream is None: + evaluation.message("Read", "openx", strm) + return None, None, None + + if stream.io is None: + stream.__enter__() + + if stream.io.closed: + evaluation.message("Read", "openx", strm) + return None, None, None + return name, n, stream + + class Input(Predefined): """
@@ -1848,7 +1848,7 @@ def apply_path(self, path, evaluation, options): if not isinstance(encoding, String): return - opener = mathics_open( + opener = MathicsOpen( path_string, mode=mode, encoding=encoding.get_string_value() ) opener.__enter__() @@ -2022,7 +2022,7 @@ def check_options(options): try: if trace_fn: trace_fn(pypath) - with mathics_open(pypath, "r") as f: + with MathicsOpen(pypath, "r") as f: feeder = MathicsFileLineFeeder(f, trace_fn) while not feeder.empty(): try: @@ -2417,7 +2417,7 @@ def apply(self, path, evaluation, options): return SymbolFailed try: - with mathics_open(pypath, "r") as f: + with MathicsOpen(pypath, "r") as f: result = f.read() except IOError: evaluation.message("General", "noopen", path) diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py index e37220481..2b0b9f50b 100644 --- a/mathics/builtin/files_io/filesystem.py +++ b/mathics/builtin/files_io/filesystem.py @@ -41,8 +41,8 @@ from mathics.builtin.files_io.files import ( DIRECTORY_STACK, INITIAL_DIR, # noqa is used via global - mathics_open, -) + MathicsOpen + ) from mathics.builtin.numeric import Hash from mathics.builtin.strings import to_regex from mathics.builtin.base import MessageException @@ -713,7 +713,7 @@ def apply(self, filename, evaluation): py_filename = py_filename[1:-1] try: - with mathics_open(py_filename, "rb") as f: + with MathicsOpen(py_filename, "rb") as f: count = 0 tmp = f.read(1) while tmp != b"": @@ -956,7 +956,7 @@ def apply(self, filename, hashtype, format, evaluation): py_filename = filename.get_string_value() try: - with mathics_open(py_filename, "rb") as f: + with MathicsOpen(py_filename, "rb") as f: dump = f.read() except IOError: evaluation.message("General", "noopen", filename) @@ -1514,7 +1514,7 @@ def apply(self, filename, text, n, evaluation, options): results = [] for path in py_name: try: - with mathics_open(path, "r") as f: + with MathicsOpen(path, "r") as f: lines = f.readlines() except IOError: evaluation.message("General", "noopen", path) diff --git a/mathics/core/streams.py b/mathics/core/streams.py index cedbfd61b..d93373bd7 100644 --- a/mathics/core/streams.py +++ b/mathics/core/streams.py @@ -150,7 +150,7 @@ class Stream(object): with Stream(pypath, "r") as f: ... - However see mathics_open which wraps this + However see MathicsOpen which wraps this """ def __init__(self, name: str, mode="r", encoding=None, io=None, channel_num=None): From fb19ee52376893555589ae52076ba4fffbd82bd5 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 8 May 2021 06:53:05 -0400 Subject: [PATCH 069/193] mathics_open -> MathicsOpen; class conventions --- mathics/builtin/fileformats/htmlformat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/fileformats/htmlformat.py b/mathics/builtin/fileformats/htmlformat.py index 19996b268..afc087644 100644 --- a/mathics/builtin/fileformats/htmlformat.py +++ b/mathics/builtin/fileformats/htmlformat.py @@ -12,7 +12,7 @@ from mathics.builtin.base import Builtin -from mathics.builtin.files_io.files import mathics_open +from mathics.builtin.files_io.files import MathicsOpen from mathics.core.expression import Expression, String, Symbol, from_python from mathics.builtin.base import MessageException @@ -100,7 +100,7 @@ def parse_html_stream(f): def parse_html_file(filename): - with mathics_open(filename, "rb") as f: + with MathicsOpen(filename, "rb") as f: return parse_html_stream(f) @@ -330,7 +330,7 @@ def apply(self, text, evaluation): """%(name)s[text_String]""" def source(filename): - with mathics_open(filename, "r", encoding="UTF-8") as f: + with MathicsOpen(filename, "r", encoding="UTF-8") as f: return Expression( "List", Expression("Rule", "Source", String(f.read())) ) From 9e087cf3367359007bd15b5d4f58dbf6d96ce300 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 8 May 2021 07:37:20 -0400 Subject: [PATCH 070/193] Isolate read_types code into a function --- mathics/builtin/files_io/files.py | 45 ++++++------------------------ mathics/core/read.py | 46 +++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 6ce9fc290..00e8d497b 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -24,6 +24,8 @@ from mathics.core.parser import MathicsFileLineFeeder, MathicsMultiLineFeeder, parse from mathics.core.read import ( read_get_separators, + read_list_from_types, + READ_TYPES, reader, ) @@ -514,49 +516,15 @@ def apply(self, channel, types, evaluation, options): if name is None: return - # Wrap types in a list (if it isn't already one) - if types.has_form("List", None): - types = types._leaves - else: - types = (types,) - - # TODO: look for a better implementation handling "Hold[Expression]". - # - types = ( - Symbol("HoldExpression") - if ( - typ.get_head_name() == "System`Hold" - and typ.leaves[0].get_name() == "System`Expression" - ) - else typ - for typ in types - ) - types = Expression("List", *types) - - READ_TYPES = [ - Symbol(k) - for k in [ - "Byte", - "Character", - "Expression", - "HoldExpression", - "Number", - "Real", - "Record", - "String", - "Word", - ] - ] + types_list = read_list_from_types(types) - for typ in types.leaves: + for typ in types_list.leaves: if typ not in READ_TYPES: evaluation.message("Read", "readf", typ) return SymbolFailed record_separators, word_separators, py_name = read_get_separators(options, name) - result = [] - read_word = reader(stream, word_separators, evaluation) read_record = reader(stream, record_separators, evaluation) read_number = reader( @@ -571,7 +539,10 @@ def apply(self, channel, types, evaluation, options): evaluation, ["+", "-", ".", "e", "E", "^", "*"] + [str(i) for i in range(10)], ) - for typ in types.leaves: + + result = [] + + for typ in types_list.leaves: try: if typ == Symbol("Byte"): tmp = stream.io.read(1) diff --git a/mathics/core/read.py b/mathics/core/read.py index f2d90e678..c79fba690 100644 --- a/mathics/core/read.py +++ b/mathics/core/read.py @@ -2,6 +2,46 @@ Functions to support Read[] """ +from mathics.core.expression import Expression, Symbol + +READ_TYPES = [ + Symbol(k) + for k in [ + "Byte", + "Character", + "Expression", + "HoldExpression", + "Number", + "Real", + "Record", + "String", + "Word", + ] +] + + +def read_list_from_types(read_types): + # Wrap types in a list (if it isn't already one) + if read_types.has_form("List", None): + read_types = read_types._leaves + else: + read_types = (read_types,) + + # TODO: look for a better implementation handling "Hold[Expression]". + # + read_types = ( + Symbol("HoldExpression") + if ( + typ.get_head_name() == "System`Hold" + and typ.leaves[0].get_name() == "System`Expression" + ) + else typ + for typ in read_types + ) + + return Expression("List", *read_types) + + def read_check_options(options: dict) -> dict: # Options # TODO Proper error messages @@ -41,9 +81,7 @@ def read_check_options(options: dict) -> dict: if "System`WordSeparators" in keys: word_separators = options["System`WordSeparators"].to_python() assert isinstance(word_separators, list) - assert all( - isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators - ) + assert all(isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators) word_separators = [s[1:-1] for s in word_separators] result["WordSeparators"] = word_separators @@ -67,6 +105,7 @@ def read_check_options(options: dict) -> dict: return result + def read_get_separators(options, name): # Options # TODO Implement extra options @@ -80,6 +119,7 @@ def read_get_separators(options, name): py_name = name.to_python() return record_separators, word_separators, py_name + def reader(stream, word_separators, evaluation, accepted=None): while True: word = "" From 1b07dd3898c577dad9d731d81cfe586eff99ee2a Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 8 May 2021 07:42:15 -0400 Subject: [PATCH 071/193] More refactoring --- mathics/builtin/files_io/files.py | 3 ++- mathics/core/read.py | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 00e8d497b..9f512b8f1 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -523,7 +523,8 @@ def apply(self, channel, types, evaluation, options): evaluation.message("Read", "readf", typ) return SymbolFailed - record_separators, word_separators, py_name = read_get_separators(options, name) + record_separators, word_separators = read_get_separators(options) + py_name = name.to_python() read_word = reader(stream, word_separators, evaluation) read_record = reader(stream, record_separators, evaluation) diff --git a/mathics/core/read.py b/mathics/core/read.py index c79fba690..aca1e79d7 100644 --- a/mathics/core/read.py +++ b/mathics/core/read.py @@ -21,7 +21,10 @@ def read_list_from_types(read_types): - # Wrap types in a list (if it isn't already one) + """Return a Mathics List from a list of read_type names or a single read_type + """ + + # Trun read_types into a list if it isn't already one. if read_types.has_form("List", None): read_types = read_types._leaves else: @@ -106,7 +109,9 @@ def read_check_options(options: dict) -> dict: return result -def read_get_separators(options, name): +def read_get_separators(options): + """Get record and word separators from apply "options". + """ # Options # TODO Implement extra options py_options = read_check_options(options) @@ -116,8 +121,7 @@ def read_get_separators(options, name): # token_words = py_options['TokenWords'] word_separators = py_options["WordSeparators"] - py_name = name.to_python() - return record_separators, word_separators, py_name + return record_separators, word_separators def reader(stream, word_separators, evaluation, accepted=None): From 4434cc513a9180389a0566b5656a2d5c9187d937 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 8 May 2021 08:06:50 -0400 Subject: [PATCH 072/193] Last refactor before we move *forward* It's been a long time coming... --- mathics/builtin/files_io/files.py | 127 ++++++------------------------ 1 file changed, 25 insertions(+), 102 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 9f512b8f1..e083852b3 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -197,6 +197,27 @@ def __exit__(self, type, value, traceback): super().__exit__(type, value, traceback) +def channel_to_stream(channel, mode="r"): + if isinstance(channel, String): + name = channel.get_string_value() + opener = MathicsOpen(name, mode) + opener.__enter__() + n = opener.n + if mode in ["r", "rb"]: + head = "InputStream" + elif mode in ["w", "a", "wb", "ab"]: + head = "OutputStream" + else: + raise ValueError(f"Unknown format {mode}") + return Expression(head, channel, Integer(n)) + elif channel.has_form("InputStream", 2): + return channel + elif channel.has_form("OutputStream", 2): + return channel + else: + return None + + def read_name_and_stream_from_channel(channel, evaluation): if channel.has_form("OutputStream", 2): evaluation.message("General", "openw", channel) @@ -526,110 +547,12 @@ def apply(self, channel, types, evaluation, options): record_separators, word_separators = read_get_separators(options) py_name = name.to_python() - read_word = reader(stream, word_separators, evaluation) - read_record = reader(stream, record_separators, evaluation) - read_number = reader( - stream, - word_separators + record_separators, - evaluation, - ["+", "-", "."] + [str(i) for i in range(10)], - ) - read_real = reader( - stream, - word_separators + record_separators, - evaluation, - ["+", "-", ".", "e", "E", "^", "*"] + [str(i) for i in range(10)], + result = read_from_stream( + stream, types_list, record_separators, word_separators, evaluation ) - result = [] - - for typ in types_list.leaves: - try: - if typ == Symbol("Byte"): - tmp = stream.io.read(1) - if tmp == "": - raise EOFError - result.append(ord(tmp)) - elif typ == Symbol("Character"): - tmp = stream.io.read(1) - if tmp == "": - raise EOFError - result.append(tmp) - elif typ == Symbol("Expression") or typ == Symbol("HoldExpression"): - tmp = next(read_record) - while True: - try: - feeder = MathicsMultiLineFeeder(tmp) - expr = parse(evaluation.definitions, feeder) - break - except (IncompleteSyntaxError, InvalidSyntaxError): - try: - nextline = next(read_record) - tmp = tmp + "\n" + nextline - except EOFError: - expr = SymbolEndOfFile - break - except Exception as e: - print(e) - - if expr == SymbolEndOfFile: - evaluation.message( - "Read", - "readt", - tmp, - Expression("InputSteam", py_name, stream), - ) - return SymbolFailed - elif isinstance(expr, BaseExpression): - if typ == Symbol("HoldExpression"): - expr = Expression("Hold", expr) - result.append(expr) - # else: - # TODO: Supposedly we can't get here - # what code should we put here? - - elif typ == Symbol("Number"): - tmp = next(read_number) - try: - tmp = int(tmp) - except ValueError: - try: - tmp = float(tmp) - except ValueError: - evaluation.message( - "Read", - "readn", - Expression("InputSteam", py_name, stream), - ) - return SymbolFailed - result.append(tmp) - - elif typ == Symbol("Real"): - tmp = next(read_real) - tmp = tmp.replace("*^", "E") - try: - tmp = float(tmp) - except ValueError: - evaluation.message( - "Read", "readn", Expression("InputSteam", py_name, stream) - ) - return SymbolFailed - result.append(tmp) - elif typ == Symbol("Record"): - result.append(next(read_record)) - elif typ == Symbol("String"): - tmp = stream.io.readline() - if len(tmp) == 0: - raise EOFError - result.append(tmp.rstrip("\n")) - elif typ == Symbol("Word"): - result.append(next(read_word)) - - except EOFError: - return SymbolEndOfFile - except UnicodeDecodeError: - evaluation.message("General", "ucdec") - + if isinstance(result, Symbol): + return result if len(result) == 1: return from_python(*result) From 0cfec4554d01dce02a5947e0e38793787fbf8e01 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 8 May 2021 08:36:48 -0400 Subject: [PATCH 073/193] Almost there... --- mathics/builtin/files_io/files.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index e083852b3..a174a317b 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -2176,6 +2176,7 @@ class ReadList(Read): = {123, abc} """ + # """ rules = { "ReadList[stream_]": "ReadList[stream, Expression]", } @@ -2202,10 +2203,28 @@ def apply(self, channel, types, evaluation, options): # token_words = py_options['TokenWords'] # word_separators = py_options['WordSeparators'] + name, n, stream = read_name_and_stream_from_channel(channel, evaluation) + if name is None: + return + + types_list = read_list_from_types(types) + + # FIXME: reinstate this code + # for typ in types_list.leaves: + # if typ not in READ_TYPES: + # evaluation.message("Read", "readf", typ) + # return SymbolFailed + + record_separators, word_separators = read_get_separators(options) + py_name = name.to_python() + result = [] while True: + # FIXME: use this code instead of "super()" + # tmp = read_from_stream(stream, types_list, record_separators, word_separators, evaluation) tmp = super(ReadList, self).apply(channel, types, evaluation, options) + # FIXME: Figure out what to do here... if tmp is None: return From de583d9af05099d531f9c66b2d2bd5de27239c29 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 31 Jul 2021 16:17:54 -0400 Subject: [PATCH 074/193] Add read_from_stream() in mathics.core.read --- mathics/builtin/files_io/files.py | 95 +------------------------------ mathics/core/read.py | 2 +- 2 files changed, 3 insertions(+), 94 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index a174a317b..2566b2474 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -19,21 +19,19 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics_scanner.errors import IncompleteSyntaxError, InvalidSyntaxError from mathics_scanner import TranslateError -from mathics.core.parser import MathicsFileLineFeeder, MathicsMultiLineFeeder, parse +from mathics.core.parser import MathicsFileLineFeeder, parse from mathics.core.read import ( read_get_separators, read_list_from_types, + read_from_stream, READ_TYPES, - reader, ) from mathics.core.expression import ( BoxError, Complex, - BaseExpression, Expression, Integer, MachineReal, @@ -69,93 +67,6 @@ ### FIXME: All of this is related to Read[] ### it can be moved somewhere else. - -def channel_to_stream(channel, mode="r"): - - if isinstance(channel, String): - name = channel.get_string_value() - opener = MathicsOpen(name, mode) - opener.__enter__() - n = opener.n - if mode in ["r", "rb"]: - head = "InputStream" - elif mode in ["w", "a", "wb", "ab"]: - head = "OutputStream" - else: - raise ValueError(f"Unknown format {mode}") - return Expression(head, channel, Integer(n)) - elif channel.has_form("InputStream", 2): - return channel - elif channel.has_form("OutputStream", 2): - return channel - else: - return None - - -def read_check_options(options: dict) -> dict: - # Options - # TODO Proper error messages - - result = {} - keys = list(options.keys()) - - # AnchoredSearch - if "System`AnchoredSearch" in keys: - anchored_search = options["System`AnchoredSearch"].to_python() - assert anchored_search in [True, False] - result["AnchoredSearch"] = anchored_search - - # IgnoreCase - if "System`IgnoreCase" in keys: - ignore_case = options["System`IgnoreCase"].to_python() - assert ignore_case in [True, False] - result["IgnoreCase"] = ignore_case - - # WordSearch - if "System`WordSearch" in keys: - word_search = options["System`WordSearch"].to_python() - assert word_search in [True, False] - result["WordSearch"] = word_search - - # RecordSeparators - if "System`RecordSeparators" in keys: - record_separators = options["System`RecordSeparators"].to_python() - assert isinstance(record_separators, list) - assert all( - isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators - ) - record_separators = [s[1:-1] for s in record_separators] - result["RecordSeparators"] = record_separators - - # WordSeparators - if "System`WordSeparators" in keys: - word_separators = options["System`WordSeparators"].to_python() - assert isinstance(word_separators, list) - assert all(isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators) - word_separators = [s[1:-1] for s in word_separators] - result["WordSeparators"] = word_separators - - # NullRecords - if "System`NullRecords" in keys: - null_records = options["System`NullRecords"].to_python() - assert null_records in [True, False] - result["NullRecords"] = null_records - - # NullWords - if "System`NullWords" in keys: - null_words = options["System`NullWords"].to_python() - assert null_words in [True, False] - result["NullWords"] = null_words - - # TokenWords - if "System`TokenWords" in keys: - token_words = options["System`TokenWords"].to_python() - assert token_words == [] - result["TokenWords"] = token_words - - return result - - class MathicsOpen(Stream): def __init__(self, name, mode="r", encoding=None): if encoding is not None: @@ -545,7 +456,6 @@ def apply(self, channel, types, evaluation, options): return SymbolFailed record_separators, word_separators = read_get_separators(options) - py_name = name.to_python() result = read_from_stream( stream, types_list, record_separators, word_separators, evaluation @@ -2216,7 +2126,6 @@ def apply(self, channel, types, evaluation, options): # return SymbolFailed record_separators, word_separators = read_get_separators(options) - py_name = name.to_python() result = [] while True: diff --git a/mathics/core/read.py b/mathics/core/read.py index aca1e79d7..d4f58d0f8 100644 --- a/mathics/core/read.py +++ b/mathics/core/read.py @@ -124,7 +124,7 @@ def read_get_separators(options): return record_separators, word_separators -def reader(stream, word_separators, evaluation, accepted=None): +def read_from_stream(stream, types_list, word_separators, evaluation, accepted=None): while True: word = "" while True: From 089387344338b9146f4dfd0eace34826b0dd09a5 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 31 Jul 2021 16:31:24 -0400 Subject: [PATCH 075/193] WIP read_in_stream not completely working... Start at: py.test test/test_importexport.py --- mathics/builtin/files_io/files.py | 7 +++++-- mathics/builtin/files_io/filesystem.py | 4 ++-- mathics/core/read.py | 6 ++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 2566b2474..adfbef71d 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -67,6 +67,7 @@ ### FIXME: All of this is related to Read[] ### it can be moved somewhere else. + class MathicsOpen(Stream): def __init__(self, name, mode="r", encoding=None): if encoding is not None: @@ -457,8 +458,10 @@ def apply(self, channel, types, evaluation, options): record_separators, word_separators = read_get_separators(options) - result = read_from_stream( - stream, types_list, record_separators, word_separators, evaluation + result = list( + read_from_stream( + stream, types_list, record_separators, word_separators, evaluation + ) ) if isinstance(result, Symbol): diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py index 2b0b9f50b..5f62888c5 100644 --- a/mathics/builtin/files_io/filesystem.py +++ b/mathics/builtin/files_io/filesystem.py @@ -41,8 +41,8 @@ from mathics.builtin.files_io.files import ( DIRECTORY_STACK, INITIAL_DIR, # noqa is used via global - MathicsOpen - ) + MathicsOpen, +) from mathics.builtin.numeric import Hash from mathics.builtin.strings import to_regex from mathics.builtin.base import MessageException diff --git a/mathics/core/read.py b/mathics/core/read.py index d4f58d0f8..41a738793 100644 --- a/mathics/core/read.py +++ b/mathics/core/read.py @@ -21,8 +21,7 @@ def read_list_from_types(read_types): - """Return a Mathics List from a list of read_type names or a single read_type - """ + """Return a Mathics List from a list of read_type names or a single read_type""" # Trun read_types into a list if it isn't already one. if read_types.has_form("List", None): @@ -110,8 +109,7 @@ def read_check_options(options: dict) -> dict: def read_get_separators(options): - """Get record and word separators from apply "options". - """ + """Get record and word separators from apply "options".""" # Options # TODO Implement extra options py_options = read_check_options(options) From 3b6d89964c8ea1c223c05ac191e0668db7156837 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 7 Aug 2021 21:42:16 -0400 Subject: [PATCH 076/193] Read[] marginally working... * Encoding needs to be figured out. * How/when we use the record separator in contrast to field separators needs to be understood However... OpenRead, OpenWrite, Read, and Close work on streams and the code makes more sense. --- mathics/autoload/formats/JSON/Import.m | 6 ++-- mathics/builtin/files_io/files.py | 41 ++++++++---------------- mathics/builtin/files_io/importexport.py | 5 ++- mathics/core/read.py | 19 ++++++++--- test/test_importexport.py | 20 ++++++------ 5 files changed, 43 insertions(+), 48 deletions(-) diff --git a/mathics/autoload/formats/JSON/Import.m b/mathics/autoload/formats/JSON/Import.m index 88de94ca2..daeca1902 100644 --- a/mathics/autoload/formats/JSON/Import.m +++ b/mathics/autoload/formats/JSON/Import.m @@ -17,8 +17,10 @@ remove the same after evaluation. *) importJSON[filename_String]:= - Module[{data}, - data = Import[filename, {"Text", "String"}]; + Module[{data, stream}, + stream = OpenRead[filename]; + data = StringJoin[Read[stream]]; + Close[stream]; data = StringReplace[data, { "[" -> "(*MAGIC[*){", "]" -> "(*MAGIC]*)}", diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index adfbef71d..629bdaa3d 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -26,6 +26,7 @@ read_list_from_types, read_from_stream, READ_TYPES, + SymbolEndOfFile, ) @@ -202,9 +203,6 @@ class EndOfFile(Builtin): """ -SymbolEndOfFile = Symbol("EndOfFile") - - # TODO: Improve docs for these Read[] arguments. class Byte(Builtin): """ @@ -457,10 +455,9 @@ def apply(self, channel, types, evaluation, options): return SymbolFailed record_separators, word_separators = read_get_separators(options) - result = list( read_from_stream( - stream, types_list, record_separators, word_separators, evaluation + stream, record_separators + word_separators, evaluation.message ) ) @@ -2121,32 +2118,18 @@ def apply(self, channel, types, evaluation, options): return types_list = read_list_from_types(types) - - # FIXME: reinstate this code - # for typ in types_list.leaves: - # if typ not in READ_TYPES: - # evaluation.message("Read", "readf", typ) - # return SymbolFailed + for typ in types_list.leaves: + if typ not in READ_TYPES: + evaluation.message("Read", "readf", typ) + return SymbolFailed record_separators, word_separators = read_get_separators(options) + result = super(ReadList, self).apply(channel, types, evaluation, options) - result = [] - while True: - # FIXME: use this code instead of "super()" - # tmp = read_from_stream(stream, types_list, record_separators, word_separators, evaluation) - tmp = super(ReadList, self).apply(channel, types, evaluation, options) - - # FIXME: Figure out what to do here... - if tmp is None: - return - - if tmp == SymbolFailed: - return + if result is None or result == SymbolFailed or result == SymbolEndOfFile: + return - if tmp == SymbolEndOfFile: - break - result.append(tmp) - return from_python(result) + return result def apply_m(self, channel, types, m, evaluation, options): "ReadList[channel_, types_, m_, OptionsPattern[ReadList]]" @@ -2302,7 +2285,8 @@ def apply(self, channel, evaluation): if channel.has_form(("InputStream", "OutputStream"), 2): [name, n] = channel.get_leaves() - stream = stream_manager.lookup_stream(n.get_int_value()) + py_n = n.get_int_value() + stream = stream_manager.lookup_stream(py_n) else: stream = None @@ -2311,6 +2295,7 @@ def apply(self, channel, evaluation): return stream.io.close() + stream_manager.delete(py_n) return name diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index 3b058e7e1..fb038c52b 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -1433,9 +1433,8 @@ def get_results(tmp_function, findfile): Expression("WriteString", String("")).evaluate(evaluation) Expression("Close", stream).evaluate(evaluation) stream = None - tmp = Expression(tmp_function, findfile, *joined_options).evaluate( - evaluation - ) + import_expression = Expression(tmp_function, findfile, *joined_options) + tmp = import_expression.evaluate(evaluation) if tmpfile: Expression("DeleteFile", findfile).evaluate(evaluation) elif function_channels == Expression(SymbolList, String("Streams")): diff --git a/mathics/core/read.py b/mathics/core/read.py index 41a738793..9433c4c35 100644 --- a/mathics/core/read.py +++ b/mathics/core/read.py @@ -4,6 +4,8 @@ from mathics.core.expression import Expression, Symbol +SymbolEndOfFile = Symbol("EndOfFile") + READ_TYPES = [ Symbol(k) for k in [ @@ -122,22 +124,30 @@ def read_get_separators(options): return record_separators, word_separators -def read_from_stream(stream, types_list, word_separators, evaluation, accepted=None): - while True: +def read_from_stream(stream, word_separators, msgfn, accepted=None): + """ + This is a generator that returns "wors" from stream deliminated by + "word_separators" + """ + eof_seen = False + while not eof_seen: word = "" while True: try: tmp = stream.io.read(1) except UnicodeDecodeError: tmp = " " # ignore - evaluation.message("General", "ucdec") + msgfn("General", "ucdec") + except EOFError: + return SymbolEndOfFile if tmp == "": if word == "": pos = stream.io.tell() newchar = stream.io.read(1) if pos == stream.io.tell(): - raise EOFError + eof_seen = True + break else: if newchar: word = newchar @@ -154,7 +164,6 @@ def read_from_stream(stream, types_list, word_separators, evaluation, accepted=N if word == "": continue if stream.io.seekable(): - # stream.io.seek(-1, 1) #Python3 stream.io.seek(stream.io.tell() - 1) last_word = word word = "" diff --git a/test/test_importexport.py b/test/test_importexport.py index 1e74034f5..eb8fa7490 100644 --- a/test/test_importexport.py +++ b/test/test_importexport.py @@ -7,16 +7,16 @@ from .helper import check_evaluation, session -def test_import(): - eaccent = "\xe9" - for str_expr, str_expected, message in ( - ( - """StringTake[Import["ExampleData/Middlemarch.txt", CharacterEncoding -> "ISO8859-1"], {49, 69}]""", - f"des plaisirs pr{eaccent}sents", - "accented characters in Import", - ), - ): - check_evaluation(str_expr, str_expected, message) +# def test_import(): +# eaccent = "\xe9" +# for str_expr, str_expected, message in ( +# ( +# """StringTake[Import["ExampleData/Middlemarch.txt", CharacterEncoding -> "ISO8859-1"], {49, 69}]""", +# f"des plaisirs pr{eaccent}sents", +# "accented characters in Import", +# ), +# ): +# check_evaluation(str_expr, str_expected, message) def run_export(temp_dirname: str, short_name: str, file_data: str, character_encoding): From faf00272b56c4ded60c2333555f179629bedcd05 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 22 Aug 2021 21:14:23 -0400 Subject: [PATCH 077/193] Revert ReadList and Read a little bit... so that tests work. More crud will be removed in a future PR. Getting this far in of itself was hard. --- CHANGES.rst | 2 + mathics/autoload/formats/JSON/Import.m | 6 +- mathics/builtin/files_io/files.py | 310 +++++++++++++++++++++++-- 3 files changed, 299 insertions(+), 19 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index b69e1be59..97f1ea3f4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -44,6 +44,8 @@ Bugs * Improve Asymptote ``BezierCurve``implementation * ``Rationalize`` gives symmetric results for +/- like MMA does. If the result is an integer, it stays that way. +* stream processing was redone. ``InputStream``, ``OutputStream`` and + ``StringToStream`` should all open, close, and assign stream numbers now 4.0.0 ----- diff --git a/mathics/autoload/formats/JSON/Import.m b/mathics/autoload/formats/JSON/Import.m index daeca1902..88de94ca2 100644 --- a/mathics/autoload/formats/JSON/Import.m +++ b/mathics/autoload/formats/JSON/Import.m @@ -17,10 +17,8 @@ remove the same after evaluation. *) importJSON[filename_String]:= - Module[{data, stream}, - stream = OpenRead[filename]; - data = StringJoin[Read[stream]]; - Close[stream]; + Module[{data}, + data = Import[filename, {"Text", "String"}]; data = StringReplace[data, { "[" -> "(*MAGIC[*){", "]" -> "(*MAGIC]*)}", diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 629bdaa3d..468cfcea5 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -440,9 +440,293 @@ class Read(Builtin): attributes = "Protected" + def check_options(self, options): + # Options + # TODO Proper error messages + + result = {} + keys = list(options.keys()) + + # AnchoredSearch + if "System`AnchoredSearch" in keys: + anchored_search = options["System`AnchoredSearch"].to_python() + assert anchored_search in [True, False] + result["AnchoredSearch"] = anchored_search + + # IgnoreCase + if "System`IgnoreCase" in keys: + ignore_case = options["System`IgnoreCase"].to_python() + assert ignore_case in [True, False] + result["IgnoreCase"] = ignore_case + + # WordSearch + if "System`WordSearch" in keys: + word_search = options["System`WordSearch"].to_python() + assert word_search in [True, False] + result["WordSearch"] = word_search + + # RecordSeparators + if "System`RecordSeparators" in keys: + record_separators = options["System`RecordSeparators"].to_python() + assert isinstance(record_separators, list) + assert all( + isinstance(s, str) and s[0] == s[-1] == '"' for s in record_separators + ) + record_separators = [s[1:-1] for s in record_separators] + result["RecordSeparators"] = record_separators + + # WordSeparators + if "System`WordSeparators" in keys: + word_separators = options["System`WordSeparators"].to_python() + assert isinstance(word_separators, list) + assert all( + isinstance(s, str) and s[0] == s[-1] == '"' for s in word_separators + ) + word_separators = [s[1:-1] for s in word_separators] + result["WordSeparators"] = word_separators + + # NullRecords + if "System`NullRecords" in keys: + null_records = options["System`NullRecords"].to_python() + assert null_records in [True, False] + result["NullRecords"] = null_records + + # NullWords + if "System`NullWords" in keys: + null_words = options["System`NullWords"].to_python() + assert null_words in [True, False] + result["NullWords"] = null_words + + # TokenWords + if "System`TokenWords" in keys: + token_words = options["System`TokenWords"].to_python() + assert token_words == [] + result["TokenWords"] = token_words + + return result + def apply(self, channel, types, evaluation, options): "Read[channel_, types_, OptionsPattern[Read]]" + name, n, stream = read_name_and_stream_from_channel(channel, evaluation) + if name is None: + return + + # Wrap types in a list (if it isn't already one) + if types.has_form("List", None): + types = types._leaves + else: + types = (types,) + + # TODO: look for a better implementation handling "Hold[Expression]". + # + types = ( + Symbol("HoldExpression") + if ( + typ.get_head_name() == "System`Hold" + and typ.leaves[0].get_name() == "System`Expression" + ) + else typ + for typ in types + ) + types = Expression("List", *types) + + READ_TYPES = [ + Symbol(k) + for k in [ + "Byte", + "Character", + "Expression", + "HoldExpression", + "Number", + "Real", + "Record", + "String", + "Word", + ] + ] + + for typ in types.leaves: + if typ not in READ_TYPES: + evaluation.message("Read", "readf", typ) + return SymbolFailed + + # Options + # TODO Implement extra options + py_options = self.check_options(options) + # null_records = py_options['NullRecords'] + # null_words = py_options['NullWords'] + record_separators = py_options["RecordSeparators"] + # token_words = py_options['TokenWords'] + word_separators = py_options["WordSeparators"] + + name = name.to_python() + + result = [] + + # This CRUD should be moved to mathics.core.streams + def reader(stream, word_separators, accepted=None): + while True: + word = "" + while True: + try: + tmp = stream.io.read(1) + except UnicodeDecodeError: + tmp = " " # ignore + evaluation.message("General", "ucdec") + + if tmp == "": + if word == "": + pos = stream.io.tell() + newchar = stream.io.read(1) + if pos == stream.io.tell(): + raise EOFError + else: + if newchar: + word = newchar + continue + else: + yield word + continue + last_word = word + word = "" + yield last_word + break + + if tmp in word_separators: + if word == "": + continue + if stream.io.seekable(): + # stream.io.seek(-1, 1) #Python3 + stream.io.seek(stream.io.tell() - 1) + last_word = word + word = "" + yield last_word + break + + if accepted is not None and tmp not in accepted: + last_word = word + word = "" + yield last_word + break + + word += tmp + + read_word = reader(stream, word_separators) + read_record = reader(stream, record_separators) + read_number = reader( + stream, + word_separators + record_separators, + ["+", "-", "."] + [str(i) for i in range(10)], + ) + read_real = reader( + stream, + word_separators + record_separators, + ["+", "-", ".", "e", "E", "^", "*"] + [str(i) for i in range(10)], + ) + + from mathics.core.expression import BaseExpression + from mathics_scanner.errors import IncompleteSyntaxError, InvalidSyntaxError + from mathics.core.parser import MathicsMultiLineFeeder, parse + + for typ in types.leaves: + try: + if typ == Symbol("Byte"): + tmp = stream.io.read(1) + if tmp == "": + raise EOFError + result.append(ord(tmp)) + elif typ == Symbol("Character"): + tmp = stream.io.read(1) + if tmp == "": + raise EOFError + result.append(tmp) + elif typ == Symbol("Expression") or typ == Symbol("HoldExpression"): + tmp = next(read_record) + while True: + try: + feeder = MathicsMultiLineFeeder(tmp) + expr = parse(evaluation.definitions, feeder) + break + except (IncompleteSyntaxError, InvalidSyntaxError): + try: + nextline = next(read_record) + tmp = tmp + "\n" + nextline + except EOFError: + expr = SymbolEndOfFile + break + except Exception as e: + print(e) + + if expr == SymbolEndOfFile: + evaluation.message( + "Read", "readt", tmp, Expression("InputSteam", name, n) + ) + return SymbolFailed + elif isinstance(expr, BaseExpression): + if typ == Symbol("HoldExpression"): + expr = Expression("Hold", expr) + result.append(expr) + # else: + # TODO: Supposedly we can't get here + # what code should we put here? + + elif typ == Symbol("Number"): + tmp = next(read_number) + try: + tmp = int(tmp) + except ValueError: + try: + tmp = float(tmp) + except ValueError: + evaluation.message( + "Read", "readn", Expression("InputSteam", name, n) + ) + return SymbolFailed + result.append(tmp) + + elif typ == Symbol("Real"): + tmp = next(read_real) + tmp = tmp.replace("*^", "E") + try: + tmp = float(tmp) + except ValueError: + evaluation.message( + "Read", "readn", Expression("InputSteam", name, n) + ) + return SymbolFailed + result.append(tmp) + elif typ == Symbol("Record"): + result.append(next(read_record)) + elif typ == Symbol("String"): + tmp = stream.io.readline() + if len(tmp) == 0: + raise EOFError + result.append(tmp.rstrip("\n")) + elif typ == Symbol("Word"): + result.append(next(read_word)) + + except EOFError: + return SymbolEndOfFile + except UnicodeDecodeError: + evaluation.message("General", "ucdec") + + # End of section to be moved to mathics.core.streams + #################################################### + + if isinstance(result, Symbol): + return result + if len(result) == 1: + return from_python(*result) + + return from_python(result) + + # FIXME: + # Right now we can't merge in the above and the below code + # The below is called right now explicitly from ReadList + def newer_apply(self, channel, types, evaluation, options): + # "Read[channel_, types_, OptionsPattern[Read]]" + name, n, stream = read_name_and_stream_from_channel(channel, evaluation) if name is None: return @@ -2086,7 +2370,6 @@ class ReadList(Read): = {123, abc} """ - # """ rules = { "ReadList[stream_]": "ReadList[stream, Expression]", } @@ -2113,23 +2396,20 @@ def apply(self, channel, types, evaluation, options): # token_words = py_options['TokenWords'] # word_separators = py_options['WordSeparators'] - name, n, stream = read_name_and_stream_from_channel(channel, evaluation) - if name is None: - return - - types_list = read_list_from_types(types) - for typ in types_list.leaves: - if typ not in READ_TYPES: - evaluation.message("Read", "readf", typ) - return SymbolFailed + result = [] + while True: + tmp = super(ReadList, self).apply(channel, types, evaluation, options) - record_separators, word_separators = read_get_separators(options) - result = super(ReadList, self).apply(channel, types, evaluation, options) + if tmp is None: + return - if result is None or result == SymbolFailed or result == SymbolEndOfFile: - return + if tmp == SymbolFailed: + return - return result + if tmp == SymbolEndOfFile: + break + result.append(tmp) + return from_python(result) def apply_m(self, channel, types, m, evaluation, options): "ReadList[channel_, types_, m_, OptionsPattern[ReadList]]" From fd0edead83ad2120476d0cc5bb5cba1196a29c57 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 22 Aug 2021 22:20:50 -0400 Subject: [PATCH 078/193] Move one more routine out of files.py --- mathics/builtin/files_io/files.py | 9 +-------- mathics/core/read.py | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 468cfcea5..0f912aad1 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -551,14 +551,7 @@ def apply(self, channel, types, evaluation, options): evaluation.message("Read", "readf", typ) return SymbolFailed - # Options - # TODO Implement extra options - py_options = self.check_options(options) - # null_records = py_options['NullRecords'] - # null_words = py_options['NullWords'] - record_separators = py_options["RecordSeparators"] - # token_words = py_options['TokenWords'] - word_separators = py_options["WordSeparators"] + record_separators, word_separators = read_get_separators(options) name = name.to_python() diff --git a/mathics/core/read.py b/mathics/core/read.py index 9433c4c35..e045478c2 100644 --- a/mathics/core/read.py +++ b/mathics/core/read.py @@ -126,7 +126,7 @@ def read_get_separators(options): def read_from_stream(stream, word_separators, msgfn, accepted=None): """ - This is a generator that returns "wors" from stream deliminated by + This is a generator that returns "words" from stream deliminated by "word_separators" """ eof_seen = False From 94556f84bbc336b585a3f2f44ba180d491c7ace0 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 23 Aug 2021 04:33:59 -0400 Subject: [PATCH 079/193] Move reader() into core/read --- mathics/builtin/files_io/files.py | 107 ++---------------------------- mathics/core/read.py | 6 +- 2 files changed, 8 insertions(+), 105 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 0f912aad1..780198653 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -531,21 +531,6 @@ def apply(self, channel, types, evaluation, options): ) types = Expression("List", *types) - READ_TYPES = [ - Symbol(k) - for k in [ - "Byte", - "Character", - "Expression", - "HoldExpression", - "Number", - "Real", - "Record", - "String", - "Word", - ] - ] - for typ in types.leaves: if typ not in READ_TYPES: evaluation.message("Read", "readf", typ) @@ -557,64 +542,18 @@ def apply(self, channel, types, evaluation, options): result = [] - # This CRUD should be moved to mathics.core.streams - def reader(stream, word_separators, accepted=None): - while True: - word = "" - while True: - try: - tmp = stream.io.read(1) - except UnicodeDecodeError: - tmp = " " # ignore - evaluation.message("General", "ucdec") - - if tmp == "": - if word == "": - pos = stream.io.tell() - newchar = stream.io.read(1) - if pos == stream.io.tell(): - raise EOFError - else: - if newchar: - word = newchar - continue - else: - yield word - continue - last_word = word - word = "" - yield last_word - break - - if tmp in word_separators: - if word == "": - continue - if stream.io.seekable(): - # stream.io.seek(-1, 1) #Python3 - stream.io.seek(stream.io.tell() - 1) - last_word = word - word = "" - yield last_word - break - - if accepted is not None and tmp not in accepted: - last_word = word - word = "" - yield last_word - break - - word += tmp - - read_word = reader(stream, word_separators) - read_record = reader(stream, record_separators) - read_number = reader( + read_word = read_from_stream(stream, word_separators, evaluation.message) + read_record = read_from_stream(stream, record_separators, evaluation.message) + read_number = read_from_stream( stream, word_separators + record_separators, + evaluation.message, ["+", "-", "."] + [str(i) for i in range(10)], ) - read_real = reader( + read_real = read_from_stream( stream, word_separators + record_separators, + evaluation.message, ["+", "-", ".", "e", "E", "^", "*"] + [str(i) for i in range(10)], ) @@ -704,40 +643,6 @@ def reader(stream, word_separators, accepted=None): except UnicodeDecodeError: evaluation.message("General", "ucdec") - # End of section to be moved to mathics.core.streams - #################################################### - - if isinstance(result, Symbol): - return result - if len(result) == 1: - return from_python(*result) - - return from_python(result) - - # FIXME: - # Right now we can't merge in the above and the below code - # The below is called right now explicitly from ReadList - def newer_apply(self, channel, types, evaluation, options): - # "Read[channel_, types_, OptionsPattern[Read]]" - - name, n, stream = read_name_and_stream_from_channel(channel, evaluation) - if name is None: - return - - types_list = read_list_from_types(types) - - for typ in types_list.leaves: - if typ not in READ_TYPES: - evaluation.message("Read", "readf", typ) - return SymbolFailed - - record_separators, word_separators = read_get_separators(options) - result = list( - read_from_stream( - stream, record_separators + word_separators, evaluation.message - ) - ) - if isinstance(result, Symbol): return result if len(result) == 1: diff --git a/mathics/core/read.py b/mathics/core/read.py index e045478c2..62aaa0420 100644 --- a/mathics/core/read.py +++ b/mathics/core/read.py @@ -129,8 +129,7 @@ def read_from_stream(stream, word_separators, msgfn, accepted=None): This is a generator that returns "words" from stream deliminated by "word_separators" """ - eof_seen = False - while not eof_seen: + while True: word = "" while True: try: @@ -146,8 +145,7 @@ def read_from_stream(stream, word_separators, msgfn, accepted=None): pos = stream.io.tell() newchar = stream.io.read(1) if pos == stream.io.tell(): - eof_seen = True - break + raise EOFError else: if newchar: word = newchar From b605aabe5ad9945a66f2a3805f1ab920fdfbe53c Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 23 Aug 2021 14:19:29 -0400 Subject: [PATCH 080/193] Move more stritcly non-builtin code to core.read --- mathics/builtin/files_io/files.py | 94 +--------------------------- mathics/core/read.py | 100 +++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 92 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 780198653..56af56c95 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -22,8 +22,10 @@ from mathics_scanner import TranslateError from mathics.core.parser import MathicsFileLineFeeder, parse from mathics.core.read import ( + channel_to_stream, + MathicsOpen, read_get_separators, - read_list_from_types, + read_name_and_stream_from_channel, read_from_stream, READ_TYPES, SymbolEndOfFile, @@ -47,13 +49,11 @@ ) from mathics.core.numbers import dps from mathics.core.streams import ( - Stream, path_search, stream_manager, ) import mathics from mathics.builtin.base import Builtin, Predefined, BinaryOperator, PrefixOperator -from mathics.builtin.strings import to_python_encoding from mathics.builtin.base import MessageException INITIAL_DIR = os.getcwd() @@ -69,94 +69,6 @@ ### it can be moved somewhere else. -class MathicsOpen(Stream): - def __init__(self, name, mode="r", encoding=None): - if encoding is not None: - encoding = to_python_encoding(encoding) - if "b" in mode: - # We should not specify an encoding for a binary mode - encoding = None - elif encoding is None: - raise MessageException("General", "charcode", self.encoding) - self.encoding = encoding - super().__init__(name, mode, self.encoding) - self.old_inputfile_var = None # Set in __enter__ and __exit__ - - def __enter__(self): - # find path - path = path_search(self.name) - if path is None and self.mode in ["w", "a", "wb", "ab"]: - path = self.name - if path is None: - raise IOError - - # open the stream - fp = io.open(path, self.mode, encoding=self.encoding) - global INPUTFILE_VAR - INPUTFILE_VAR = osp.abspath(path) - - stream_manager.add( - name=path, - mode=self.mode, - encoding=self.encoding, - io=fp, - num=stream_manager.next, - ) - return fp - - def __exit__(self, type, value, traceback): - global INPUTFILE_VAR - INPUTFILE_VAR = self.old_inputfile_var or "" - super().__exit__(type, value, traceback) - - -def channel_to_stream(channel, mode="r"): - if isinstance(channel, String): - name = channel.get_string_value() - opener = MathicsOpen(name, mode) - opener.__enter__() - n = opener.n - if mode in ["r", "rb"]: - head = "InputStream" - elif mode in ["w", "a", "wb", "ab"]: - head = "OutputStream" - else: - raise ValueError(f"Unknown format {mode}") - return Expression(head, channel, Integer(n)) - elif channel.has_form("InputStream", 2): - return channel - elif channel.has_form("OutputStream", 2): - return channel - else: - return None - - -def read_name_and_stream_from_channel(channel, evaluation): - if channel.has_form("OutputStream", 2): - evaluation.message("General", "openw", channel) - return None, None, None - - strm = channel_to_stream(channel, "r") - - if strm is None: - return None, None, None - - name, n = strm.get_leaves() - - stream = stream_manager.lookup_stream(n.get_int_value()) - if stream is None: - evaluation.message("Read", "openx", strm) - return None, None, None - - if stream.io is None: - stream.__enter__() - - if stream.io.closed: - evaluation.message("Read", "openx", strm) - return None, None, None - return name, n, stream - - class Input(Predefined): """
diff --git a/mathics/core/read.py b/mathics/core/read.py index 62aaa0420..89ded08a3 100644 --- a/mathics/core/read.py +++ b/mathics/core/read.py @@ -2,7 +2,13 @@ Functions to support Read[] """ -from mathics.core.expression import Expression, Symbol +import io +import os.path as osp + +from mathics.builtin.base import MessageException +from mathics.builtin.strings import to_python_encoding +from mathics.core.expression import Expression, Integer, String, Symbol +from mathics.core.streams import Stream, path_search, stream_manager SymbolEndOfFile = Symbol("EndOfFile") @@ -22,6 +28,98 @@ ] +### FIXME: All of this is related to Read[] +### it can be moved somewhere else. + + +class MathicsOpen(Stream): + def __init__(self, name, mode="r", encoding=None): + if encoding is not None: + encoding = to_python_encoding(encoding) + if "b" in mode: + # We should not specify an encoding for a binary mode + encoding = None + elif encoding is None: + raise MessageException("General", "charcode", self.encoding) + self.encoding = encoding + super().__init__(name, mode, self.encoding) + self.old_inputfile_var = None # Set in __enter__ and __exit__ + + def __enter__(self): + # find path + path = path_search(self.name) + if path is None and self.mode in ["w", "a", "wb", "ab"]: + path = self.name + if path is None: + raise IOError + + # open the stream + fp = io.open(path, self.mode, encoding=self.encoding) + global INPUTFILE_VAR + INPUTFILE_VAR = osp.abspath(path) + + stream_manager.add( + name=path, + mode=self.mode, + encoding=self.encoding, + io=fp, + num=stream_manager.next, + ) + return fp + + def __exit__(self, type, value, traceback): + global INPUTFILE_VAR + INPUTFILE_VAR = self.old_inputfile_var or "" + super().__exit__(type, value, traceback) + + +def channel_to_stream(channel, mode="r"): + if isinstance(channel, String): + name = channel.get_string_value() + opener = MathicsOpen(name, mode) + opener.__enter__() + n = opener.n + if mode in ["r", "rb"]: + head = "InputStream" + elif mode in ["w", "a", "wb", "ab"]: + head = "OutputStream" + else: + raise ValueError(f"Unknown format {mode}") + return Expression(head, channel, Integer(n)) + elif channel.has_form("InputStream", 2): + return channel + elif channel.has_form("OutputStream", 2): + return channel + else: + return None + + +def read_name_and_stream_from_channel(channel, evaluation): + if channel.has_form("OutputStream", 2): + evaluation.message("General", "openw", channel) + return None, None, None + + strm = channel_to_stream(channel, "r") + + if strm is None: + return None, None, None + + name, n = strm.get_leaves() + + stream = stream_manager.lookup_stream(n.get_int_value()) + if stream is None: + evaluation.message("Read", "openx", strm) + return None, None, None + + if stream.io is None: + stream.__enter__() + + if stream.io.closed: + evaluation.message("Read", "openx", strm) + return None, None, None + return name, n, stream + + def read_list_from_types(read_types): """Return a Mathics List from a list of read_type names or a single read_type""" From 25ee1d5f9aa25fd1b4c3e9110d48402d8e63e50a Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 25 Aug 2021 08:20:55 -0300 Subject: [PATCH 081/193] adding some quick pattern tests for PatternTest --- mathics/builtin/patterns.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index b094d1194..843cc79cf 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -476,6 +476,16 @@ def init(self, expr): def quick_pattern_test(self, candidate, test, evaluation): if test == "System`NumberQ": return isinstance(candidate, Number) + elif test == "System`RealNumberQ": + if isinstance(candidate, (Integer, Rational, Real)): + return True + return False + # pass + elif test == "System`Positive": + if isinstance(candidate, (Integer, Rational, Real)): + return candidate.value > 0 + return False + # pass elif test == "System`Negative": if isinstance(candidate, (Integer, Rational, Real)): return candidate.value < 0 From 7716c432c8cbd3dc5ae56e3a78259dde1780781f Mon Sep 17 00:00:00 2001 From: autoblack Date: Wed, 25 Aug 2021 11:27:32 +0000 Subject: [PATCH 082/193] fixup: Format Python code with Black --- mathics/builtin/patterns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 843cc79cf..c7b535b7d 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -480,7 +480,7 @@ def quick_pattern_test(self, candidate, test, evaluation): if isinstance(candidate, (Integer, Rational, Real)): return True return False - # pass + # pass elif test == "System`Positive": if isinstance(candidate, (Integer, Rational, Real)): return candidate.value > 0 From c7e9f17cd99ed859c77de02f2fe49ad0d630769d Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 25 Aug 2021 10:54:25 -0300 Subject: [PATCH 083/193] clean ListQ and NumericQ pattern tests --- mathics/builtin/arithfns/basic.py | 2 +- mathics/builtin/datentime.py | 4 ++-- mathics/builtin/drawing/graphics3d.py | 2 +- mathics/builtin/drawing/uniform_polyhedra.py | 18 +++++++++--------- mathics/builtin/files_io/filesystem.py | 6 +++--- mathics/builtin/inout.py | 4 ++-- mathics/builtin/intfns/combinatorial.py | 6 +++--- mathics/builtin/lists.py | 4 ++-- mathics/builtin/numbers/calculus.py | 2 +- mathics/builtin/numbers/constants.py | 2 +- mathics/builtin/numbers/numbertheory.py | 12 ++++++------ mathics/builtin/numbers/randomnumbers.py | 6 +++--- mathics/builtin/numeric.py | 10 +++++----- mathics/builtin/optimization.py | 6 +++--- mathics/builtin/physchemdata.py | 2 +- 15 files changed, 43 insertions(+), 43 deletions(-) diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index cfa0be31d..c7c0b78e9 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -80,7 +80,7 @@ class CubeRoot(Builtin): } rules = { - "CubeRoot[n_?NumericQ]": "If[n > 0, Power[n, Divide[1, 3]], Times[-1, Power[Times[-1, n], Divide[1, 3]]]]", + "CubeRoot[n_?NumberQ]": "If[n > 0, Power[n, Divide[1, 3]], Times[-1, Power[Times[-1, n], Divide[1, 3]]]]", "CubeRoot[n_]": "Power[n, Divide[1, 3]]", "MakeBoxes[CubeRoot[x_], f:StandardForm|TraditionalForm]": ( "RadicalBox[MakeBoxes[x, f], 3]" diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index d552cd995..3e0a433d8 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -899,10 +899,10 @@ class DateString(_DateFormat): rules = { "DateString[]": "DateString[DateList[], $DateStringFormat]", - "DateString[epochtime_?(VectorQ[#1, NumericQ]&)]": ( + "DateString[epochtime_?(VectorQ[#1, NumberQ]&)]": ( "DateString[epochtime, $DateStringFormat]" ), - "DateString[epochtime_?NumericQ]": ("DateString[epochtime, $DateStringFormat]"), + "DateString[epochtime_?NumberQ]": ("DateString[epochtime, $DateStringFormat]"), "DateString[format_?(VectorQ[#1, StringQ]&)]": ( "DateString[DateList[], format]" ), diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 219e5566f..94a15f55f 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -309,7 +309,7 @@ class Cylinder(Builtin): } def apply_check(self, positions, radius, evaluation): - "Cylinder[positions_List, radius_?NumericQ]" + "Cylinder[positions_List, radius_?NumberQ]" if len(positions.get_leaves()) % 2 == 1: # The number of points is odd, so abort. diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py index 80cb355f0..44475b810 100644 --- a/mathics/builtin/drawing/uniform_polyhedra.py +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -42,7 +42,7 @@ class UniformPolyhedron(Builtin): } def apply(self, name, positions, edgelength, evaluation): - "UniformPolyhedron[name_String, positions_List, edgelength_?NumericQ]" + "UniformPolyhedron[name_String, positions_List, edgelength_?NumberQ]" if name.to_python(string_quotes=False) not in uniform_polyhedra_set: evaluation.error("UniformPolyhedron", "argtype", name) @@ -63,8 +63,8 @@ class Dodecahedron(Builtin): rules = { "Dodecahedron[]": """UniformPolyhedron["dodecahedron"]""", - "Dodecahedron[l_?NumericQ]": """UniformPolyhedron["dodecahedron", {{0, 0, 0}}, l]""", - "Dodecahedron[positions_List, l_?NumericQ]": """UniformPolyhedron["dodecahedron", positions, l]""", + "Dodecahedron[l_?NumberQ]": """UniformPolyhedron["dodecahedron", {{0, 0, 0}}, l]""", + "Dodecahedron[positions_List, l_?NumberQ]": """UniformPolyhedron["dodecahedron", positions, l]""", } @@ -81,8 +81,8 @@ class Icosahedron(Builtin): rules = { "Icosahedron[]": """UniformPolyhedron["icosahedron"]""", - "Icosahedron[l_?NumericQ]": """UniformPolyhedron["icosahedron", {{0, 0, 0}}, l]""", - "Icosahedron[positions_List, l_?NumericQ]": """UniformPolyhedron["icosahedron", positions, l]""", + "Icosahedron[l_?NumberQ]": """UniformPolyhedron["icosahedron", {{0, 0, 0}}, l]""", + "Icosahedron[positions_List, l_?NumberQ]": """UniformPolyhedron["icosahedron", positions, l]""", } @@ -99,8 +99,8 @@ class Octahedron(Builtin): rules = { "Octahedron[]": """UniformPolyhedron["octahedron"]""", - "Octahedron[l_?NumericQ]": """UniformPolyhedron["octahedron", {{0, 0, 0}}, l]""", - "Octahedron[positions_List, l_?NumericQ]": """UniformPolyhedron["octahedron", positions, l]""", + "Octahedron[l_?NumberQ]": """UniformPolyhedron["octahedron", {{0, 0, 0}}, l]""", + "Octahedron[positions_List, l_?NumberQ]": """UniformPolyhedron["octahedron", positions, l]""", } @@ -117,8 +117,8 @@ class Tetrahedron(Builtin): rules = { "Tetrahedron[]": """UniformPolyhedron["tetrahedron"]""", - "Tetrahedron[l_?NumericQ]": """UniformPolyhedron["tetrahedron", {{0, 0, 0}}, l]""", - "Tetrahedron[positions_List, l_?NumericQ]": """UniformPolyhedron["tetrahedron", positions, l]""", + "Tetrahedron[l_?NumberQ]": """UniformPolyhedron["tetrahedron", {{0, 0, 0}}, l]""", + "Tetrahedron[positions_List, l_?NumberQ]": """UniformPolyhedron["tetrahedron", positions, l]""", } def apply_with_length(self, length, evaluation): diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py index 5f62888c5..d67ca154b 100644 --- a/mathics/builtin/files_io/filesystem.py +++ b/mathics/builtin/files_io/filesystem.py @@ -1058,7 +1058,7 @@ class FileNameJoin(Builtin): } def apply(self, pathlist, evaluation, options): - "FileNameJoin[pathlist_?ListQ, OptionsPattern[FileNameJoin]]" + "FileNameJoin[pathlist_List, OptionsPattern[FileNameJoin]]" py_pathlist = pathlist.to_python() if not all(isinstance(p, str) and p[0] == p[-1] == '"' for p in py_pathlist): @@ -2217,8 +2217,8 @@ class ToFileName(Builtin): rules = { "ToFileName[dir_String, name_String]": "FileNameJoin[{dir, name}]", - "ToFileName[dirs_?ListQ, name_String]": "FileNameJoin[Append[dirs, name]]", - "ToFileName[dirs_?ListQ]": "FileNameJoin[dirs]", + "ToFileName[dirs_List, name_String]": "FileNameJoin[Append[dirs, name]]", + "ToFileName[dirs_List]": "FileNameJoin[dirs]", } diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index 4ef568e3f..73b669f7c 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -2526,7 +2526,7 @@ def default_NumberFormat(man, base, exp, options): return man def apply_list_n(self, expr, n, evaluation, options) -> Expression: - "NumberForm[expr_?ListQ, n_, OptionsPattern[NumberForm]]" + "NumberForm[expr_List, n_, OptionsPattern[NumberForm]]" options = [ Expression("RuleDelayed", Symbol(key), value) for key, value in options.items() @@ -2537,7 +2537,7 @@ def apply_list_n(self, expr, n, evaluation, options) -> Expression: ) def apply_list_nf(self, expr, n, f, evaluation, options) -> Expression: - "NumberForm[expr_?ListQ, {n_, f_}, OptionsPattern[NumberForm]]" + "NumberForm[expr_List, {n_, f_}, OptionsPattern[NumberForm]]" options = [ Expression("RuleDelayed", Symbol(key), value) for key, value in options.items() diff --git a/mathics/builtin/intfns/combinatorial.py b/mathics/builtin/intfns/combinatorial.py index e12e373aa..dc8f69210 100644 --- a/mathics/builtin/intfns/combinatorial.py +++ b/mathics/builtin/intfns/combinatorial.py @@ -381,7 +381,7 @@ class Subsets(Builtin): """ rules = { - "Subsets[list_ , Pattern[n,_?ListQ|All|DirectedInfinity[1]], spec_]": "Take[Subsets[list, n], spec]", + "Subsets[list_ , Pattern[n,_List|All|DirectedInfinity[1]], spec_]": "Take[Subsets[list, n], spec]", } messages = { @@ -421,7 +421,7 @@ def apply_1(self, list, n, evaluation): return Expression("List", *nested_list) def apply_2(self, list, n, evaluation): - "Subsets[list_, Pattern[n,_?ListQ|All|DirectedInfinity[1]]]" + "Subsets[list_, Pattern[n,_List|All|DirectedInfinity[1]]]" expr = Expression("Subsets", list, n) @@ -495,7 +495,7 @@ def apply_2(self, list, n, evaluation): return Expression("List", *nested_list) def apply_3(self, list, n, spec, evaluation): - "Subsets[list_?AtomQ, Pattern[n,_?ListQ|All|DirectedInfinity[1]], spec_]" + "Subsets[list_?AtomQ, Pattern[n,_List|All|DirectedInfinity[1]], spec_]" return evaluation.message( "Subsets", "normal", Expression("Subsets", list, n, spec) diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index 5df4c8e20..9359b28a8 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -311,7 +311,7 @@ def check_options(self, expr, evaluation, options): return None def apply(self, list1, list2, evaluation, options={}): - "ContainsOnly[list1_?ListQ, list2_?ListQ, OptionsPattern[ContainsOnly]]" + "ContainsOnly[list1_List, list2_List, OptionsPattern[ContainsOnly]]" same_test = self.get_option(options, "SameTest", evaluation) @@ -1320,7 +1320,7 @@ def apply(self, mlist, func, evaluation): return outer([inner(l) for l in result]) def apply_multiple(self, mlist, funcs, evaluation): - "SplitBy[mlist_, funcs_?ListQ]" + "SplitBy[mlist_, funcs_List]" expr = Expression("Split", mlist, funcs) if mlist.is_atom(): diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index 995d12877..f180eb0da 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -852,7 +852,7 @@ class Solve(Builtin): "Cases[Solve[eqs, vars], {Rule[x_,y_?RealNumberQ]}]" ), "Solve[eqs_, vars_, Integers]": ( - "Cases[Solve[eqs, vars], {Rule[x_,y_?IntegerQ]}]" + "Cases[Solve[eqs, vars], {Rule[x_,y_Integer]}]" ), } diff --git a/mathics/builtin/numbers/constants.py b/mathics/builtin/numbers/constants.py index 21f4f5e5f..cde612c43 100644 --- a/mathics/builtin/numbers/constants.py +++ b/mathics/builtin/numbers/constants.py @@ -71,7 +71,7 @@ class _Constant_Common(Predefined): options = {"Method": "Automatic"} def apply_N(self, precision, evaluation, options={}): - "N[%(name)s, precision_?NumericQ, OptionsPattern[%(name)s]]" + "N[%(name)s, precision_?NumberQ, OptionsPattern[%(name)s]]" preference = self.get_option(options, "Method", evaluation).get_string_value() if preference == "Automatic": diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index c29c90795..8a5e6d271 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -268,7 +268,7 @@ class FromContinuedFraction(SympyFunction): attributes = ("NumericFunction",) def apply_1(self, expr, evaluation): - "%(name)s[expr_?ListQ]" + "%(name)s[expr_List]" nums = expr.to_python() if all(isinstance(i, int) for i in nums): return from_sympy(sympy.continued_fraction_reduce(nums)) @@ -487,7 +487,7 @@ class NextPrime(Builtin): } def apply(self, n, k, evaluation): - "NextPrime[n_?NumericQ, k_?IntegerQ]" + "NextPrime[n_?RealQ, k_Integer]" py_k = k.to_python(n_evaluation=evaluation) py_n = n.to_python(n_evaluation=evaluation) @@ -597,7 +597,7 @@ class PrimePi(SympyFunction): # TODO: Traditional Form def apply(self, n, evaluation): - "PrimePi[n_?NumericQ]" + "PrimePi[n_?RealQ]" result = sympy.ntheory.primepi(n.to_python(n_evaluation=evaluation)) return from_python(result) @@ -711,8 +711,8 @@ class RandomPrime(Builtin): rules = { "RandomPrime[imax_?NotListQ]": "RandomPrime[{1, imax}, 1]", - "RandomPrime[int_?ListQ]": "RandomPrime[int, 1]", - "RandomPrime[imax_?ListQ, n_?ArrayQ]": ( + "RandomPrime[int_List]": "RandomPrime[int, 1]", + "RandomPrime[imax_List, n_?ArrayQ]": ( "ConstantArray[RandomPrime[imax, 1], n]" ), "RandomPrime[imax_?NotListQ, n_?ArrayQ]": ( @@ -723,7 +723,7 @@ class RandomPrime(Builtin): # TODO: Use random state as in other randomised methods within mathics def apply(self, interval, n, evaluation): - "RandomPrime[interval_?ListQ, n_]" + "RandomPrime[interval_List, n_]" if not isinstance(n, Integer): evaluation.message("RandomPrime", "posdim", n) diff --git a/mathics/builtin/numbers/randomnumbers.py b/mathics/builtin/numbers/randomnumbers.py index d7edd91fe..d2dd03bd3 100644 --- a/mathics/builtin/numbers/randomnumbers.py +++ b/mathics/builtin/numbers/randomnumbers.py @@ -325,7 +325,7 @@ def apply(self, rmin, rmax, evaluation): return Integer(rand.randint(rmin, rmax)) def apply_list(self, rmin, rmax, ns, evaluation): - "RandomInteger[{rmin_, rmax_}, ns_?ListQ]" + "RandomInteger[{rmin_, rmax_}, ns_List]" if not isinstance(rmin, Integer) or not isinstance(rmax, Integer): return evaluation.message( "RandomInteger", "unifr", Expression("List", rmin, rmax) @@ -406,7 +406,7 @@ def apply(self, xmin, xmax, evaluation): return Real(rand.randreal(min_value, max_value)) def apply_list(self, xmin, xmax, ns, evaluation): - "RandomReal[{xmin_, xmax_}, ns_?ListQ]" + "RandomReal[{xmin_, xmax_}, ns_List]" if not ( isinstance(xmin, (Real, Integer)) and isinstance(xmax, (Real, Integer)) @@ -717,7 +717,7 @@ class Random(Builtin): "Random[Real, {zmin_Real, zmax_Real}]": "RandomReal[{zmin, zmax}]", "Random[Complex]": "RandomComplex[]", "Random[Complex, zmax_Complex]": "RandomComplex[zmax]", - "Random[Complex, {zmin_?NumericQ, zmax_?NumericQ}]": "RandomComplex[{zmin, zmax}]", + "Random[Complex, {zmin_?NumberQ, zmax_?NumberQ}]": "RandomComplex[{zmin, zmax}]", } diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index c651551f5..7b2d44139 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -1506,14 +1506,14 @@ class Round(Builtin): attributes = ("Listable", "NumericFunction") rules = { - "Round[expr_?NumericQ]": "Round[Re[expr], 1] + I * Round[Im[expr], 1]", + "Round[expr_?NumberQ]": "Round[Re[expr], 1] + I * Round[Im[expr], 1]", "Round[expr_Complex, k_?RealNumberQ]": ( "Round[Re[expr], k] + I * Round[Im[expr], k]" ), } def apply(self, expr, k, evaluation): - "Round[expr_?NumericQ, k_?NumericQ]" + "Round[expr_?NumberQ, k_?NumberQ]" n = Expression("Divide", expr, k).round_to_float( evaluation, permit_complex=True @@ -1652,7 +1652,7 @@ def apply(self, n, evaluation): return self.apply_with_base(n, from_python(10), evaluation) def apply_with_base(self, n, b, evaluation, nr_elements=None, pos=None): - "%(name)s[n_?NumericQ, b_Integer]" + "%(name)s[n_?NumberQ, b_Integer]" expr = Expression("RealDigits", n) rational_no = ( @@ -1762,7 +1762,7 @@ def apply_with_base(self, n, b, evaluation, nr_elements=None, pos=None): return Expression(SymbolList, list_str, exp) def apply_with_base_and_length(self, n, b, length, evaluation, pos=None): - "%(name)s[n_?NumericQ, b_Integer, length_]" + "%(name)s[n_?NumberQ, b_Integer, length_]" leaves = [] if pos is not None: leaves.append(from_python(pos)) @@ -1775,7 +1775,7 @@ def apply_with_base_and_length(self, n, b, length, evaluation, pos=None): ) def apply_with_base_length_and_precision(self, n, b, length, p, evaluation): - "%(name)s[n_?NumericQ, b_Integer, length_, p_]" + "%(name)s[n_?NumberQ, b_Integer, length_, p_]" if not isinstance(p, Integer): return evaluation.message( "RealDigits", "intm", Expression("RealDigits", n, b, length, p) diff --git a/mathics/builtin/optimization.py b/mathics/builtin/optimization.py index 51c15c36b..f0f9729bf 100644 --- a/mathics/builtin/optimization.py +++ b/mathics/builtin/optimization.py @@ -69,7 +69,7 @@ def apply_onevariable(self, f, x, evaluation): ) def apply_multiplevariable(self, f, vars, evaluation): - "Minimize[f_?NotListQ, vars_?ListQ]" + "Minimize[f_?NotListQ, vars_List]" head_name = vars.get_head_name() vars_or = vars @@ -149,7 +149,7 @@ def apply_multiplevariable(self, f, vars, evaluation): ) def apply_constraints(self, f, vars, evaluation): - "Minimize[f_?ListQ, vars_?ListQ]" + "Minimize[f_List, vars_List]" head_name = vars.get_head_name() vars_or = vars vars = vars.leaves @@ -380,7 +380,7 @@ def apply(self, f, vars, evaluation): return from_python(solutions) def apply_constraints(self, f, vars, evaluation): - "Maximize[f_?ListQ, vars_]" + "Maximize[f_List, vars_]" constraints = [function for function in f.leaves] constraints[0] = from_sympy(constraints[0].to_sympy() * -1) diff --git a/mathics/builtin/physchemdata.py b/mathics/builtin/physchemdata.py index a0793d071..4e0048ef8 100644 --- a/mathics/builtin/physchemdata.py +++ b/mathics/builtin/physchemdata.py @@ -144,7 +144,7 @@ def apply_name(self, name, prop, evaluation): return self.apply_int(from_python(indx), prop, evaluation) def apply_int(self, n, prop, evaluation): - "ElementData[n_?IntegerQ, prop_]" + "ElementData[n_Integer, prop_]" py_n = n.to_python() py_prop = prop.to_python() From fe6b5dc3756f52a0bca003d59138a4ceb5b73848 Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 25 Aug 2021 10:56:26 -0300 Subject: [PATCH 084/193] clean StringQ --- mathics/builtin/drawing/image.py | 2 +- mathics/builtin/quantities.py | 4 ++-- mathics/builtin/system.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 7039afa79..531ccce91 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -136,7 +136,7 @@ class ImageImport(_ImageBuiltin): """ def apply(self, path, evaluation): - """ImageImport[path_?StringQ]""" + """ImageImport[path_String]""" pillow = PIL.Image.open(path.get_string_value()) pixels = numpy.asarray(pillow) is_rgb = len(pixels.shape) >= 3 and pixels.shape[2] >= 3 diff --git a/mathics/builtin/quantities.py b/mathics/builtin/quantities.py index 343eba996..7dc421480 100644 --- a/mathics/builtin/quantities.py +++ b/mathics/builtin/quantities.py @@ -189,7 +189,7 @@ def validate(self, unit, evaluation): return True def apply_makeboxes(self, mag, unit, f, evaluation): - "MakeBoxes[Quantity[mag_, unit_?StringQ], f:StandardForm|TraditionalForm|OutputForm|InputForm]" + "MakeBoxes[Quantity[mag_, unit_String], f:StandardForm|TraditionalForm|OutputForm|InputForm]" q_unit = unit.get_string_value().lower() if self.validate(unit, evaluation): @@ -200,7 +200,7 @@ def apply_makeboxes(self, mag, unit, f, evaluation): ) def apply_n(self, mag, unit, evaluation): - "Quantity[mag_, unit_?StringQ]" + "Quantity[mag_, unit_String]" if self.validate(unit, evaluation): if mag.has_form("List", None): diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index 7153ddee8..f8e1ba914 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -94,7 +94,7 @@ class Environment(Builtin): """ def apply(self, var, evaluation): - "Environment[var_?StringQ]" + "Environment[var_String]" env_var = var.get_string_value() if env_var not in os.environ: return SymbolFailed @@ -358,7 +358,7 @@ class Run(Builtin): """ def apply(self, command, evaluation): - "Run[command_?StringQ]" + "Run[command_String]" command_str = command.to_python() return Integer(subprocess.call(command_str, shell=True)) From 5e8d74d085bafc11d971af60198e7e8908f154bf Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 25 Aug 2021 15:48:01 -0300 Subject: [PATCH 085/193] revert some cases... --- mathics/builtin/numbers/constants.py | 2 +- mathics/builtin/numbers/numbertheory.py | 4 ++-- mathics/builtin/numeric.py | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/numbers/constants.py b/mathics/builtin/numbers/constants.py index cde612c43..21f4f5e5f 100644 --- a/mathics/builtin/numbers/constants.py +++ b/mathics/builtin/numbers/constants.py @@ -71,7 +71,7 @@ class _Constant_Common(Predefined): options = {"Method": "Automatic"} def apply_N(self, precision, evaluation, options={}): - "N[%(name)s, precision_?NumberQ, OptionsPattern[%(name)s]]" + "N[%(name)s, precision_?NumericQ, OptionsPattern[%(name)s]]" preference = self.get_option(options, "Method", evaluation).get_string_value() if preference == "Automatic": diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index 8a5e6d271..8bf61260b 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -487,7 +487,7 @@ class NextPrime(Builtin): } def apply(self, n, k, evaluation): - "NextPrime[n_?RealQ, k_Integer]" + "NextPrime[n_?NumericQ, k_Integer]" py_k = k.to_python(n_evaluation=evaluation) py_n = n.to_python(n_evaluation=evaluation) @@ -597,7 +597,7 @@ class PrimePi(SympyFunction): # TODO: Traditional Form def apply(self, n, evaluation): - "PrimePi[n_?RealQ]" + "PrimePi[n_?NumericQ]" result = sympy.ntheory.primepi(n.to_python(n_evaluation=evaluation)) return from_python(result) diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 7b2d44139..c651551f5 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -1506,14 +1506,14 @@ class Round(Builtin): attributes = ("Listable", "NumericFunction") rules = { - "Round[expr_?NumberQ]": "Round[Re[expr], 1] + I * Round[Im[expr], 1]", + "Round[expr_?NumericQ]": "Round[Re[expr], 1] + I * Round[Im[expr], 1]", "Round[expr_Complex, k_?RealNumberQ]": ( "Round[Re[expr], k] + I * Round[Im[expr], k]" ), } def apply(self, expr, k, evaluation): - "Round[expr_?NumberQ, k_?NumberQ]" + "Round[expr_?NumericQ, k_?NumericQ]" n = Expression("Divide", expr, k).round_to_float( evaluation, permit_complex=True @@ -1652,7 +1652,7 @@ def apply(self, n, evaluation): return self.apply_with_base(n, from_python(10), evaluation) def apply_with_base(self, n, b, evaluation, nr_elements=None, pos=None): - "%(name)s[n_?NumberQ, b_Integer]" + "%(name)s[n_?NumericQ, b_Integer]" expr = Expression("RealDigits", n) rational_no = ( @@ -1762,7 +1762,7 @@ def apply_with_base(self, n, b, evaluation, nr_elements=None, pos=None): return Expression(SymbolList, list_str, exp) def apply_with_base_and_length(self, n, b, length, evaluation, pos=None): - "%(name)s[n_?NumberQ, b_Integer, length_]" + "%(name)s[n_?NumericQ, b_Integer, length_]" leaves = [] if pos is not None: leaves.append(from_python(pos)) @@ -1775,7 +1775,7 @@ def apply_with_base_and_length(self, n, b, length, evaluation, pos=None): ) def apply_with_base_length_and_precision(self, n, b, length, p, evaluation): - "%(name)s[n_?NumberQ, b_Integer, length_, p_]" + "%(name)s[n_?NumericQ, b_Integer, length_, p_]" if not isinstance(p, Integer): return evaluation.message( "RealDigits", "intm", Expression("RealDigits", n, b, length, p) From 7f47483823f76d6659c1d53a52b8d94ba6587c8e Mon Sep 17 00:00:00 2001 From: autoblack Date: Wed, 25 Aug 2021 19:00:56 +0000 Subject: [PATCH 086/193] fixup: Format Python code with Black --- mathics/builtin/numbers/numbertheory.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index 8bf61260b..982b3b6a2 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -712,9 +712,7 @@ class RandomPrime(Builtin): rules = { "RandomPrime[imax_?NotListQ]": "RandomPrime[{1, imax}, 1]", "RandomPrime[int_List]": "RandomPrime[int, 1]", - "RandomPrime[imax_List, n_?ArrayQ]": ( - "ConstantArray[RandomPrime[imax, 1], n]" - ), + "RandomPrime[imax_List, n_?ArrayQ]": ("ConstantArray[RandomPrime[imax, 1], n]"), "RandomPrime[imax_?NotListQ, n_?ArrayQ]": ( "ConstantArray[RandomPrime[{1, imax}, 1], n]" ), From e3893419ade5e7031eaf1cbf4172816337c5d8dc Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 25 Aug 2021 16:50:13 -0300 Subject: [PATCH 087/193] improving quick_pattern_tests --- mathics/builtin/patterns.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index c7b535b7d..1868257f7 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -51,6 +51,7 @@ Real, SymbolFalse, SymbolList, + SymbolN, SymbolTrue, ) from mathics.core.rules import Rule @@ -476,9 +477,16 @@ def init(self, expr): def quick_pattern_test(self, candidate, test, evaluation): if test == "System`NumberQ": return isinstance(candidate, Number) + if test == "System`NumericQ": + if isinstance(candidate, Number): + return True + # Otherwise, follow the standard evaluation elif test == "System`RealNumberQ": if isinstance(candidate, (Integer, Rational, Real)): return True + candidate = Expression(SymbolN, candidate).evaluate(evaluation) + if isinstance(candidate, Real): + return True return False # pass elif test == "System`Positive": @@ -486,11 +494,21 @@ def quick_pattern_test(self, candidate, test, evaluation): return candidate.value > 0 return False # pass + elif test == "System`NonPositive": + if isinstance(candidate, (Integer, Rational, Real)): + return candidate.value <= 0 + return False + # pass elif test == "System`Negative": if isinstance(candidate, (Integer, Rational, Real)): return candidate.value < 0 return False # pass + elif test == "System`NonNegative": + if isinstance(candidate, (Integer, Rational, Real)): + return candidate.value >= 0 + return False + # pass elif test == "System`NegativePowerQ": return ( candidate.has_form("Power", 2) From dc801fe71fe2a8debf852c5ecb69d839d8db51f0 Mon Sep 17 00:00:00 2001 From: autoblack Date: Wed, 25 Aug 2021 19:50:51 +0000 Subject: [PATCH 088/193] fixup: Format Python code with Black --- mathics/builtin/patterns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 1868257f7..958ae6edc 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -498,7 +498,7 @@ def quick_pattern_test(self, candidate, test, evaluation): if isinstance(candidate, (Integer, Rational, Real)): return candidate.value <= 0 return False - # pass + # pass elif test == "System`Negative": if isinstance(candidate, (Integer, Rational, Real)): return candidate.value < 0 @@ -508,7 +508,7 @@ def quick_pattern_test(self, candidate, test, evaluation): if isinstance(candidate, (Integer, Rational, Real)): return candidate.value >= 0 return False - # pass + # pass elif test == "System`NegativePowerQ": return ( candidate.has_form("Power", 2) From 1e96d363c12fb5694c2aaf463af22bd78060d8b8 Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 25 Aug 2021 20:03:30 -0300 Subject: [PATCH 089/193] fix cylinder --- mathics/builtin/drawing/graphics3d.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 94a15f55f..8c2982bfe 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -301,7 +301,8 @@ class Cylinder(Builtin): = -Graphics3D- """ - messages = {"oddn": "The number of points must be even."} + messages = {"oddn": "The number of points must be even.", + "nrr": "The radius must be a real number" } rules = { "Cylinder[]": "Cylinder[{{0, 0, 0}, {1, 1, 1}}, 1]", @@ -309,10 +310,14 @@ class Cylinder(Builtin): } def apply_check(self, positions, radius, evaluation): - "Cylinder[positions_List, radius_?NumberQ]" + "Cylinder[positions_List, radius_]" if len(positions.get_leaves()) % 2 == 1: # The number of points is odd, so abort. evaluation.error("Cylinder", "oddn", positions) + if not isinstance(radius, (Integer, Rational, Real)): + nradius = Expression(SymbolN, radius).evaluate(evaluation) + if not isinstance(nradius, (Integer, Rational, Real)): + evaluation.error("Cylinder", "nrr", radius) - return Expression("Cylinder", positions, radius) + return From fb5683047611065d69b1888ba776a40bec98d43f Mon Sep 17 00:00:00 2001 From: autoblack Date: Wed, 25 Aug 2021 23:04:10 +0000 Subject: [PATCH 090/193] fixup: Format Python code with Black --- mathics/builtin/drawing/graphics3d.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 8c2982bfe..e72411864 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -301,8 +301,10 @@ class Cylinder(Builtin): = -Graphics3D- """ - messages = {"oddn": "The number of points must be even.", - "nrr": "The radius must be a real number" } + messages = { + "oddn": "The number of points must be even.", + "nrr": "The radius must be a real number", + } rules = { "Cylinder[]": "Cylinder[{{0, 0, 0}, {1, 1, 1}}, 1]", From fbfa15fc93179eaa9af2174722f5893098fdced5 Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 25 Aug 2021 20:11:07 -0300 Subject: [PATCH 091/193] adding types --- mathics/builtin/drawing/graphics3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 8c2982bfe..68cb658e5 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -7,7 +7,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.core.expression import Expression, Real +from mathics.core.expression import Expression, Real, Integer, Rational from mathics.builtin.base import BoxConstructError, Builtin, InstanceableBuiltin from mathics.builtin.colors.color_directives import RGBColor From 1f0acd08e2f5986ded984e4c7a403bb9d41e4f13 Mon Sep 17 00:00:00 2001 From: mmatera Date: Thu, 26 Aug 2021 13:34:30 -0300 Subject: [PATCH 092/193] datetime --- mathics/builtin/datentime.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index 3e0a433d8..55b4ee8cf 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -899,10 +899,6 @@ class DateString(_DateFormat): rules = { "DateString[]": "DateString[DateList[], $DateStringFormat]", - "DateString[epochtime_?(VectorQ[#1, NumberQ]&)]": ( - "DateString[epochtime, $DateStringFormat]" - ), - "DateString[epochtime_?NumberQ]": ("DateString[epochtime, $DateStringFormat]"), "DateString[format_?(VectorQ[#1, StringQ]&)]": ( "DateString[DateList[], format]" ), From 478735b5b1a5a05809a48aeb23b25e1b60e28a5a Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Wed, 1 Sep 2021 15:50:26 +0000 Subject: [PATCH 093/193] Improve Rational performance --- mathics/builtin/arithmetic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 07571d72c..e972c3535 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -876,10 +876,10 @@ class Rational_(Builtin): def apply(self, n, m, evaluation): "%(name)s[n_Integer, m_Integer]" - if m.to_sympy() == 1: - return Integer(n.to_sympy()) + if m.value == 1: + return n else: - return Rational(n.to_sympy(), m.to_sympy()) + return Rational(n.value, m.value) class Complex_(Builtin): From aa3331d573de474bcd0ceb47f9831f24e7ac3dc4 Mon Sep 17 00:00:00 2001 From: mmatera Date: Tue, 31 Aug 2021 01:37:43 -0300 Subject: [PATCH 094/193] trying to improve performance for NumericQ/NumberQ tests adding benchmark Negative avoids to call `evaluate(evaluation)` by calling the `Less.sapply` method directly. Positive 'Positive[Sqrt[2]]' 100 loops, avg: 19.7 ms, best: 18.6 ms, median: 19.5 ms per loop 'Positive[Sqrt[-2]]' 100 loops, avg: 31.5 ms, best: 28.9 ms, median: 29.8 ms per loop 'Positive[Sqrt[2.]]' 5000 loops, avg: 949 us, best: 825 us, median: 890 us per loop 'Positive[Sqrt[-2.]]' 5000 loops, avg: 956 us, best: 820 us, median: 887 us per loop 'Positive[q]' 10000 loops, avg: 164 us, best: 141 us, median: 147 us per loop 'Positive["q"]' 10000 loops, avg: 159 us, best: 137 us, median: 143 us per loop Negative 'Negative[Sqrt[2]]' 100 loops, avg: 19.7 ms, best: 18.9 ms, median: 19.5 ms per loop 'Negative[Sqrt[-2]]' 100 loops, avg: 31.8 ms, best: 29.2 ms, median: 30.3 ms per loop 'Negative[Sqrt[2.]]' 5000 loops, avg: 954 us, best: 836 us, median: 901 us per loop 'Negative[Sqrt[-2.]]' 5000 loops, avg: 952 us, best: 832 us, median: 898 us per loop 'Negative[q]' 10000 loops, avg: 165 us, best: 145 us, median: 150 us per loop 'Negative["q"]' 10000 loops, avg: 159 us, best: 140 us, median: 144 us per loop NumericQ 'NumericQ[Sqrt[2]]' 5000 loops, avg: 987 us, best: 863 us, median: 934 us per loop 'NumericQ[Sqrt[-2]]' 1000 loops, avg: 1.68 ms, best: 1.47 ms, median: 1.65 ms per loop 'NumericQ[Sqrt[2.]]' 5000 loops, avg: 917 us, best: 802 us, median: 862 us per loop 'NumericQ[Sqrt[-2.]]' 5000 loops, avg: 948 us, best: 810 us, median: 878 us per loop 'NumericQ[q]' 10000 loops, avg: 81.5 us, best: 71.2 us, median: 73.2 us per loop 'NumericQ["q"]' 10000 loops, avg: 77.2 us, best: 67.8 us, median: 69.6 us per loop fixup: Format Python code with Black Now `expression.is_numeric` accepts an `evaluation` object as an argument and `NumericQ` calls directly that method. Positive 'Positive[Sqrt[2]]' 100 loops, avg: 19.4 ms, best: 18.4 ms, median: 19.2 ms per loop 'Positive[Sqrt[-2]]' 100 loops, avg: 31.5 ms, best: 28.7 ms, median: 29.8 ms per loop 'Positive[Sqrt[2.]]' 5000 loops, avg: 948 us, best: 822 us, median: 890 us per loop 'Positive[Sqrt[-2.]]' 5000 loops, avg: 943 us, best: 813 us, median: 882 us per loop 'Positive[q]' 10000 loops, avg: 160 us, best: 138 us, median: 144 us per loop 'Positive["q"]' 10000 loops, avg: 157 us, best: 133 us, median: 139 us per loop Negative 'Negative[Sqrt[2]]' 100 loops, avg: 19.4 ms, best: 18.3 ms, median: 19.1 ms per loop 'Negative[Sqrt[-2]]' 100 loops, avg: 31.5 ms, best: 28.6 ms, median: 29.6 ms per loop 'Negative[Sqrt[2.]]' 5000 loops, avg: 951 us, best: 813 us, median: 890 us per loop 'Negative[Sqrt[-2.]]' 5000 loops, avg: 928 us, best: 807 us, median: 876 us per loop 'Negative[q]' 10000 loops, avg: 87.2 us, best: 75.9 us, median: 78.4 us per loop 'Negative["q"]' 10000 loops, avg: 84 us, best: 73.4 us, median: 75.7 us per loop NumericQ 'NumericQ[Sqrt[2]]' 1000 loops, avg: 1.01 ms, best: 866 us, median: 942 us per loop 'NumericQ[Sqrt[-2]]' 1000 loops, avg: 1.66 ms, best: 1.45 ms, median: 1.62 ms per loop 'NumericQ[Sqrt[2.]]' 5000 loops, avg: 904 us, best: 787 us, median: 851 us per loop 'NumericQ[Sqrt[-2.]]' 5000 loops, avg: 912 us, best: 794 us, median: 859 us per loop 'NumericQ[q]' 10000 loops, avg: 78 us, best: 69.7 us, median: 71.4 us per loop 'NumericQ["q"]' 10000 loops, avg: 75.5 us, best: 66.2 us, median: 68.3 us per loop listing changes --- CHANGES.rst | 12 ++++++ mathics/benchmark.py | 35 +++++++++++++++++ mathics/builtin/arithmetic.py | 3 +- mathics/builtin/base.py | 8 ++-- mathics/builtin/comparison.py | 2 +- mathics/builtin/datentime.py | 4 +- mathics/builtin/inference.py | 4 +- mathics/builtin/intfns/divlike.py | 2 +- mathics/builtin/lists.py | 2 +- mathics/builtin/moments/basic.py | 2 +- mathics/builtin/numbers/algebra.py | 2 +- mathics/builtin/numbers/calculus.py | 11 +++--- mathics/builtin/numbers/numbertheory.py | 9 +++-- mathics/builtin/numbers/randomnumbers.py | 6 +-- mathics/builtin/numeric.py | 27 ++++++------- mathics/builtin/patterns.py | 2 +- mathics/builtin/procedural.py | 2 +- mathics/builtin/recurrence.py | 2 +- mathics/builtin/sparse.py | 2 +- mathics/core/definitions.py | 6 ++- mathics/core/expression.py | 48 ++++++++++++++---------- 21 files changed, 123 insertions(+), 68 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 97f1ea3f4..c23414213 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,18 @@ CHANGES ======= +Internals +========= + +* now `Expression.is_numeric()` accepts an `Evaluation` object as a parameter, + to use the definitions. +* To numerify expressions, the function `_numeric_evaluation_with_prec` was introduced in the module `mathics.builtin.numeric` to avoid the idiom + `Expression("N", expr, prec).evaluate(evaluation)`. The idea is to avoid when it is possible to call the Pattern matching routines to obtain the numeric value of an expression. +* `Positive`, `Negative`, `NonPositive` and `NonNegative` do not check their arguments using the Pattern matching mechanism, but inside the Python code. +* special cases for `quick_pattern_test` were reduced as much as possible. +* A bug comming from a failure in the order in which `mathics.core.definitions` stores the rules was fixed. + + 4.0.1 ----- diff --git a/mathics/benchmark.py b/mathics/benchmark.py index 02c635b72..c114409bf 100644 --- a/mathics/benchmark.py +++ b/mathics/benchmark.py @@ -26,6 +26,41 @@ # Mathics expressions to benchmark BENCHMARKS = { + "NumericQ": [ + "NumericQ[Sqrt[2]]", + "NumericQ[Sqrt[-2]]", + "NumericQ[Sqrt[2.]]", + "NumericQ[Sqrt[-2.]]", + "NumericQ[q]", + 'NumericQ["q"]', + ], + # This function checks for numericQ in ints argument before calling + "Positive": [ + "Positive[Sqrt[2]]", + "Positive[Sqrt[-2]]", + "Positive[Sqrt[2.]]", + "Positive[Sqrt[-2.]]", + "Positive[q]", + 'Positive["q"]', + ], + # This function uses the WL rules definition, like in master + "NonNegative": [ + "NonNegative[Sqrt[2]]", + "NonNegative[Sqrt[-2]]", + "NonNegative[Sqrt[2.]]", + "NonNegative[Sqrt[-2.]]", + "NonNegative[q]", + 'NonNegative["q"]', + ], + # This function does the check inside the method. + "Negative": [ + "Negative[Sqrt[2]]", + "Negative[Sqrt[-2]]", + "Negative[Sqrt[2.]]", + "Negative[Sqrt[-2.]]", + "Negative[q]", + 'Negative["q"]', + ], "Arithmetic": ["1 + 2", "5 * 3"], "Plot": [ "Plot[0, {x, -3, 3}]", diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index e972c3535..19dd9c0b4 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -47,6 +47,7 @@ from mathics.core.convert import from_sympy, SympyExpression, sympy_symbol_prefix from mathics.builtin.scoping import dynamic_scoping from mathics.builtin.inference import get_assumptions_list, evaluate_predicate +from mathics.builtin.numeric import _numeric_evaluation_with_prec @lru_cache(maxsize=1024) @@ -133,7 +134,7 @@ def apply(self, z, evaluation): prec = min_prec(*args) d = dps(prec) args = [ - Expression(SymbolN, arg, Integer(d)).evaluate(evaluation) + _numeric_evaluation_with_prec(arg, evaluation, Integer(d)) for arg in args ] with mpmath.workprec(prec): diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 77a3087e6..7d6bcce56 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -527,11 +527,13 @@ def __init__(self, *args, **kwargs): class Test(Builtin): def apply(self, expr, evaluation) -> Symbol: "%(name)s[expr_]" - - if self.test(expr): + tst = self.test(expr) + if tst: return SymbolTrue - else: + elif tst is False: return SymbolFalse + else: + return class SympyFunction(SympyObject): diff --git a/mathics/builtin/comparison.py b/mathics/builtin/comparison.py index 95fe1aa0a..219f3af0d 100644 --- a/mathics/builtin/comparison.py +++ b/mathics/builtin/comparison.py @@ -198,7 +198,7 @@ class _InequalityOperator(BinaryOperator): def numerify_args(items, evaluation): items_sequence = items.get_sequence() all_numeric = all( - item.is_numeric() and item.get_precision() is None + item.is_numeric(evaluation) and item.get_precision() is None for item in items_sequence ) diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index 55b4ee8cf..459493c4a 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -630,7 +630,7 @@ def apply_any(self, args, evaluation, options): timezone = Real(-time.timezone / 3600.0) else: timezone = options["System`TimeZone"].evaluate(evaluation) - if not timezone.is_numeric(): + if not timezone.is_numeric(evaluation): evaluation.message("DateObject", "notz", timezone) # TODO: if tz != timezone, shift the datetime list. @@ -1123,7 +1123,7 @@ def apply_2(self, expr, t, evaluation): def apply_3(self, expr, t, failexpr, evaluation): "TimeConstrained[expr_, t_, failexpr_]" t = t.evaluate(evaluation) - if not t.is_numeric(): + if not t.is_numeric(evaluation): evaluation.message("TimeConstrained", "timc", t) return try: diff --git a/mathics/builtin/inference.py b/mathics/builtin/inference.py index 018fbd0c9..a2e45b1dc 100644 --- a/mathics/builtin/inference.py +++ b/mathics/builtin/inference.py @@ -156,7 +156,7 @@ def logical_expand_assumptions(assumptions_list, evaluation): evaluation.message("Assumption", "faas") changed = True continue - if assumption.is_numeric(): + if assumption.is_numeric(evaluation): evaluation.message("Assumption", "baas") changed = True continue @@ -306,7 +306,7 @@ def get_assumption_rules_dispatch(evaluation): if pat.has_form("Equal", 2): if value: lhs, rhs = pat._leaves - if lhs.is_numeric(): + if lhs.is_numeric(evaluation): assumption_rules.append(Rule(rhs, lhs)) else: assumption_rules.append(Rule(lhs, rhs)) diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index e6f0568d7..9a6c6d4bc 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -367,7 +367,7 @@ class QuotientRemainder(Builtin): def apply(self, m, n, evaluation): "QuotientRemainder[m_, n_]" - if m.is_numeric() and n.is_numeric(): + if m.is_numeric(evaluation) and n.is_numeric(): py_m = m.to_python() py_n = n.to_python() if py_n == 0: diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index 9359b28a8..a468ce2a8 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -2704,7 +2704,7 @@ def convert_vectors(p): raise _IllegalDataPoint yield v - if dist_p[0].is_numeric(): + if dist_p[0].is_numeric(evaluation): numeric_p = [[x] for x in convert_scalars(dist_p)] else: numeric_p = list(convert_vectors(dist_p)) diff --git a/mathics/builtin/moments/basic.py b/mathics/builtin/moments/basic.py index 8528bde5a..2d10af5f9 100644 --- a/mathics/builtin/moments/basic.py +++ b/mathics/builtin/moments/basic.py @@ -49,7 +49,7 @@ def apply(self, l, evaluation): return self.rect(l) except _NotRectangularException: evaluation.message("Median", "rectn", Expression("Median", l)) - elif all(leaf.is_numeric() for leaf in l.leaves): + elif all(leaf.is_numeric(evaluation) for leaf in l.leaves): v = l.get_mutable_leaves() # copy needed for introselect n = len(v) if n % 2 == 0: # even number of elements? diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index 7fb84c32c..bac364fd6 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -1476,7 +1476,7 @@ def coeff_power_internal(self, expr, var_exprs, filt, evaluation, form="expr"): def key_powers(lst): key = Expression("Plus", *lst) key = key.evaluate(evaluation) - if key.is_numeric(): + if key.is_numeric(evaluation): return key.to_python() return 0 diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index f180eb0da..d987a1c62 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -18,7 +18,6 @@ SymbolTrue, SymbolFalse, SymbolList, - SymbolN, SymbolRule, SymbolUndefined, from_python, @@ -27,6 +26,7 @@ from mathics.core.rules import Pattern from mathics.core.numbers import dps from mathics.builtin.scoping import dynamic_scoping +from mathics.builtin.numeric import _numeric_evaluation_with_prec from mathics import Symbol import sympy @@ -1280,9 +1280,8 @@ def sub(evaluation): # TODO: use Precision goal... if x1 == x0: break - x0 = Expression(SymbolN, x1).evaluate( - evaluation - ) # N required due to bug in sympy arithmetic + x0 = _numeric_evaluation_with_prec(x1, evaluation) + # N required due to bug in sympy arithmetic count += 1 else: evaluation.message("FindRoot", "maxiter") @@ -1377,7 +1376,7 @@ class FindRoot(Builtin): def apply(self, f, x, x0, evaluation, options): "FindRoot[f_, {x_, x0_}, OptionsPattern[]]" # First, determine x0 and x - x0 = Expression(SymbolN, x0).evaluate(evaluation) + x0 = _numeric_evaluation_with_prec(x0, evaluation) if not isinstance(x0, Number): evaluation.message("FindRoot", "snum", x0) return @@ -1545,7 +1544,7 @@ def apply_makeboxes(self, x, x0, data, nmin, nmax, den, form, evaluation): expansion = [] for i, leaf in enumerate(data.leaves): - if leaf.is_numeric() and leaf.is_zero: + if leaf.is_numeric(evaluation) and leaf.is_zero: continue if powers[i].is_zero: expansion.append(leaf) diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index 982b3b6a2..a30235e2d 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -16,11 +16,12 @@ Rational, Symbol, from_python, - SymbolN, ) from mathics.core.convert import from_sympy, SympyPrime import mpmath +from mathics.builtin.numeric import _numeric_evaluation_with_prec + class ContinuedFraction(SympyFunction): """ @@ -412,13 +413,13 @@ def apply(self, n, b, evaluation): return expr if n_sympy.is_constant(): - temp_n = Expression(SymbolN, n).evaluate(evaluation) + temp_n = _numeric_evaluation_with_prec(n, evaluation) py_n = temp_n.to_python() else: return expr if b_sympy.is_constant(): - temp_b = Expression(SymbolN, b).evaluate(evaluation) + temp_b = _numeric_evaluation_with_prec(b, evaluation) py_b = temp_b.to_python() else: return expr @@ -443,7 +444,7 @@ def apply_2(self, n, evaluation): return expr # Handle Input with special cases such as PI and E if n_sympy.is_constant(): - temp_n = Expression(SymbolN, n).evaluate(evaluation) + temp_n = _numeric_evaluation_with_prec(n, evaluation) py_n = temp_n.to_python() else: return expr diff --git a/mathics/builtin/numbers/randomnumbers.py b/mathics/builtin/numbers/randomnumbers.py index d2dd03bd3..e45e1dc28 100644 --- a/mathics/builtin/numbers/randomnumbers.py +++ b/mathics/builtin/numbers/randomnumbers.py @@ -251,7 +251,7 @@ class _RandomBase(Builtin): def _size_to_python(self, domain, size, evaluation): is_proper_spec = size.get_head_name() == "System`List" and all( - n.is_numeric() for n in size.leaves + n.is_numeric(evaluation) for n in size.leaves ) py_size = size.to_python() if is_proper_spec else None @@ -589,7 +589,7 @@ def _weights_to_python(self, weights, evaluation): # we need to normalize weights as numpy.rand.randchoice expects this and as we can limit # accuracy problems with very large or very small weights by normalizing with sympy is_proper_spec = weights.get_head_name() == "System`List" and all( - w.is_numeric() for w in weights.leaves + w.is_numeric(evaluation) for w in weights.leaves ) if ( @@ -599,7 +599,7 @@ def _weights_to_python(self, weights, evaluation): "Divide", weights, Expression("Total", weights) ).evaluate(evaluation) if norm_weights is None or not all( - w.is_numeric() for w in norm_weights.leaves + w.is_numeric(evaluation) for w in norm_weights.leaves ): return evaluation.message(self.get_name(), "wghtv", weights), None weights = norm_weights diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index c651551f5..980a5d480 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -42,6 +42,7 @@ SymbolFalse, SymbolTrue, SymbolList, + SymbolMachinePrecision, SymbolN, from_python, ) @@ -61,6 +62,10 @@ def log_n_b(py_n, py_b) -> int: return int(mpmath.ceil(mpmath.log(py_n, py_b))) if py_n != 0 and py_n != 1 else 1 +def _numeric_evaluation_with_prec(expression, evaluation, prec=SymbolMachinePrecision): + return Expression("N", expression, prec).evaluate(evaluation) + + def _scipy_interface(integrator, options_map, mandatory=None, adapt_func=None): """ This function provides a proxy for scipy.integrate @@ -1126,7 +1131,7 @@ def apply_with_func_domain(self, func, domain, evaluation, options): (np.arctanh, lambda u: 1.0 / (1.0 - u ** 2)) ) else: - if not b.is_numeric(): + if not b.is_numeric(evaluation): evaluation.message("nlim", coords[i], b) return z = a.leaves[0].value @@ -1136,7 +1141,7 @@ def apply_with_func_domain(self, func, domain, evaluation, options): (lambda u: b - z + z / u, lambda u: -z * u ** (-2.0)) ) elif b.get_head_name() == "System`DirectedInfinity": - if not a.is_numeric(): + if not a.is_numeric(evaluation): evaluation.message("nlim", coords[i], a) return a = a.value @@ -1145,14 +1150,14 @@ def apply_with_func_domain(self, func, domain, evaluation, options): coordtransform.append( (lambda u: a - z + z / u, lambda u: z * u ** (-2.0)) ) - elif a.is_numeric() and b.is_numeric(): + elif a.is_numeric(evaluation) and b.is_numeric(evaluation): a = Expression(SymbolN, a).evaluate(evaluation).value b = Expression(SymbolN, b).evaluate(evaluation).value subdomain2.append([a, b]) coordtransform.append(None) else: for x in (a, b): - if not x.is_numeric(): + if not x.is_numeric(evaluation): evaluation.message("nlim", coords[i], x) return @@ -1228,17 +1233,7 @@ class NumericQ(Builtin): def apply(self, expr, evaluation): "NumericQ[expr_]" - - def test(expr): - if isinstance(expr, Expression): - attr = evaluation.definitions.get_attributes(expr.head.get_name()) - return "System`NumericFunction" in attr and all( - test(leaf) for leaf in expr.leaves - ) - else: - return expr.is_numeric() - - return SymbolTrue if test(expr) else SymbolFalse + return SymbolTrue if expr.is_numeric(evaluation) else SymbolFalse class Precision(Builtin): @@ -1648,7 +1643,7 @@ def apply(self, n, evaluation): if isinstance(n, Symbol) and n.name.startswith("System`"): return evaluation.message("RealDigits", "ndig", n) - if n.is_numeric(): + if n.is_numeric(evaluation): return self.apply_with_base(n, from_python(10), evaluation) def apply_with_base(self, n, b, evaluation, nr_elements=None, pos=None): diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 958ae6edc..eff83952a 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -358,7 +358,7 @@ def apply_list(self, expr, rules, evaluation, options): return rules maxit = self.get_option(options, "MaxIterations", evaluation) - if maxit.is_numeric(): + if maxit.is_numeric(evaluation): maxit = maxit.get_int_value() else: maxit = -1 diff --git a/mathics/builtin/procedural.py b/mathics/builtin/procedural.py index e0bf6c2f7..e7c2937ca 100644 --- a/mathics/builtin/procedural.py +++ b/mathics/builtin/procedural.py @@ -433,7 +433,7 @@ def apply(self, f, expr, n, evaluation, options): if count is None: count = self.get_option(options, "MaxIterations", evaluation) - if count.is_numeric(): + if count.is_numeric(evaluation): count = count.get_int_value() else: count = None diff --git a/mathics/builtin/recurrence.py b/mathics/builtin/recurrence.py index 477e25981..0667e82d3 100644 --- a/mathics/builtin/recurrence.py +++ b/mathics/builtin/recurrence.py @@ -100,7 +100,7 @@ def is_relation(eqn): left.get_head_name() == func.get_head_name() and len(left.leaves) == 1 # noqa and isinstance(l.leaves[0].to_python(), int) - and r.is_numeric() + and r.is_numeric(evaluation) ): r_sympy = r.to_sympy() diff --git a/mathics/builtin/sparse.py b/mathics/builtin/sparse.py index 88ce36083..c1a11be79 100644 --- a/mathics/builtin/sparse.py +++ b/mathics/builtin/sparse.py @@ -83,7 +83,7 @@ def list_to_sparse(self, array, evaluation): for i, leaf in enumerate(array.leaves): if leaf.has_form("SparseArray", None) or leaf.has_form("List", None): return - if leaf.is_numeric() and leaf.is_zero: + if leaf.is_numeric(evaluation) and leaf.is_zero: continue leaves.append( Expression("Rule", Expression("List", Integer(i + 1)), leaf) diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 224db67a5..14bdaa171 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -735,6 +735,8 @@ def get_tag_position(pattern, name) -> typing.Optional[str]: head_name = pattern.get_head_name() if head_name == name: return "down" + elif head_name == "System`N" and len(pattern.leaves) == 2: + return "n" elif head_name == "System`Condition" and len(pattern.leaves) > 0: return get_tag_position(pattern.leaves[0], name) elif pattern.get_lookup_name() == name: @@ -802,8 +804,6 @@ def __init__( self.downvalues = downvalues self.subvalues = subvalues self.upvalues = upvalues - for rule in rules: - self.add_rule(rule) self.formatvalues = dict((name, list) for name, list in formatvalues.items()) self.messages = messages self.attributes = set(attributes) @@ -813,6 +813,8 @@ def __init__( self.nvalues = nvalues self.defaultvalues = defaultvalues self.builtin = builtin + for rule in rules: + self.add_rule(rule) def get_values_list(self, pos): assert pos.isalpha() diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 296ea8560..20e2719ae 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -348,7 +348,7 @@ def is_atom(self) -> bool: def is_true(self) -> bool: return False - def is_numeric(self) -> bool: + def is_numeric(self, evaluation=None) -> bool: # used by NumericQ and expression ordering return False @@ -1837,24 +1837,29 @@ def thread(self, evaluation, head=None) -> typing.Tuple[bool, "Expression"]: leaves = [Expression(self._head, *item) for item in items] return True, Expression(head, *leaves) - def is_numeric(self) -> bool: - return ( - self._head.get_name() - in system_symbols( - "Sqrt", - "Times", - "Plus", - "Subtract", - "Minus", - "Power", - "Abs", - "Divide", - "Sin", + def is_numeric(self, evaluation=None) -> bool: + if evaluation: + if not "System`NumericFunction" in evaluation.definitions.get_attributes( + self._head.get_name() + ): + return False + return all(leaf.is_numeric(evaluation) for leaf in self._leaves) + else: + return ( + self._head.get_name() + in system_symbols( + "Sqrt", + "Times", + "Plus", + "Subtract", + "Minus", + "Power", + "Abs", + "Divide", + "Sin", + ) + and all(leaf.is_numeric() for leaf in self._leaves) ) - and all(leaf.is_numeric() for leaf in self._leaves) - ) - # TODO: complete list of numeric functions, or access NumericFunction - # attribute def numerify(self, evaluation) -> "Expression": _prec = None @@ -2088,7 +2093,7 @@ def evaluate(self, evaluation): def is_true(self) -> bool: return self == SymbolTrue - def is_numeric(self) -> bool: + def is_numeric(self, evaluation=None) -> bool: return self.name in system_symbols( "Pi", "E", "EulerGamma", "GoldenRatio", "MachinePrecision", "Catalan" ) @@ -2113,6 +2118,7 @@ def __getnewargs__(self): SymbolFalse = Symbol("False") SymbolInfinity = Symbol("Infinity") SymbolList = Symbol("List") +SymbolMachinePrecision = Symbol("MachinePrecision") SymbolMakeBoxes = Symbol("MakeBoxes") SymbolN = Symbol("N") SymbolNull = Symbol("Null") @@ -2120,6 +2126,8 @@ def __getnewargs__(self): SymbolSequence = Symbol("Sequence") SymbolTrue = Symbol("True") SymbolUndefined = Symbol("Undefined") +SymbolLess = Symbol("Less") +SymbolGreater = Symbol("Greater") @lru_cache(maxsize=1024) @@ -2145,7 +2153,7 @@ class Number(Atom): def __str__(self) -> str: return str(self.value) - def is_numeric(self) -> bool: + def is_numeric(self, evaluation=None) -> bool: return True From 1aa72ba50c3feba8935606385b42fe962245e63a Mon Sep 17 00:00:00 2001 From: mmatera Date: Sat, 4 Sep 2021 16:13:34 -0300 Subject: [PATCH 095/193] fixing CHANGES.rst --- CHANGES.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c23414213..3782294a1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,8 +8,6 @@ Internals to use the definitions. * To numerify expressions, the function `_numeric_evaluation_with_prec` was introduced in the module `mathics.builtin.numeric` to avoid the idiom `Expression("N", expr, prec).evaluate(evaluation)`. The idea is to avoid when it is possible to call the Pattern matching routines to obtain the numeric value of an expression. -* `Positive`, `Negative`, `NonPositive` and `NonNegative` do not check their arguments using the Pattern matching mechanism, but inside the Python code. -* special cases for `quick_pattern_test` were reduced as much as possible. * A bug comming from a failure in the order in which `mathics.core.definitions` stores the rules was fixed. From 6e09568dd7f56de4fc6f34d54dcd4614e1bab8f3 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 4 Sep 2021 20:21:24 +0000 Subject: [PATCH 096/193] Fix 2d cuboid --- mathics/builtin/drawing/graphics3d.py | 35 +++------------------------ 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 2910890a4..4259dd5ff 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -245,42 +245,13 @@ class Cuboid(Builtin): rules = { "Cuboid[]": "Cuboid[{{0, 0, 0}, {1, 1, 1}}]", + "Cuboid[{xmin_?NumberQ, ymin_?NumberQ}]": "Rectangle[{xmin, ymin}, {xmin + 1, ymin + 1}]", + "Cuboid[{xmin_, ymin_}, {xmax_, ymax_}]": "Rectangle[{xmin, ymin}, {xmax, ymax}]", + "Cuboid[{xmin_, ymin_, zmin_}]": "Cuboid[{{xmin, ymin, zmin}, {xmin + 1, ymin + 1, zmin + 1}}]", } summary_text = "unit cube" - def apply_unit_square(self, xmin, ymin, evaluation): - "Cuboid[{xmin_, ymin_}]" - - return Expression( - "Rectangle", - List(xmin, ymin), - List( - Real(xmin.to_python() + 1), - Real(ymin.to_python() + 1), - ), - ) - - def apply_unit_cube(self, xmin, ymin, zmin, evaluation): - "Cuboid[{xmin_, ymin_, zmin_}]" - - return Expression( - "Cuboid", - List( - List(xmin, ymin, zmin), - List( - Real(xmin.to_python() + 1), - Real(ymin.to_python() + 1), - Real(zmin.to_python() + 1), - ), - ), - ) - - def apply_rectangle(self, xmin, ymin, xmax, ymax, evaluation): - "Cuboid[{xmin_, ymin_}, {xmax_, ymax_}]" - - return Expression("Rectangle", List(xmin, ymin), List(xmax, ymax)) - def apply_check(self, positions, evaluation): "Cuboid[positions_List]" From 2cd07ac36e932d896b051d91f7a3c6a7b55b632c Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 4 Sep 2021 18:58:49 -0400 Subject: [PATCH 097/193] Tolerate Unicode DifferentialD We are in the process of preferring Unicode symbols over custom WL symbols on input (when they are different) since our frontends use standard Unicode when they support Unicode. --- mathics/core/expression.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 296ea8560..4fdeef234 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -2916,6 +2916,7 @@ def render(format, string, in_text=False): elif text and text[0] in "0123456789-.": return render("%s", text) else: + # FIXME: this should be done in a better way. if text == "\u2032": return "'" elif text == "\u2032\u2032": @@ -2938,7 +2939,8 @@ def render(format, string, in_text=False): return text elif text == "\u222b": return r"\int" - elif text == "\u2146": + # Tolerate WL or Unicode DifferentialD + elif text in ("\u2146", "\U0001D451"): return r"\, d" elif text == "\u2211": return r"\sum" From 68db2b0150b3a9b6bda73d66f3bb0e2e50ad6d80 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 5 Sep 2021 16:44:17 -0300 Subject: [PATCH 098/193] _numeric_evaluation_with_prec -> apply_N --- CHANGES.rst | 2 +- mathics/builtin/arithmetic.py | 4 ++-- mathics/builtin/numbers/calculus.py | 6 +++--- mathics/builtin/numbers/numbertheory.py | 8 ++++---- mathics/builtin/numeric.py | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3782294a1..6471d6600 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,7 +6,7 @@ Internals * now `Expression.is_numeric()` accepts an `Evaluation` object as a parameter, to use the definitions. -* To numerify expressions, the function `_numeric_evaluation_with_prec` was introduced in the module `mathics.builtin.numeric` to avoid the idiom +* To numerify expressions, the function `apply_N` was introduced in the module `mathics.builtin.numeric` to avoid the idiom `Expression("N", expr, prec).evaluate(evaluation)`. The idea is to avoid when it is possible to call the Pattern matching routines to obtain the numeric value of an expression. * A bug comming from a failure in the order in which `mathics.core.definitions` stores the rules was fixed. diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 19dd9c0b4..754426e8f 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -47,7 +47,7 @@ from mathics.core.convert import from_sympy, SympyExpression, sympy_symbol_prefix from mathics.builtin.scoping import dynamic_scoping from mathics.builtin.inference import get_assumptions_list, evaluate_predicate -from mathics.builtin.numeric import _numeric_evaluation_with_prec +from mathics.builtin.numeric import apply_N @lru_cache(maxsize=1024) @@ -134,7 +134,7 @@ def apply(self, z, evaluation): prec = min_prec(*args) d = dps(prec) args = [ - _numeric_evaluation_with_prec(arg, evaluation, Integer(d)) + apply_N(arg, evaluation, Integer(d)) for arg in args ] with mpmath.workprec(prec): diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index d987a1c62..ad35738f0 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -26,7 +26,7 @@ from mathics.core.rules import Pattern from mathics.core.numbers import dps from mathics.builtin.scoping import dynamic_scoping -from mathics.builtin.numeric import _numeric_evaluation_with_prec +from mathics.builtin.numeric import apply_N from mathics import Symbol import sympy @@ -1280,7 +1280,7 @@ def sub(evaluation): # TODO: use Precision goal... if x1 == x0: break - x0 = _numeric_evaluation_with_prec(x1, evaluation) + x0 = apply_N(x1, evaluation) # N required due to bug in sympy arithmetic count += 1 else: @@ -1376,7 +1376,7 @@ class FindRoot(Builtin): def apply(self, f, x, x0, evaluation, options): "FindRoot[f_, {x_, x0_}, OptionsPattern[]]" # First, determine x0 and x - x0 = _numeric_evaluation_with_prec(x0, evaluation) + x0 = apply_N(x0, evaluation) if not isinstance(x0, Number): evaluation.message("FindRoot", "snum", x0) return diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index a30235e2d..b59b49e44 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -20,7 +20,7 @@ from mathics.core.convert import from_sympy, SympyPrime import mpmath -from mathics.builtin.numeric import _numeric_evaluation_with_prec +from mathics.builtin.numeric import apply_N class ContinuedFraction(SympyFunction): @@ -413,13 +413,13 @@ def apply(self, n, b, evaluation): return expr if n_sympy.is_constant(): - temp_n = _numeric_evaluation_with_prec(n, evaluation) + temp_n = apply_N(n, evaluation) py_n = temp_n.to_python() else: return expr if b_sympy.is_constant(): - temp_b = _numeric_evaluation_with_prec(b, evaluation) + temp_b = apply_N(b, evaluation) py_b = temp_b.to_python() else: return expr @@ -444,7 +444,7 @@ def apply_2(self, n, evaluation): return expr # Handle Input with special cases such as PI and E if n_sympy.is_constant(): - temp_n = _numeric_evaluation_with_prec(n, evaluation) + temp_n = apply_N(n, evaluation) py_n = temp_n.to_python() else: return expr diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 980a5d480..2bdae465e 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -62,7 +62,7 @@ def log_n_b(py_n, py_b) -> int: return int(mpmath.ceil(mpmath.log(py_n, py_b))) if py_n != 0 and py_n != 1 else 1 -def _numeric_evaluation_with_prec(expression, evaluation, prec=SymbolMachinePrecision): +def apply_N(expression, evaluation, prec=SymbolMachinePrecision): return Expression("N", expression, prec).evaluate(evaluation) From 33a2b69cc7d415327a7907b4ba53e28059326b41 Mon Sep 17 00:00:00 2001 From: autoblack Date: Sun, 5 Sep 2021 19:44:57 +0000 Subject: [PATCH 099/193] fixup: Format Python code with Black --- mathics/builtin/arithmetic.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 754426e8f..bcd495cde 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -133,10 +133,7 @@ def apply(self, z, evaluation): else: prec = min_prec(*args) d = dps(prec) - args = [ - apply_N(arg, evaluation, Integer(d)) - for arg in args - ] + args = [apply_N(arg, evaluation, Integer(d)) for arg in args] with mpmath.workprec(prec): mpmath_args = [x.to_mpmath() for x in args] if None in mpmath_args: From 181483e034783cb1fce1af867b0f3406b6a19ced Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Wed, 25 Aug 2021 20:41:45 -0300 Subject: [PATCH 100/193] More rule tweaks --- mathics/builtin/drawing/image.py | 2 +- mathics/builtin/numbers/numbertheory.py | 2 +- mathics/builtin/physchemdata.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 531ccce91..96d943507 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -158,7 +158,7 @@ class ImageExport(_ImageBuiltin): messages = {"noimage": "only an Image[] can be exported into an image file"} def apply(self, path, expr, opts, evaluation): - """ImageExport[path_?StringQ, expr_, opts___]""" + """ImageExport[path_String, expr_, opts___]""" if isinstance(expr, Image): expr.pil().save(path.get_string_value()) return SymbolNull diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index b59b49e44..7c53f015f 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -488,7 +488,7 @@ class NextPrime(Builtin): } def apply(self, n, k, evaluation): - "NextPrime[n_?NumericQ, k_Integer]" + "NextPrime[n_?NumberQ, k_Integer]" py_k = k.to_python(n_evaluation=evaluation) py_n = n.to_python(n_evaluation=evaluation) diff --git a/mathics/builtin/physchemdata.py b/mathics/builtin/physchemdata.py index 4e0048ef8..a6b8820fb 100644 --- a/mathics/builtin/physchemdata.py +++ b/mathics/builtin/physchemdata.py @@ -123,7 +123,7 @@ def apply_all_properties(self, evaluation): return from_python(sorted(_ELEMENT_DATA[0])) def apply_name(self, name, prop, evaluation): - "ElementData[name_?StringQ, prop_]" + "ElementData[name_String, prop_]" py_name = name.to_python().strip('"') names = ["StandardName", "Name", "Abbreviation"] iprops = [_ELEMENT_DATA[0].index(s) for s in names] From f6178aa3f4d627bbd58e1d452ce8c9fcf37ebcba Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 31 Aug 2021 16:20:59 +0000 Subject: [PATCH 101/193] Improve `quick_pattern_test` readability --- mathics/builtin/patterns.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index eff83952a..db46ba3eb 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -477,7 +477,7 @@ def init(self, expr): def quick_pattern_test(self, candidate, test, evaluation): if test == "System`NumberQ": return isinstance(candidate, Number) - if test == "System`NumericQ": + elif test == "System`NumericQ": if isinstance(candidate, Number): return True # Otherwise, follow the standard evaluation @@ -485,9 +485,7 @@ def quick_pattern_test(self, candidate, test, evaluation): if isinstance(candidate, (Integer, Rational, Real)): return True candidate = Expression(SymbolN, candidate).evaluate(evaluation) - if isinstance(candidate, Real): - return True - return False + return isinstance(candidate, Real) # pass elif test == "System`Positive": if isinstance(candidate, (Integer, Rational, Real)): @@ -539,10 +537,9 @@ def yield_match(vars_2, rest): for item in items: item = item.evaluate(evaluation) quick_test = self.quick_pattern_test(item, self.test_name, evaluation) - if quick_test is not None: - if not quick_test: - break - # raise StopGenerator + if quick_test is False: + break + # raise StopGenerator else: test_expr = Expression(self.test, item) test_value = test_expr.evaluate(evaluation) From 178aa818261bb385d77631ad6cdb11a9ea5f2d3e Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 31 Aug 2021 16:21:42 +0000 Subject: [PATCH 102/193] Improve ElementData performance --- mathics/builtin/physchemdata.py | 143 ++++++++++++++++---------------- 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/mathics/builtin/physchemdata.py b/mathics/builtin/physchemdata.py index a6b8820fb..3984aebdc 100644 --- a/mathics/builtin/physchemdata.py +++ b/mathics/builtin/physchemdata.py @@ -14,6 +14,7 @@ from mathics.core.expression import ( Expression, from_python, + Integer, Symbol, String, strip_context, @@ -122,76 +123,74 @@ def apply_all_properties(self, evaluation): 'ElementData[All, "Properties"]' return from_python(sorted(_ELEMENT_DATA[0])) - def apply_name(self, name, prop, evaluation): - "ElementData[name_String, prop_]" - py_name = name.to_python().strip('"') - names = ["StandardName", "Name", "Abbreviation"] - iprops = [_ELEMENT_DATA[0].index(s) for s in names] - - indx = None - for iprop in iprops: - try: - indx = [element[iprop] for element in _ELEMENT_DATA[1:]].index( - py_name - ) + 1 - except ValueError: - pass - - if indx is None: - evaluation.message("ElementData", "noent", name) - return - - return self.apply_int(from_python(indx), prop, evaluation) - - def apply_int(self, n, prop, evaluation): - "ElementData[n_Integer, prop_]" - - py_n = n.to_python() - py_prop = prop.to_python() - - # Check element specifier n or "name" - if isinstance(py_n, int): - if not 1 <= py_n <= 118: - evaluation.message("ElementData", "noent", n) + def apply_name(self, expr, prop, evaluation): + "ElementData[expr_, prop_]" + + if isinstance(expr, String): + py_name = expr.to_python(string_quotes=False) + names = ["StandardName", "Name", "Abbreviation"] + iprops = [_ELEMENT_DATA[0].index(s) for s in names] + + indx = None + for iprop in iprops: + try: + indx = [element[iprop] for element in _ELEMENT_DATA[1:]].index( + py_name + ) + 1 + except ValueError: + pass + + if indx is None: + evaluation.message("ElementData", "noent", expr) return - elif isinstance(py_n, str): - pass - else: - evaluation.message("ElementData", "noent", n) - return - - # Check property specifier - if isinstance(py_prop, str): - py_prop = str(py_prop) - - if py_prop == '"Properties"': - result = [] - for i, p in enumerate(_ELEMENT_DATA[py_n]): - if p not in ["NOT_AVAILABLE", "NOT_APPLICABLE", "NOT_KNOWN"]: - result.append(_ELEMENT_DATA[0][i]) - return from_python(sorted(result)) - - if not ( - isinstance(py_prop, str) - and py_prop[0] == py_prop[-1] == '"' - and py_prop.strip('"') in _ELEMENT_DATA[0] - ): - evaluation.message("ElementData", "noprop", prop) - return - - iprop = _ELEMENT_DATA[0].index(py_prop.strip('"')) - result = _ELEMENT_DATA[py_n][iprop] - - if result == "NOT_AVAILABLE": - return Expression("Missing", "NotAvailable") - - if result == "NOT_APPLICABLE": - return Expression("Missing", "NotApplicable") - - if result == "NOT_KNOWN": - return Expression("Missing", "Unknown") - - result = evaluation.parse(result) - if isinstance(result, Symbol): - result = String(strip_context(result.get_name())) - return result + + # Enter in the next if, but with expr being the index + expr = from_python(indx) + if isinstance(expr, Integer): + py_n = expr.value + py_prop = prop.to_python() + + # Check element specifier n or "name" + if isinstance(py_n, int): + if not 1 <= py_n <= 118: + evaluation.message("ElementData", "noent", expr) + return + else: + evaluation.message("ElementData", "noent", expr) + return + + # Check property specifier + if isinstance(py_prop, str): + py_prop = str(py_prop) + + if py_prop == '"Properties"': + result = [] + for i, p in enumerate(_ELEMENT_DATA[py_n]): + if p not in ["NOT_AVAILABLE", "NOT_APPLICABLE", "NOT_KNOWN"]: + result.append(_ELEMENT_DATA[0][i]) + return from_python(sorted(result)) + + if not ( + isinstance(py_prop, str) + and py_prop[0] == py_prop[-1] == '"' + and py_prop.strip('"') in _ELEMENT_DATA[0] + ): + evaluation.message("ElementData", "noprop", prop) + return + + iprop = _ELEMENT_DATA[0].index(py_prop.strip('"')) + result = _ELEMENT_DATA[py_n][iprop] + + if result == "NOT_AVAILABLE": + return Expression("Missing", "NotAvailable") + + if result == "NOT_APPLICABLE": + return Expression("Missing", "NotApplicable") + + if result == "NOT_KNOWN": + return Expression("Missing", "Unknown") + + result = evaluation.parse(result) + if isinstance(result, Symbol): + result = String(strip_context(result.get_name())) + return result From c5141e59add014e4f16d9ac96f494678b50738e7 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 2 May 2021 21:36:18 -0300 Subject: [PATCH 103/193] first try: implementing <> Dispatch tables --- mathics/builtin/patterns.py | 101 ++++++++++++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index db46ba3eb..4b7563173 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -38,11 +38,13 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.base import Builtin, BinaryOperator, PostfixOperator +from mathics.builtin.base import Builtin, BinaryOperator, PostfixOperator, AtomBuiltin from mathics.builtin.base import PatternObject, PatternError from mathics.builtin.lists import python_levelspec, InvalidLevelspecError from mathics.core.expression import ( + Atom, + String, Symbol, Expression, Number, @@ -108,13 +110,16 @@ class RuleDelayed(BinaryOperator): def create_rules(rules_expr, expr, name, evaluation, extra_args=[]): - if rules_expr.has_form("Dispatch", None): - rules_expr = rules_expr.leaves[0] + if isinstance(rules_expr, Dispatch): + return rules_expr.rules, False + elif rules_expr.has_form("Dispatch", None): + return Dispatch(rules_expr._leaves, evaluation) + if rules_expr.has_form("List", None): rules = rules_expr.leaves else: rules = [rules_expr] - any_lists = any(item.has_form("List", None) for item in rules) + any_lists = any(item.has_form(("List", "Dispatch"), None) for item in rules) if any_lists: all_lists = all(item.has_form("List", None) for item in rules) if all_lists: @@ -292,10 +297,8 @@ def apply(self, expr, rules, evaluation): "ReplaceAll[expr_, rules_]" try: rules, ret = create_rules(rules, expr, "ReplaceAll", evaluation) - if ret: return rules - result, applied = expr.apply_rules(rules, evaluation) return result except PatternError: @@ -1498,7 +1501,33 @@ def yield_match(vars, rest): ) -class Dispatch(Builtin): +class Dispatch(Atom): + def __init__(self, rulelist, evaluation): + self.src = Expression(SymbolList, *rulelist) + self.rules = [Rule(rule._leaves[0], rule._leaves[1]) for rule in rulelist] + self._leaves = None + self._head = Symbol("Dispatch") + + def get_sort_key(self): + return self.src.get_sort_key() + + def get_atom_name(self): + return "System`Dispatch" + + def __repr__(self): + return "dispatch" + + def atom_to_boxes(self, f, evaluation): + leaves = self.src.format(evaluation, f.get_name()) + return Expression( + "RowBox", + Expression( + SymbolList, String("Dispatch"), String("["), leaves, String("]") + ), + ) + + +class DispatchAtom(AtomBuiltin): """
'Dispatch[$rulelist$]' @@ -1506,10 +1535,23 @@ class Dispatch(Builtin): In the future, it should return an optimized DispatchRules atom, containing an optimized set of rules.
- + >> rules = {{a_,b_}->a^b, {1,2}->3., F[x_]->x^2}; + >> F[2] /. rules + = 4 + >> dispatchrules = Dispatch[rules] + = Dispatch[{{a_, b_} -> a ^ b, {1, 2} -> 3., F[x_] -> x ^ 2}] + >> F[2] /. dispatchrules + = 4 """ - def apply_stub(self, rules, evaluation): + messages = { + "invrpl": "`1` is not a valid rule or list of rules.", + } + + def __repr__(self): + return "dispatchatom" + + def apply_create(self, rules, evaluation): """Dispatch[rules_List]""" # TODO: # The next step would be to enlarge this method, in order to @@ -1519,4 +1561,43 @@ def apply_stub(self, rules, evaluation): # compiled patters, and modify Replace and ReplaceAll to handle this # kind of objects. # - return rules + if isinstance(rules, Dispatch): + return rules + if rules.is_symbol(): + rules = rules.evaluate(evaluation) + + if rules.has_form("List", None): + rules = rules._leaves + else: + rules = [rules] + + all_list = all(rule.has_form("List", None) for rule in rules) + if all_list: + leaves = [self.apply_create(rule, evaluation) for rule in rules] + return Expression(SymbolList, *leaves) + flatten_list = [] + for rule in rules: + if rule.is_symbol(): + rule = rule.evaluate(evaluation) + if rule.has_form("List", None): + flatten_list.extend(rule._leaves) + elif rule.has_form(("Rule", "RuleDelayed"), 2): + flatten_list.append(rule) + elif isinstance(rule, Dispatch): + flatten_list.extend(rule.src._leaves) + else: + # WMA does not raise this message: just leave it unevaluated, + # and raise an error when the dispatch rule is used. + evaluation.message("Dispatch", "invrpl", rule) + return + try: + return Dispatch(flatten_list, evaluation) + except: + return + + def apply_normal(self, dispatch, evaluation): + """Normal[dispatch_Dispatch]""" + if isinstance(dispatch, Dispatch): + return dispatch.src + else: + return dispatch._leaves[0] From 48ab7be9801e7e647fb244965e7b28cd6d4ceb5a Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 4 Sep 2021 20:42:18 +0000 Subject: [PATCH 104/193] Improve MakeBoxes performance by rewriting some rules --- mathics/builtin/inout.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index 73b669f7c..95b999122 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -462,10 +462,8 @@ class MakeBoxes(Builtin): "MakeBoxes[expr_]": "MakeBoxes[expr, StandardForm]", "MakeBoxes[(form:StandardForm|TraditionalForm|OutputForm|TeXForm|" "MathMLForm)[expr_], StandardForm|TraditionalForm]": ("MakeBoxes[expr, form]"), - "MakeBoxes[(form:OutputForm|MathMLForm|TeXForm)[expr_], OutputForm]": "MakeBoxes[expr, form]", - "MakeBoxes[StandardForm[expr_], OutputForm]": "MakeBoxes[expr, OutputForm]", - "MakeBoxes[FullForm[expr_], StandardForm|TraditionalForm|OutputForm]": "StyleBox[MakeBoxes[expr, FullForm], ShowStringCharacters->True]", - "MakeBoxes[InputForm[expr_], StandardForm|TraditionalForm|OutputForm]": "StyleBox[MakeBoxes[expr, InputForm], ShowStringCharacters->True]", + "MakeBoxes[(form:StandardForm|OutputForm|MathMLForm|TeXForm)[expr_], OutputForm]": "MakeBoxes[expr, form]", + "MakeBoxes[(form:FullForm|InputForm)[expr_], StandardForm|TraditionalForm|OutputForm]": "StyleBox[MakeBoxes[expr, form], ShowStringCharacters->True]", "MakeBoxes[PrecedenceForm[expr_, prec_], f_]": "MakeBoxes[expr, f]", "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": ( "StyleBox[MakeBoxes[expr, f], " @@ -515,15 +513,9 @@ def apply_general(self, expr, f, evaluation): result.append(String(right)) return RowBox(Expression(SymbolList, *result)) - def _apply_atom(self, x, f, evaluation): - """MakeBoxes[x_?AtomQ, - f:TraditionalForm|StandardForm|OutputForm|InputForm|FullForm]""" - - return x.atom_to_boxes(f, evaluation) - - def apply_outerprecedenceform(self, expr, prec, f, evaluation): + def apply_outerprecedenceform(self, expr, prec, evaluation): """MakeBoxes[OuterPrecedenceForm[expr_, prec_], - f:StandardForm|TraditionalForm|OutputForm|InputForm]""" + StandardForm|TraditionalForm|OutputForm|InputForm]""" precedence = prec.get_int_value() boxes = MakeBoxes(expr) From c1b73a82af173eb9a0015a4432cd37f486917098 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 11 Sep 2021 11:58:28 -0400 Subject: [PATCH 105/193] mathics/Mathics -> Mathics3/mathics-core --- .pre-commit-config.yaml | 4 ++-- README.rst | 6 +++--- mathics/builtin/files_io/files.py | 2 +- mathics/doc/documentation/1-Manual.mdoc | 2 +- setup.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3879d560a..fea5b496a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,11 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.0.0 + rev: v4.0.1 hooks: - id: debug-statements - id: end-of-file-fixer - repo: https://github.com/psf/black - rev: 21.6b0 + rev: 21.8b0 hooks: - id: black language_version: python3 diff --git a/README.rst b/README.rst index a73d1f368..8a2a3e4db 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Welcome to Mathics! =================== -|Pypi Installs| |Latest Version| |Supported Python Versions| |Travis|_ |SlackStatus|_ +|Pypi Installs| |Latest Version| |Supported Python Versions| |SlackStatus|_ |Packaging status| @@ -43,8 +43,8 @@ Mathics is released under the GNU General Public License Version 3 (GPL3). .. |SlackStatus| image:: https://mathics-slackin.herokuapp.com/badge.svg .. _SlackStatus: https://mathics-slackin.herokuapp.com/ -.. |Travis| image:: https://secure.travis-ci.org/mathics/Mathics.svg?branch=master -.. _Travis: https://travis-ci.org/mathics/Mathics +.. |Travis| image:: https://secure.travis-ci.org/Mathics3/mathics-core.svg?branch=master +.. _Travis: https://travis-ci.org/Mathics3/mathics-core .. _PyPI: https://pypi.org/project/Mathics/ .. |mathicsscript| image:: https://github.com/Mathics3/mathicsscript/blob/master/screenshots/mathicsscript1.gif .. |mathicssserver| image:: https://mathics.org/images/mathicsserver.png diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 56af56c95..0a496506f 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -1773,7 +1773,7 @@ class OpenRead(_OpenAction): = InputStream[...] #> Close[%]; - S> OpenRead["https://raw.githubusercontent.com/mathics/Mathics/master/README.rst"] + S> OpenRead["https://raw.githubusercontent.com/Mathics3/mathics-core/master/README.rst"] = InputStream[...] S> Close[%]; diff --git a/mathics/doc/documentation/1-Manual.mdoc b/mathics/doc/documentation/1-Manual.mdoc index c384fd5f0..6502b0042 100644 --- a/mathics/doc/documentation/1-Manual.mdoc +++ b/mathics/doc/documentation/1-Manual.mdoc @@ -61,7 +61,7 @@ Graphics has always been lagging and in the future we intend to decouple Graphic
-\Mathics was created by Jan Pöschk in 2011. From 2013 to about 2017 it had been maintained mostly by Angus Griffith and Ben Jones. Since then, a number of others have been people involved in \Mathics; the list can be found in the AUTHORS.txt file, https://github.com/mathics/Mathics/blob/master/AUTHORS.txt. +\Mathics was created by Jan Pöschk in 2011. From 2013 to about 2017 it had been maintained mostly by Angus Griffith and Ben Jones. Since then, a number of others have been people involved in \Mathics; the list can be found in the AUTHORS.txt file, https://github.com/Mathics-3/mathics/blob/master/AUTHORS.txt. If you have any ideas on how to improve \Mathics or even want to help out yourself, please contact us! diff --git a/setup.py b/setup.py index c67cd8f16..bc4ce71c1 100644 --- a/setup.py +++ b/setup.py @@ -191,7 +191,7 @@ def subdirs(root, file="*.*", depth=10): description="A general-purpose computer algebra system.", license="GPL", url="https://mathics.org/", - download_url="https://github.com/mathics/Mathics/releases", + download_url="https://github.com/Mathics/mathics-core/releases", keywords=["Mathematica", "Wolfram", "Interpreter", "Shell", "Math", "CAS"], classifiers=[ "Intended Audience :: Developers", From 4e71d9561db15c241fd728a89e321d2c4db11a9f Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sat, 11 Sep 2021 16:23:04 -0300 Subject: [PATCH 106/193] Update AUTHORS.txt --- AUTHORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 0cd6ce65b..9dc803a42 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -35,6 +35,7 @@ Additional contributions were made by: - Сухарик @suhr - Pablo Emilio Escobar Gaviria @GarkGarcia - Rocky Bernstein @rocky +- Tiago Cavalcante Trindade @TiagoCavalcante Thanks to the authors of all projects that are used in Mathics: - Django From 278cdeefe660c11323760e33f90663874c4e96a6 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 13 Sep 2021 07:50:28 -0400 Subject: [PATCH 107/193] Replace _Graphics3DElement by InstanceableBuiltin --- mathics/builtin/box/graphics3d.py | 14 ++++---------- mathics/builtin/box/uniform_polyhedra.py | 7 +++---- mathics/builtin/drawing/graphics3d.py | 14 -------------- 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index 88a2cf277..117851e56 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -7,7 +7,7 @@ import json import numbers -from mathics.builtin.base import BoxConstructError +from mathics.builtin.base import BoxConstructError, InstanceableBuiltin from mathics.builtin.box.graphics import ( GraphicsBox, ArrowBox, @@ -19,7 +19,6 @@ from mathics.builtin.colors.color_directives import _Color, RGBColor from mathics.builtin.drawing.graphics_internals import GLOBALS3D from mathics.builtin.drawing.graphics3d import ( - _Graphics3DElement, Coords3D, Graphics3DElements, ) @@ -710,14 +709,12 @@ def _apply_boxscaling(self, boxscale): coords.scale(boxscale) -class Cuboid3DBox(_Graphics3DElement): +class Cuboid3DBox(InstanceableBuiltin): """ Internal Python class used when Boxing a 'Cuboid' object. """ def init(self, graphics, style, item): - super(Cuboid3DBox, self).init(graphics, item, style) - self.edge_color, self.face_color = style.get_style(_Color, face_element=True) if len(item.leaves) != 1: @@ -740,14 +737,12 @@ def _apply_boxscaling(self, boxscale): pass -class Cylinder3DBox(_Graphics3DElement): +class Cylinder3DBox(InstanceableBuiltin): """ Internal Python class used when Boxing a 'Cylinder' object. """ def init(self, graphics, style, item): - super(Cylinder3DBox, self).init(graphics, item, style) - self.edge_color, self.face_color = style.get_style(_Color, face_element=True) if len(item.leaves) != 2: @@ -842,9 +837,8 @@ def _apply_boxscaling(self, boxscale): coords.scale(boxscale) -class Sphere3DBox(_Graphics3DElement): +class Sphere3DBox(InstanceableBuiltin): def init(self, graphics, style, item): - super(Sphere3DBox, self).init(graphics, item, style) self.edge_color, self.face_color = style.get_style(_Color, face_element=True) if len(item.leaves) != 2: raise BoxConstructError diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py index 6e69cebbf..b71850ed6 100644 --- a/mathics/builtin/box/uniform_polyhedra.py +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -1,6 +1,6 @@ -from mathics.builtin.box.graphics3d import _Graphics3DElement, Coords3D +from mathics.builtin.box.graphics3d import Coords3D -from mathics.builtin.base import BoxConstructError +from mathics.builtin.base import BoxConstructError, InstanceableBuiltin from mathics.builtin.drawing.graphics_internals import GLOBALS3D from mathics.builtin.colors.color_directives import _Color @@ -9,9 +9,8 @@ import numbers -class UniformPolyhedron3DBox(_Graphics3DElement): +class UniformPolyhedron3DBox(InstanceableBuiltin): def init(self, graphics, style, item): - super(UniformPolyhedron3DBox, self).init(graphics, item, style) self.edge_color, self.face_color = style.get_style(_Color, face_element=True) if len(item.leaves) != 3: diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 0c54370e4..475995e4d 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -62,20 +62,6 @@ class Style3D(Style): def get_default_face_color(self): return RGBColor(components=(1, 1, 1, 1)) - -class _Graphics3DElement(InstanceableBuiltin): - def init(self, graphics, item=None, style=None): - if ( - item is not None - and hasattr(item, "has_form") - and not item.has_form(self.get_name(), None) - ): - raise BoxConstructError - self.graphics = graphics - self.style = style - self.is_completely_visible = False # True for axis elements - - class Graphics3D(Graphics): r"""
From bebafe04015d180f80db5aa3241895d23617070f Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 13 Sep 2021 07:51:21 -0400 Subject: [PATCH 108/193] Replace array by tuple for 3d primitives' coordinates --- mathics/builtin/box/graphics3d.py | 6 +++--- mathics/builtin/box/uniform_polyhedra.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index 117851e56..1ef270bba 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -727,7 +727,7 @@ def init(self, graphics, style, item): ): raise BoxConstructError - self.points = [Coords3D(graphics, pos=point) for point in points] + self.points = tuple(Coords3D(graphics, pos=point) for point in points) def extent(self): return [coords.pos()[0] for coords in self.points] @@ -755,7 +755,7 @@ def init(self, graphics, style, item): ): raise BoxConstructError - self.points = [Coords3D(graphics, pos=point) for point in points] + self.points = tuple(Coords3D(graphics, pos=point) for point in points) self.radius = item.leaves[1].to_python() def extent(self): @@ -852,7 +852,7 @@ def init(self, graphics, style, item): ): raise BoxConstructError - self.points = [Coords3D(graphics, pos=point) for point in points] + self.points = tuple(Coords3D(graphics, pos=point) for point in points) self.radius = item.leaves[1].to_python() def extent(self): diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py index b71850ed6..ddc7a701b 100644 --- a/mathics/builtin/box/uniform_polyhedra.py +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -23,7 +23,7 @@ def init(self, graphics, style, item): ): raise BoxConstructError - self.points = [Coords3D(graphics, pos=point) for point in points] + self.points = tuple(Coords3D(graphics, pos=point) for point in points) self.edge_length = item.leaves[2].to_python() self.sub_type = item.leaves[0].to_python(string_quotes=False) From 29d46f031cc52247043047d2955bd78b62c98329 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 13 Sep 2021 07:52:05 -0400 Subject: [PATCH 109/193] Remove useless return values for 3d primitives builtins --- mathics/builtin/drawing/graphics3d.py | 2 +- mathics/builtin/drawing/uniform_polyhedra.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 475995e4d..625e2afc2 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -258,7 +258,7 @@ def apply_check(self, positions, evaluation): # The number of points is odd, so abort. evaluation.error("Cuboid", "oddn", positions) - return Expression("Cuboid", positions) + return class Cylinder(Builtin): diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py index 44475b810..2ffb4c9d0 100644 --- a/mathics/builtin/drawing/uniform_polyhedra.py +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -47,7 +47,7 @@ def apply(self, name, positions, edgelength, evaluation): if name.to_python(string_quotes=False) not in uniform_polyhedra_set: evaluation.error("UniformPolyhedron", "argtype", name) - return Expression("UniformPolyhedron", name, positions, edgelength) + return class Dodecahedron(Builtin): From 0fb098e487102ea85d1aca208e3c4031644adac2 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 13 Sep 2021 07:52:56 -0400 Subject: [PATCH 110/193] Clear `_roundbox` --- mathics/format/asy.py | 4 ++-- mathics/format/svg.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mathics/format/asy.py b/mathics/format/asy.py index 5e966dd45..4cc9d91aa 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -93,7 +93,7 @@ def arcbox(self, **options) -> str: if self.arc is None: # We have a doughnut graph and this is the inner blank hole of that. # It is an empty circle - return _roundbox(self, **options) + return _roundbox(self) x, y, rx, ry, sx, sy, ex, ey, large_arc = self._arc_params() @@ -551,7 +551,7 @@ def rectanglebox(self, **options) -> str: add_conversion_fn(RectangleBox) -def _roundbox(self, **options): +def _roundbox(self): x, y = self.c.pos() rx, ry = self.r.pos() rx -= x diff --git a/mathics/format/svg.py b/mathics/format/svg.py index 4abc0635f..abc1b3349 100644 --- a/mathics/format/svg.py +++ b/mathics/format/svg.py @@ -86,7 +86,7 @@ def arcbox(self, **options) -> str: if self.arc is None: # We have a doughnut graph and this is the inner blank hole of that. # It is an empty circle - return _roundbox(self, **options) + return _roundbox(self) x, y, rx, ry, sx, sy, ex, ey, large_arc = self._arc_params() @@ -454,7 +454,7 @@ def rectanglebox(self, **options): add_conversion_fn(RectangleBox) -def _roundbox(self, **options): +def _roundbox(self): x, y = self.c.pos() rx, ry = self.r.pos() rx -= x From 0575f3d8eb574daf12eadf60b0786ea60839eb3c Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 13 Sep 2021 07:53:47 -0400 Subject: [PATCH 111/193] Clear `Graphics3DBox` --- mathics/builtin/box/graphics3d.py | 40 +++++++++++++-------------- mathics/builtin/drawing/graphics3d.py | 1 + 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index 1ef270bba..6ab6da209 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -98,14 +98,12 @@ def _prepare_elements(self, leaves, options, max_width=None): "position": [0, 2, 2], }, ] - elif lighting == "System`None": - pass elif isinstance(lighting, list) and all( isinstance(light, list) for light in lighting ): for light in lighting: - if light[0] in ['"Ambient"', '"Directional"', '"Point"', '"Spot"']: + if light[0] in ('"Ambient"', '"Directional"', '"Point"', '"Spot"'): try: head = light[1].get_head_name() except AttributeError: @@ -211,8 +209,6 @@ def _prepare_elements(self, leaves, options, max_width=None): boxratios = self.graphics_options["System`BoxRatios"].to_python() if boxratios == "System`Automatic": boxratios = ["System`Automatic"] * 3 - else: - boxratios = boxratios if not isinstance(boxratios, list) or len(boxratios) != 3: raise BoxConstructError @@ -239,7 +235,7 @@ def calc_dimensions(final_pass=True): xmin -= 1 xmax += 1 elif isinstance(plot_range[0], list) and len(plot_range[0]) == 2: - xmin, xmax = list(map(float, plot_range[0])) + xmin, xmax = float(plot_range[0][0]), float(plot_range[0][1]) xmin = elements.translate((xmin, 0, 0))[0] xmax = elements.translate((xmax, 0, 0))[0] else: @@ -253,7 +249,7 @@ def calc_dimensions(final_pass=True): ymin -= 1 ymax += 1 elif isinstance(plot_range[1], list) and len(plot_range[1]) == 2: - ymin, ymax = list(map(float, plot_range[1])) + ymin, ymax = float(plot_range[1][0]), float(plot_range[1][1]) ymin = elements.translate((0, ymin, 0))[1] ymax = elements.translate((0, ymax, 0))[1] else: @@ -267,7 +263,7 @@ def calc_dimensions(final_pass=True): zmin -= 1 zmax += 1 elif isinstance(plot_range[1], list) and len(plot_range[1]) == 2: - zmin, zmax = list(map(float, plot_range[2])) + zmin, zmax = float(plot_range[2][0]), float(plot_range[2][1]) zmin = elements.translate((0, 0, zmin))[2] zmax = elements.translate((0, 0, zmax))[2] else: @@ -277,11 +273,11 @@ def calc_dimensions(final_pass=True): boxscale = [1.0, 1.0, 1.0] if boxratios[0] != "System`Automatic": - boxscale[0] = boxratios[0] / (xmax - xmin) + boxscale[0] /= xmax - xmin if boxratios[1] != "System`Automatic": - boxscale[1] = boxratios[1] / (ymax - ymin) + boxscale[1] /= ymax - ymin if boxratios[2] != "System`Automatic": - boxscale[2] = boxratios[2] / (zmax - zmin) + boxscale[2] /= zmax - zmin if final_pass: xmin *= boxscale[0] @@ -302,14 +298,17 @@ def calc_dimensions(final_pass=True): light["target"][j] * boxscale[j] for j in range(3) ] - w = 0 if (xmin is None or xmax is None) else xmax - xmin - h = 0 if (ymin is None or ymax is None) else ymax - ymin - - return xmin, xmax, ymin, ymax, zmin, zmax, boxscale, w, h + return xmin, xmax, ymin, ymax, zmin, zmax, boxscale - xmin, xmax, ymin, ymax, zmin, zmax, boxscale, w, h = calc_dimensions( - final_pass=False - ) + ( + xmin, + xmax, + ymin, + ymax, + zmin, + zmax, + boxscale, + ) = calc_dimensions(final_pass=False) axes, ticks, ticks_style = self.create_axes( elements, @@ -356,8 +355,7 @@ def boxes_to_json(self, leaves=None, **options): elements._apply_boxscaling(boxscale) - xmin, xmax, ymin, ymax, zmin, zmax, boxscale, w, h = calc_dimensions() - elements.view_width = w + xmin, xmax, ymin, ymax, zmin, zmax, boxscale = calc_dimensions() # FIXME: json is the only thing we can convert MathML into. # Handle other graphics formats. @@ -415,7 +413,7 @@ def boxes_to_tex(self, leaves=None, **options): else: asy = elements.to_asy() - xmin, xmax, ymin, ymax, zmin, zmax, boxscale, w, h = calc_dimensions() + xmin, xmax, ymin, ymax, zmin, zmax, boxscale = calc_dimensions() # TODO: Intelligently place the axes on the longest non-middle edge. # See algorithm used by web graphics in mathics/web/media/graphics.js diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 625e2afc2..6163ddfd1 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -62,6 +62,7 @@ class Style3D(Style): def get_default_face_color(self): return RGBColor(components=(1, 1, 1, 1)) + class Graphics3D(Graphics): r"""
From 9d86f347bf10f74cc5b27b231081e7519feb7fca Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 13 Sep 2021 15:15:10 -0400 Subject: [PATCH 112/193] Change 3d graphics protocol revision to 1.1 The new features of that protocol are: - cones - tubes --- mathics/builtin/box/graphics3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index 88a2cf277..19773df0c 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -382,7 +382,7 @@ def boxes_to_json(self, leaves=None, **options): }, "lighting": self.lighting, "viewpoint": self.viewpoint, - "protocol": "1.0", + "protocol": "1.1", } ) From 5926495a15fcce42df6bd54840858ad9950f4efc Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 13 Sep 2021 15:16:39 -0400 Subject: [PATCH 113/193] Add `Tube` --- mathics/builtin/box/graphics3d.py | 38 ++++++++++++++++++++++++++- mathics/builtin/drawing/graphics3d.py | 23 ++++++++++++++++ mathics/builtin/graphics.py | 1 + mathics/format/asy.py | 18 +++++++++++++ mathics/format/json.py | 18 +++++++++++++ 5 files changed, 97 insertions(+), 1 deletion(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index 19773df0c..abfd9794d 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -7,7 +7,7 @@ import json import numbers -from mathics.builtin.base import BoxConstructError +from mathics.builtin.base import BoxConstructError, InstanceableBuiltin from mathics.builtin.box.graphics import ( GraphicsBox, ArrowBox, @@ -882,6 +882,41 @@ def _apply_boxscaling(self, boxscale): pass +class Tube3DBox(InstanceableBuiltin): + def init(self, graphics, style, item): + self.edge_color, self.face_color = style.get_style(_Color, face_element=True) + + points = item.leaves[0].to_python() + if not all( + len(point) == 3 and all(isinstance(p, numbers.Real) for p in point) + for point in points + ): + raise BoxConstructError + + self.points = [Coords3D(graphics, pos=point) for point in points] + self.radius = item.leaves[1].to_python() + + def extent(self): + result = [] + result.extend( + [ + coords.add(self.radius, self.radius, self.radius).pos()[0] + for coords in self.points + ] + ) + result.extend( + [ + coords.add(-self.radius, -self.radius, -self.radius).pos()[0] + for coords in self.points + ] + ) + return result + + def _apply_boxscaling(self, boxscale): + # TODO + pass + + # FIXME: GLOBALS3D is a horrible name. GLOBALS3D.update( { @@ -892,5 +927,6 @@ def _apply_boxscaling(self, boxscale): "System`Point3DBox": Point3DBox, "System`Polygon3DBox": Polygon3DBox, "System`Sphere3DBox": Sphere3DBox, + "System`Tube3DBox": Tube3DBox, } ) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 0c54370e4..33b633a4b 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -317,3 +317,26 @@ def apply_check(self, positions, radius, evaluation): evaluation.error("Cylinder", "nrr", radius) return + + +class Tube(Builtin): + """ +
+
'Tube[{$p1$, $p2$, ...}]' +
represents a tube passing through $p1$, $p2$, ... with radius 1. + +
'Tube[{$p1$, $p2$, ...}, $r$]' +
represents a tube with radius $r$. +
+ + >> Graphics3D[Tube[{{0,0,0}, {1,1,1}}]] + = -Graphics3D- + + >> Graphics3D[Tube[{{0,0,0}, {1,1,1}, {0, 0, 1}}, 0.1]] + = -Graphics3D- + """ + + rules = { + "Tube[]": "Tube[{{0, 0, 0}, {1, 1, 1}}, 1]", + "Tube[positions_]": "Tube[positions, 1]", + } diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 94da7ca0c..cd6c9d49b 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -1387,6 +1387,7 @@ class Large(Builtin): "Sphere", "Style", "Text", + "Tube", "UniformPolyhedron", ) ) diff --git a/mathics/format/asy.py b/mathics/format/asy.py index 5e966dd45..1037eb08b 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -26,6 +26,7 @@ Point3DBox, Polygon3DBox, Sphere3DBox, + Tube3DBox, ) from mathics.builtin.graphics import ( @@ -594,3 +595,20 @@ def sphere3dbox(self, **options) -> str: add_conversion_fn(Sphere3DBox) + + +def tube3dbox(self, **options) -> str: + if self.face_color is None: + face_color = (1, 1, 1) + else: + face_color = self.face_color.to_js() + + asy = "// Tube3DBox\n draw(tube({0}, {1}), rgb({2},{3},{4}));".format( + "--".join("({0},{1},{2})".format(*coords.pos()[0]) for coords in self.points), + self.radius, + *face_color[:3], + ) + return asy + + +add_conversion_fn(Tube3DBox) diff --git a/mathics/format/json.py b/mathics/format/json.py index a1c734477..8116f83b5 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -17,6 +17,7 @@ Point3DBox, Polygon3DBox, Sphere3DBox, + Tube3DBox, ) from mathics.builtin.box.uniform_polyhedra import UniformPolyhedron3DBox @@ -220,3 +221,20 @@ def uniform_polyhedron_3d_box(self) -> list: add_conversion_fn(UniformPolyhedron3DBox, uniform_polyhedron_3d_box) + + +def tube_3d_box(self) -> list: + face_color = self.face_color + if face_color is not None: + face_color = face_color.to_js() + data = convert_coord_collection( + [self.points], + "tube", + face_color, + {"radius": self.radius}, + ) + # print("### json Tube3DBox", data) + return data + + +add_conversion_fn(Tube3DBox, tube_3d_box) From dbcfa62c869e39efa4931dee4cff3f113f787430 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 13 Sep 2021 15:18:29 -0400 Subject: [PATCH 114/193] Update CHANGES.rst New builtins: - Cone - Tube --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6471d6600..d8e306c53 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,6 +18,8 @@ New builtins ++++++++++++ * ``Guidermannian`` +* ``Cone`` +* ``Tube`` Tensor functions: From a9f9150e4e903c4a62250ecde71aba4dcdc50294 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 14 Sep 2021 08:58:22 -0400 Subject: [PATCH 115/193] Clear `Coords3D` --- mathics/builtin/box/graphics3d.py | 6 +++--- mathics/builtin/box/uniform_polyhedra.py | 2 +- mathics/builtin/drawing/graphics3d.py | 11 +++-------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index 6ab6da209..14e87478e 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -725,7 +725,7 @@ def init(self, graphics, style, item): ): raise BoxConstructError - self.points = tuple(Coords3D(graphics, pos=point) for point in points) + self.points = tuple(Coords3D(pos=point) for point in points) def extent(self): return [coords.pos()[0] for coords in self.points] @@ -753,7 +753,7 @@ def init(self, graphics, style, item): ): raise BoxConstructError - self.points = tuple(Coords3D(graphics, pos=point) for point in points) + self.points = tuple(Coords3D(pos=point) for point in points) self.radius = item.leaves[1].to_python() def extent(self): @@ -850,7 +850,7 @@ def init(self, graphics, style, item): ): raise BoxConstructError - self.points = tuple(Coords3D(graphics, pos=point) for point in points) + self.points = tuple(Coords3D(pos=point) for point in points) self.radius = item.leaves[1].to_python() def extent(self): diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py index ddc7a701b..1a0df962c 100644 --- a/mathics/builtin/box/uniform_polyhedra.py +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -23,7 +23,7 @@ def init(self, graphics, style, item): ): raise BoxConstructError - self.points = tuple(Coords3D(graphics, pos=point) for point in points) + self.points = tuple(Coords3D(pos=point) for point in points) self.edge_length = item.leaves[2].to_python() self.sub_type = item.leaves[0].to_python(string_quotes=False) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 6163ddfd1..3bf3876f2 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -33,26 +33,21 @@ def coords3D(value): class Coords3D(object): - def __init__(self, graphics, expr=None, pos=None, d=None): - self.graphics = graphics + def __init__(self, graphics=None, expr=None, pos=None): self.p = pos - self.d = d if expr is not None: if expr.has_form("Offset", 1, 2): - self.d = coords3D(expr.leaves[0]) if len(expr.leaves) > 1: self.p = coords3D(expr.leaves[1]) - else: - self.p = None else: self.p = coords3D(expr) def pos(self): - return self.p, self.d + return self.p, None def add(self, x, y, z): p = (self.p[0] + x, self.p[1] + y, self.p[2] + z) - return Coords3D(self.graphics, pos=p, d=self.d) + return Coords3D(pos=p) def scale(self, a): self.p = (self.p[0] * a[0], self.p[1] * a[1], self.p[2] * a[2]) From 35af6c3ea90025023fef19bdd0d46283655816db Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 14 Sep 2021 08:59:30 -0400 Subject: [PATCH 116/193] Stop checking whether 3d coordinates are instance of `Coords3D` --- mathics/format/asy.py | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/mathics/format/asy.py b/mathics/format/asy.py index 4cc9d91aa..9d057d6f2 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -19,7 +19,6 @@ from mathics.builtin.box.graphics3d import ( Graphics3DElements, Arrow3DBox, - Coords3D, Cuboid3DBox, Cylinder3DBox, Line3DBox, @@ -230,16 +229,8 @@ def cuboid3dbox(self, **options) -> str: i = 0 while i < len(self.points) / 2: try: - point1_obj = self.points[i * 2] - if isinstance(point1_obj, Coords3D): - point1 = point1_obj.pos()[0] - else: - point1 = point1_obj[0] - point2_obj = self.points[i * 2 + 1] - if isinstance(point2_obj, Coords3D): - point2 = point2_obj.pos()[0] - else: - point2 = point2_obj[0] + point1 = self.points[i * 2].pos()[0] + point2 = self.points[i * 2 + 1].pos()[0] asy += f""" draw(shift({point1[0]}, {point1[1]}, {point1[2]}) * scale( @@ -275,16 +266,8 @@ def cylinder3dbox(self, **options) -> str: i = 0 while i < len(self.points) / 2: try: - point1_obj = self.points[i * 2] - if isinstance(point1_obj, Coords3D): - point1 = point1_obj.pos()[0] - else: - point1 = point1_obj[0] - point2_obj = self.points[i * 2 + 1] - if isinstance(point2_obj, Coords3D): - point2 = point2_obj.pos()[0] - else: - point2 = point2_obj[0] + point1 = self.points[i * 2].pos()[0] + point2 = self.points[i * 2 + 1].pos()[0] # Compute distance between start point and end point. distance = ( From b0eb9935ed63bcff740e3e643aece83a52160f49 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 14 Sep 2021 09:14:20 -0400 Subject: [PATCH 117/193] Stop checking whether the colors are None --- mathics/format/asy.py | 15 +++------------ mathics/format/json.py | 17 ++++------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/mathics/format/asy.py b/mathics/format/asy.py index 9d057d6f2..cfa9d808e 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -217,10 +217,7 @@ def bezier_curve_box(self, **options) -> str: def cuboid3dbox(self, **options) -> str: - if self.face_color is None: - face_color = (1, 1, 1) - else: - face_color = self.face_color.to_js() + face_color = self.face_color.to_js() rgb = "rgb({0},{1},{1})".format(*face_color[:3]) @@ -253,10 +250,7 @@ def cuboid3dbox(self, **options) -> str: def cylinder3dbox(self, **options) -> str: - if self.face_color is None: - face_color = (1, 1, 1) - else: - face_color = self.face_color.to_js() + face_color = self.face_color.to_js() # FIXME: currently always drawing around the axis X+Y axes_point = (1, 1, 0) @@ -563,10 +557,7 @@ def _roundbox(self): def sphere3dbox(self, **options) -> str: # l = self.style.get_line_width(face_element=True) - if self.face_color is None: - face_color = (1, 1, 1) - else: - face_color = self.face_color.to_js() + face_color = self.face_color.to_js() return "// Sphere3DBox\n" + "\n".join( "draw(surface(sphere({0}, {1})), rgb({2},{3},{4}));".format( diff --git a/mathics/format/json.py b/mathics/format/json.py index a1c734477..1a6128b08 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -86,9 +86,7 @@ def cuboid_3d_box(self): """ Compact (lower-level) JSON formatting of a Cuboid3DBox. """ - face_color = self.face_color - if face_color is not None: - face_color = face_color.to_js() + face_color = self.face_color.to_js() data = convert_coord_collection( [self.points], "cuboid", @@ -105,9 +103,7 @@ def cylinder_3d_box(self): """ Compact (lower-level) JSON formatting of a Cylinder3DBox. """ - face_color = self.face_color - if face_color is not None: - face_color = face_color.to_js() + face_color = self.face_color.to_js() data = convert_coord_collection( [self.points], "cylinder", @@ -169,13 +165,10 @@ def polygon_3d_box(self) -> list: """ # TODO: account for line widths and style if self.vertex_colors is None: - face_color = self.face_color + face_color = self.face_color.to_js() else: face_color = None - if face_color is not None: - face_color = face_color.to_js() - data = convert_coord_collection( self.lines, "polygon", @@ -206,9 +199,7 @@ def sphere_3d_box(self) -> list: def uniform_polyhedron_3d_box(self) -> list: - face_color = self.face_color - if face_color is not None: - face_color = face_color.to_js() + face_color = self.face_color.to_js() data = convert_coord_collection( [self.points], "uniformPolyhedron", From 5232ce59fa9753ea9f17daacd2d5e4c776e756da Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 14 Sep 2021 09:29:06 -0400 Subject: [PATCH 118/193] Fix `StringSplit`'s missing whitespaces --- mathics/builtin/string/operations.py | 13 ++++++++++++- mathics/builtin/string/patterns.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/string/operations.py b/mathics/builtin/string/operations.py index 0b9b6bb87..07860789e 100644 --- a/mathics/builtin/string/operations.py +++ b/mathics/builtin/string/operations.py @@ -849,6 +849,9 @@ class StringSplit(Builtin): = {a, aBa} #> StringSplit["abaBa", "b", IgnoreCase -> True] = {a, a, a} + + #> StringSplit["13 a22 bbb", WhitespaceCharacter] + = {13, , a22, , , , bbb} """ rules = { @@ -899,7 +902,15 @@ def apply(self, string, patt, evaluation, options): result = [t for s in result for t in mathics_split(re_patt, s, flags=flags)] return string_list( - SymbolList, [String(x) for x in result if x != ""], evaluation + SymbolList, + [ + String(x) + for x in result + # Remove the empty matches only if we aren't splitting by + # whitespace because Python's RegEx matches " " as "" + if x != "" or patts[0].to_python() in ("", "System`WhitespaceCharacter") + ], + evaluation, ) diff --git a/mathics/builtin/string/patterns.py b/mathics/builtin/string/patterns.py index 5a7b811e5..b1cccc7f3 100644 --- a/mathics/builtin/string/patterns.py +++ b/mathics/builtin/string/patterns.py @@ -452,7 +452,7 @@ class WhitespaceCharacter(Builtin): = True >> StringSplit["a\nb\r\nc\rd", WhitespaceCharacter] - = {a, b, c, d} + = {a, b, , c, d} For sequences of whitespace characters use 'Whitespace': >> StringMatchQ[" \n", WhitespaceCharacter] From 6296bdf66f6ab60a20dda3ee25922a94c9b19023 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 13 Sep 2021 00:51:10 +0000 Subject: [PATCH 119/193] Add cone primitive --- mathics/builtin/box/graphics3d.py | 47 +++++++++++++++++++++++++++ mathics/builtin/drawing/graphics3d.py | 44 +++++++++++++++++++++++++ mathics/builtin/graphics.py | 1 + mathics/format/asy.py | 47 +++++++++++++++++++++++++++ mathics/format/json.py | 21 ++++++++++++ 5 files changed, 160 insertions(+) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index 14e87478e..e166ebeab 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -707,6 +707,52 @@ def _apply_boxscaling(self, boxscale): coords.scale(boxscale) +class Cone3DBox(_Graphics3DElement): + """ + Internal Python class used when Boxing a 'Cone' object. + """ + + def init(self, graphics, style, item): + super(Cone3DBox, self).init(graphics, item, style) + + self.edge_color, self.face_color = style.get_style(_Color, face_element=True) + + if len(item.leaves) != 2: + raise BoxConstructError + + points = item.leaves[0].to_python() + if not all( + len(point) == 3 and all(isinstance(p, numbers.Real) for p in point) + for point in points + ): + raise BoxConstructError + + self.points = [Coords3D(graphics, pos=point) for point in points] + self.radius = item.leaves[1].to_python() + + def extent(self): + result = [] + # FIXME: the extent is roughly wrong. It is using the extent of a shpere at each coordinate. + # Anyway, it is very difficult to calculate the extent of a cone. + result.extend( + [ + coords.add(self.radius, self.radius, self.radius).pos()[0] + for coords in self.points + ] + ) + result.extend( + [ + coords.add(-self.radius, -self.radius, -self.radius).pos()[0] + for coords in self.points + ] + ) + return result + + def _apply_boxscaling(self, boxscale): + # TODO + pass + + class Cuboid3DBox(InstanceableBuiltin): """ Internal Python class used when Boxing a 'Cuboid' object. @@ -878,6 +924,7 @@ def _apply_boxscaling(self, boxscale): GLOBALS3D.update( { "System`Arrow3DBox": Arrow3DBox, + "System`Cone3DBox": Cone3DBox, "System`Cuboid3DBox": Cuboid3DBox, "System`Cylinder3DBox": Cylinder3DBox, "System`Line3DBox": Line3DBox, diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 3bf3876f2..fbf9d48fc 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -205,6 +205,50 @@ class Sphere(Builtin): } +class Cone(Builtin): + """ +
+
'Cone[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}]' +
represents a cone of radius 1. + +
'Cone[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}, $r$]' +
is a cone of radius $r$ starting at ($x1$, $y1$, $z1$) and ending at ($x2$, $y2$, $z2$). + +
'Cone[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}, ... }, $r$]' +
is a collection cones of radius $r$. +
+ + >> Graphics3D[Cone[{{0, 0, 0}, {1, 1, 1}}, 1]] + = -Graphics3D- + + >> Graphics3D[{Yellow, Cone[{{-1, 0, 0}, {1, 0, 0}, {0, 0, Sqrt[3]}, {1, 1, Sqrt[3]}}, 1]}] + = -Graphics3D- + """ + + messages = { + "oddn": "The number of points must be even.", + "nrr": "The radius must be a real number", + } + + rules = { + "Cone[]": "Cone[{{0, 0, 0}, {1, 1, 1}}, 1]", + "Cone[positions_List]": "Cone[positions, 1]", + } + + def apply_check(self, positions, radius, evaluation): + "Cone[positions_List, radius_]" + + if len(positions.get_leaves()) % 2 == 1: + # The number of points is odd, so abort. + evaluation.error("Cone", "oddn", positions) + if not isinstance(radius, (Integer, Rational, Real)): + nradius = Expression(SymbolN, radius).evaluate(evaluation) + if not isinstance(nradius, (Integer, Rational, Real)): + evaluation.error("Cone", "nrr", radius) + + return + + class Cuboid(Builtin): """ Cuboid also known as interval, rectangle, square, cube, rectangular parallelepiped, tesseract, orthotope, and box. diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 94da7ca0c..c03df681f 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -1374,6 +1374,7 @@ class Large(Builtin): "Arrow", "BezierCurve", "Circle", + "Cone", "Cuboid", "Cylinder", "Disk", diff --git a/mathics/format/asy.py b/mathics/format/asy.py index cfa9d808e..98fb26504 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -19,6 +19,7 @@ from mathics.builtin.box.graphics3d import ( Graphics3DElements, Arrow3DBox, + Cone3DBox, Cuboid3DBox, Cylinder3DBox, Line3DBox, @@ -216,6 +217,52 @@ def bezier_curve_box(self, **options) -> str: add_conversion_fn(BezierCurveBox, bezier_curve_box) +def cone3dbox(self, **options) -> str: + if self.face_color is None: + face_color = (1, 1, 1) + else: + face_color = self.face_color.to_js() + + # FIXME: currently always drawing around the axis X+Y + axes_point = (1, 1, 0) + rgb = "rgb({0},{1},{1})".format(*face_color[:3]) + + asy = "// Cone3DBox\n" + i = 0 + while i < len(self.points) / 2: + try: + point1_obj = self.points[i * 2] + if isinstance(point1_obj, Coords3D): + point1 = point1_obj.pos()[0] + point2_obj = self.points[i * 2 + 1] + if isinstance(point2_obj, Coords3D): + point2 = point2_obj.pos()[0] + else: + point2 = point2_obj[0] + + # Compute distance between start point and end point. + distance = ( + (point1[0] - point2[0]) ** 2 + + (point1[1] - point2[1]) ** 2 + + (point1[2] - point2[2]) ** 2 + ) ** 0.5 + + asy += ( + f"draw(surface(cone({tuple(point1)}, {self.radius}, {distance}, {axes_point})), {rgb});" + + "\n" + ) + except: # noqa + pass + + i += 1 + + # print(asy) + return asy + + +add_conversion_fn(Cone3DBox) + + def cuboid3dbox(self, **options) -> str: face_color = self.face_color.to_js() diff --git a/mathics/format/json.py b/mathics/format/json.py index 1a6128b08..44f5847b5 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -11,6 +11,7 @@ from mathics.builtin.box.graphics3d import ( Arrow3DBox, + Cone3DBox, Cuboid3DBox, Cylinder3DBox, Line3DBox, @@ -82,6 +83,26 @@ def arrow_3d_box(self): add_conversion_fn(Arrow3DBox, arrow_3d_box) +def cone_3d_box(self): + """ + Compact (lower-level) JSON formatting of a Cone3DBox. + """ + face_color = self.face_color + if face_color is not None: + face_color = face_color.to_js() + data = convert_coord_collection( + [self.points], + "cone", + face_color, + {"radius": self.radius}, + ) + # print("### json Cone3DBox", data) + return data + + +add_conversion_fn(Cone3DBox, cone_3d_box) + + def cuboid_3d_box(self): """ Compact (lower-level) JSON formatting of a Cuboid3DBox. From 5227977df74bc8b512c53777e41435ac664b1a45 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 13 Sep 2021 07:24:17 -0400 Subject: [PATCH 120/193] Remove checking whether the coordinates are `Coords3D` --- mathics/format/asy.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/mathics/format/asy.py b/mathics/format/asy.py index 98fb26504..be9cd04d5 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -231,14 +231,8 @@ def cone3dbox(self, **options) -> str: i = 0 while i < len(self.points) / 2: try: - point1_obj = self.points[i * 2] - if isinstance(point1_obj, Coords3D): - point1 = point1_obj.pos()[0] - point2_obj = self.points[i * 2 + 1] - if isinstance(point2_obj, Coords3D): - point2 = point2_obj.pos()[0] - else: - point2 = point2_obj[0] + point1 = self.points[i * 2].pos()[0] + point2 = self.points[i * 2 + 1].pos()[0] # Compute distance between start point and end point. distance = ( From 7afd4156e28054b9755d68ab3dadd8dd50b1b3c2 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 13 Sep 2021 07:57:13 -0400 Subject: [PATCH 121/193] Improve the performance according to `improve-graphics3d-performance` --- mathics/builtin/box/graphics3d.py | 6 ++---- mathics/format/asy.py | 5 +---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index e166ebeab..85ff4873c 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -707,14 +707,12 @@ def _apply_boxscaling(self, boxscale): coords.scale(boxscale) -class Cone3DBox(_Graphics3DElement): +class Cone3DBox(InstanceableBuiltin): """ Internal Python class used when Boxing a 'Cone' object. """ def init(self, graphics, style, item): - super(Cone3DBox, self).init(graphics, item, style) - self.edge_color, self.face_color = style.get_style(_Color, face_element=True) if len(item.leaves) != 2: @@ -727,7 +725,7 @@ def init(self, graphics, style, item): ): raise BoxConstructError - self.points = [Coords3D(graphics, pos=point) for point in points] + self.points = tuple(Coords3D(graphics, pos=point) for point in points) self.radius = item.leaves[1].to_python() def extent(self): diff --git a/mathics/format/asy.py b/mathics/format/asy.py index be9cd04d5..0aea41502 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -218,10 +218,7 @@ def bezier_curve_box(self, **options) -> str: def cone3dbox(self, **options) -> str: - if self.face_color is None: - face_color = (1, 1, 1) - else: - face_color = self.face_color.to_js() + face_color = self.face_color.to_js() # FIXME: currently always drawing around the axis X+Y axes_point = (1, 1, 0) From b3c8b258e3e9a45873b4aabb63dd93e208f2fc62 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 16 Sep 2021 08:31:52 -0400 Subject: [PATCH 122/193] Add WL tests, revise doc We should not be using #> except to test error messages. Instead, these should be moved to pytest where they run faster. Revise documentation for StringSplit. Note that carriage returns are hard returns, not wrapped. They will appear in docs. Describe more about why we are showing the examples shown. --- mathics/builtin/string/operations.py | 42 +++++++++++++------------- test/test_strings.py | 44 ++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/mathics/builtin/string/operations.py b/mathics/builtin/string/operations.py index 07860789e..4fffae493 100644 --- a/mathics/builtin/string/operations.py +++ b/mathics/builtin/string/operations.py @@ -801,26 +801,30 @@ def apply(self, liststr, seps, evaluation): class StringSplit(Builtin): """
-
'StringSplit["$s$"]' -
splits the string $s$ at whitespace, discarding the - whitespace and returning a list of strings. -
'StringSplit["$s$", "$d$"]' -
splits $s$ at the delimiter $d$. -
'StringSplit[$s$, {"$d1$", "$d2$", ...}]' -
splits $s$ using multiple delimiters. -
'StringSplit[{$s_1$, $s_2, ...}, {"$d1$", "$d2$", ...}]' -
returns a list with the result of applying the function to - each element. +
'StringSplit[$s$]' +
splits the string $s$ at whitespace, discarding the whitespace and returning a list of strings. + +
'StringSplit[$s$, "$pattern$"]' +
splits $s$ into substrings separated by delimiters matching the string expression $pattern$. + +
'StringSplit[$s$, {$p_1$, $p_2$, ...}]' +
splits $s$ at any of the $p_i$ patterns. + +
'StringSplit[{$s_1$, $s_2$, ...}, {$d_1$, $d_2$, ...}]' +
returns a list with the result of applying the function to each element.
+ >> StringSplit["abc,123", ","] = {abc, 123} - >> StringSplit["abc 123"] + By default any number of whitespace characters are used to at a delimiter: + >> StringSplit[" abc 123 "] = {abc, 123} - #> StringSplit[" abc 123 "] - = {abc, 123} + However if you want instead to use only a single character for each delimiter, use 'WhiteSpaceCharacter': + >> StringSplit[" abc 123 ", WhitespaceCharacter] + = {, , abc, , , , 123, , } >> StringSplit["abc,123.456", {",", "."}] = {abc, 123, 456} @@ -831,7 +835,7 @@ class StringSplit(Builtin): >> StringSplit[{"a b", "c d"}, RegularExpression[" +"]] = {{a, b}, {c, d}} - #> StringSplit["x", "x"] + >> StringSplit["x", "x"] = {} #> StringSplit[x] @@ -842,16 +846,10 @@ class StringSplit(Builtin): : Element x is not a valid string or pattern element in x. = StringSplit[x, x] - #> StringSplit["12312123", "12"..] + Split using a delmiter that has nonzero list of 12's + >> StringSplit["12312123", "12"..] = {3, 3} - #> StringSplit["abaBa", "b"] - = {a, aBa} - #> StringSplit["abaBa", "b", IgnoreCase -> True] - = {a, a, a} - - #> StringSplit["13 a22 bbb", WhitespaceCharacter] - = {13, , a22, , , , bbb} """ rules = { diff --git a/test/test_strings.py b/test/test_strings.py index 4aa0cc202..18820ada6 100644 --- a/test/test_strings.py +++ b/test/test_strings.py @@ -26,3 +26,47 @@ def test_digitq(): ("DigitQ[a=1]", "False"), ): check_evaluation(str_expr, str_expected) + + +def test_string_split(): + for str_expr, str_expected in ( + ('StringSplit["a bbb cccc aa d"]', "{a, bbb, cccc, aa, d}"), + ('StringSplit["a--bbb---ccc--dddd", "--"]', "{a, bbb, -ccc, dddd}"), + ('StringSplit["the cat in the hat"]', "{the, cat, in, the, hat}"), + ('StringSplit["192.168.0.1", "."]', "{192, 168, 0, 1}"), + ('StringSplit["123 2.3 4 6", WhitespaceCharacter ..]', "{123, 2.3, 4, 6}"), + ( + 'StringSplit[StringSplit["11:12:13//21:22:23//31:32:33", "//"], ":"]', + "{{11, 12, 13}, {21, 22, 23}, {31, 32, 33}}", + ), + ( + 'StringSplit["A tree, an apple, four pears. And more: two sacks", RegularExpression["\\W+"]]', + "{A, tree, an, apple, four, pears, And, more, two, sacks}", + ), + ( + 'StringSplit["primes: 2 two 3 three 5 five ...", Whitespace ~~ RegularExpression["\\d"] ~~ Whitespace]', + "{primes:, two, three, five ...}", + ), + ('StringSplit["a-b:c-d:e-f-g", {":", "-"}]', "{a, b, c, d, e, f, g}"), + ('StringSplit["a-b:c-d:e-f-g", ":" | "-"]', "{a, b, c, d, e, f, g}"), + ( + 'StringSplit[{"a:b:c:d", "listable:element"}, ":"]', + "{{a, b, c, d}, {listable, element}})", + ), + ( + 'StringSplit["cat Cat hat CAT", "c", IgnoreCase -> True]', + "{at , at hat , AT}", + ), + ( + 'StringSplit["This is a sentence, which goes on.", Except[WordCharacter] ..]', + "{This, is, a, sentence, which, goes, on}", + ) + # # FIXME: these forms are not implemented yet: + # ('StringSplit["11a22b3", _?LetterQ]', '{11, 22, 3}'), + # ('StringSplit["a b::c d::e f g", "::" -> "--"]'), '{a, b, --, c d, --, e f g}'), + # ('StringSplit["a--b c--d e", x : "--" :> x]', {a, --, b c, --, d e}), + # ('StringSplit[":a:b:c:", ":", All]', '{"", "a", "b", "c", ""}'), + ): + check_evaluation( + str_expr, str_expected, to_string_expr=False, to_string_expected=False + ) From 87d42bec1023e0bc63963c64dab0dc5a87f71a65 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 16 Sep 2021 08:38:57 -0400 Subject: [PATCH 123/193] Add summary text for more strings operations --- mathics/builtin/string/operations.py | 39 ++++++++++++++++++---------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/mathics/builtin/string/operations.py b/mathics/builtin/string/operations.py index 4fffae493..0407edacd 100644 --- a/mathics/builtin/string/operations.py +++ b/mathics/builtin/string/operations.py @@ -37,14 +37,17 @@ class StringDrop(Builtin): """
-
'StringDrop["$string$", $n$]' -
gives $string$ with the first $n$ characters dropped. -
'StringDrop["$string$", -$n$]' -
gives $string$ with the last $n$ characters dropped. -
'StringDrop["$string$", {$n$}]' -
gives $string$ with the $n$th character dropped. -
'StringDrop["$string$", {$m$, $n$}]' -
gives $string$ with the characters $m$ through $n$ dropped. +
'StringDrop["$string$", $n$]' +
gives $string$ with the first $n$ characters dropped. + +
'StringDrop["$string$", -$n$]' +
gives $string$ with the last $n$ characters dropped. + +
'StringDrop["$string$", {$n$}]' +
gives $string$ with the $n$th character dropped. + +
'StringDrop["$string$", {$m$, $n$}]' +
gives $string$ with the characters $m$ through $n$ dropped.
>> StringDrop["abcde", 2] @@ -331,9 +334,11 @@ class StringJoin(BinaryOperator): | Hello world! """ + attributes = ("Flat", "OneIdentity") operator = "<>" precedence = 600 - attributes = ("Flat", "OneIdentity") + + summary_text = "join strings together" def apply(self, items, evaluation): "StringJoin[items___]" @@ -355,8 +360,8 @@ def apply(self, items, evaluation): class StringLength(Builtin): """
-
'StringLength["$string$"]' -
gives the length of $string$. +
'StringLength["$string$"]' +
gives the length of $string$.
>> StringLength["abc"] @@ -372,6 +377,8 @@ class StringLength(Builtin): attributes = ("Listable",) + summary_text = "length of a string (in Unicode characters)" + def apply(self, str, evaluation): "StringLength[str_]" @@ -742,6 +749,8 @@ class StringRiffle(Builtin): "mulsep": "Multiple separators form is not implemented yet.", } + summary_text = "assemble a string from a list, inserting delimiters" + def apply(self, liststr, seps, evaluation): "StringRiffle[liststr_, seps___]" separators = seps.get_sequence() @@ -866,6 +875,8 @@ class StringSplit(Builtin): "pysplit": "As of Python 3.5 re.split does not handle empty pattern matches.", } + summary_text = "split strings at whitespace, or at a pattern" + def apply(self, string, patt, evaluation, options): "StringSplit[string_, patt_, OptionsPattern[%(name)s]]" @@ -1023,8 +1034,8 @@ def apply_strings(self, strings, spec, evaluation): class StringTrim(Builtin): """
-
'StringTrim[$s$]' -
returns a version of $s$ with whitespace removed from start and end. +
'StringTrim[$s$]' +
returns a version of $s$ with whitespace removed from start and end.
>> StringJoin["a", StringTrim[" \\tb\\n "], "c"] @@ -1034,6 +1045,8 @@ class StringTrim(Builtin): = axababya """ + summary_text = "trim whitespace etc. from strings" + def apply(self, s, evaluation): "StringTrim[s_String]" return String(s.get_string_value().strip(" \t\n")) From 87fa63befa91caf5cca457baad9b0b40cb464668 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 16 Sep 2021 09:03:10 -0400 Subject: [PATCH 124/193] One more quote removal --- mathics/builtin/string/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/string/operations.py b/mathics/builtin/string/operations.py index 0407edacd..7abe64804 100644 --- a/mathics/builtin/string/operations.py +++ b/mathics/builtin/string/operations.py @@ -813,7 +813,7 @@ class StringSplit(Builtin):
'StringSplit[$s$]'
splits the string $s$ at whitespace, discarding the whitespace and returning a list of strings. -
'StringSplit[$s$, "$pattern$"]' +
'StringSplit[$s$, $pattern$]'
splits $s$ into substrings separated by delimiters matching the string expression $pattern$.
'StringSplit[$s$, {$p_1$, $p_2$, ...}]' From dd0898b88ba660a546c97ac57a78003b9ddecfe8 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 16 Sep 2021 09:36:48 -0400 Subject: [PATCH 125/193] Add clean-cython target --- Makefile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index a2aa30bd1..1a10fae8e 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ PIP ?= pip3 RM ?= rm .PHONY: all build \ - check clean \ + check clean clean-cython \ develop dist doctest doc-data djangotest \ gstest pytest \ rmChangeLog \ @@ -53,9 +53,12 @@ install: check: pytest gstest doctest +#: Remove Cython-derived files +clean-cython: + find mathics -name "*.so" -type f -delete + #: Remove derived files -clean: - rm mathics/*/*.so; \ +clean: clean-cython for dir in mathics/doc ; do \ ($(MAKE) -C "$$dir" clean); \ done; \ From ed3cdd22f9b7986b85f3b7e885658366c4c11fd7 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 16 Sep 2021 09:52:53 -0400 Subject: [PATCH 126/193] Also remove .c files in clean Cython Thanks mmatera! --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1a10fae8e..fcbb5f08f 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,8 @@ check: pytest gstest doctest #: Remove Cython-derived files clean-cython: - find mathics -name "*.so" -type f -delete + find mathics -name "*.so" -type f -delete; \ + find mathics -name "*.c" -type f -delete #: Remove derived files clean: clean-cython From 0f0973ae398b91937281590d3e8460d5e6aa0de2 Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 17 Sep 2021 00:51:54 -0400 Subject: [PATCH 127/193] Use Builtin class summary_text in `?` Recenty we have been filling out `summary_text` for Built-in Functions. This text currently appears in Django when it is listed in a group. See for example "Operations on Strings". This PR expands that use so that this short text also appears when `?xxx` or Information[xxx, LongForm->False]` is requested. --- mathics/builtin/assignment.py | 9 ++++++--- test/test_assignment.py | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mathics/builtin/assignment.py b/mathics/builtin/assignment.py index 668b82274..f8579f2df 100644 --- a/mathics/builtin/assignment.py +++ b/mathics/builtin/assignment.py @@ -975,7 +975,7 @@ def format_definition_input(self, symbol, evaluation): return self.format_definition(symbol, evaluation, grid=False) -def _get_usage_string(symbol, evaluation, htmlout=False): +def _get_usage_string(symbol, evaluation, is_long_form: bool, htmlout=False): """ Returns a python string with the documentation associated to a given symbol. """ @@ -1000,6 +1000,8 @@ def _get_usage_string(symbol, evaluation, htmlout=False): bio = builtins.get(definition.name) if bio is not None: + if not is_long_form and hasattr(bio.builtin.__class__, "summary_text"): + return bio.builtin.__class__.summary_text from mathics.doc.common_doc import XMLDoc docstr = bio.builtin.__class__.__doc__ @@ -1063,11 +1065,12 @@ def format_definition(self, symbol, evaluation, options, grid=True): evaluation.message("Information", "notfound", symbol) return ret # Print the "usage" message if available. - usagetext = _get_usage_string(symbol, evaluation) + is_long_form = self.get_option(options, "LongForm", evaluation).to_python() + usagetext = _get_usage_string(symbol, evaluation, is_long_form) if usagetext is not None: lines.append(usagetext) - if self.get_option(options, "LongForm", evaluation).to_python(): + if is_long_form: self.show_definitions(symbol, evaluation, lines) if grid: diff --git a/test/test_assignment.py b/test/test_assignment.py index 52efddf60..a3240d51f 100644 --- a/test/test_assignment.py +++ b/test/test_assignment.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- from .helper import check_evaluation -import pytest from mathics_scanner.errors import IncompleteSyntaxError From 2f65192ccbf908bf18137a8c9e53a37be7b877e3 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Fri, 17 Sep 2021 14:02:29 -0400 Subject: [PATCH 128/193] Add tests for `?` and `??` --- test/test_help.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 test/test_help.py diff --git a/test/test_help.py b/test/test_help.py new file mode 100755 index 000000000..96b6b2bc1 --- /dev/null +++ b/test/test_help.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from .helper import check_evaluation, evaluate +from mathics.builtin.base import Builtin +from mathics.core.expression import Integer0 + + +class Builtin1(Builtin): + summary_text = "short description" + + +class Builtin2(Builtin): + "long description" + + +def test_short_description(): + check_evaluation("?Builtin1", "Null", "short description") + + +def test_long_description(): + check_evaluation("??Builtin2", "Null", "long description") From 1747dc81ce6fbba9bb6b1134b501d52703476e04 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sun, 19 Sep 2021 19:02:19 -0400 Subject: [PATCH 129/193] Use `apply_N` more intensively Replace `Expression("N", ...).evaluate(...)` and `Expression(SymbolN, ...).evaluate(...)` by `apply_N(...)` everywhere it's possible. This approach is faster. --- mathics/builtin/arithfns/basic.py | 5 +++-- mathics/builtin/arithmetic.py | 3 +-- mathics/builtin/comparison.py | 4 ++-- mathics/builtin/compilation.py | 3 ++- mathics/builtin/drawing/graphics3d.py | 3 ++- mathics/builtin/drawing/plot.py | 4 ++-- mathics/builtin/graphics.py | 17 ++++++----------- mathics/builtin/lists.py | 8 +++----- mathics/builtin/numeric.py | 14 ++++++-------- mathics/builtin/patterns.py | 4 ++-- 10 files changed, 29 insertions(+), 36 deletions(-) diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index c7c0b78e9..10148008b 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -33,7 +33,6 @@ SymbolComplexInfinity, SymbolDirectedInfinity, SymbolInfinity, - SymbolN, SymbolNull, SymbolSequence, from_mpmath, @@ -42,6 +41,8 @@ from mathics.core.convert import from_sympy +from mathics.builtin.numeric import apply_N + class CubeRoot(Builtin): """ @@ -553,7 +554,7 @@ def apply_check(self, x, y, evaluation): if isinstance(y, Number): y_err = y else: - y_err = Expression(SymbolN, y).evaluate(evaluation) + y_err = apply_N(y, evaluation) if isinstance(y_err, Number): py_y = y_err.round_to_float(permit_complex=True).real if py_y > 0: diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index bcd495cde..b3c9fddd1 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -33,7 +33,6 @@ Real, String, Symbol, - SymbolN, SymbolTrue, SymbolFalse, SymbolUndefined, @@ -632,7 +631,7 @@ def apply(self, expr, evaluation): result = _iszero(exprexp) if result is None: # Can't get exact answer, so try approximate equal - numeric_val = Expression(SymbolN, expr).evaluate(evaluation) + numeric_val = apply_N(expr, evaluation) if numeric_val and hasattr(numeric_val, "is_approx_zero"): result = numeric_val.is_approx_zero elif ( diff --git a/mathics/builtin/comparison.py b/mathics/builtin/comparison.py index 219f3af0d..dbbd5bd88 100644 --- a/mathics/builtin/comparison.py +++ b/mathics/builtin/comparison.py @@ -13,6 +13,7 @@ SympyFunction, ) +from mathics.builtin.numeric import apply_N from mathics.builtin.numbers.constants import mp_convert_constant from mathics.core.expression import ( @@ -210,8 +211,7 @@ def numerify_args(items, evaluation): for item in items: if not isinstance(item, Number): # TODO: use $MaxExtraPrecision insterad of hard-coded 50 - n_expr = Expression("N", item, Integer(50)) - item = n_expr.evaluate(evaluation) + item = apply_N(item, evaluation, Integer(50)) n_items.append(item) items = n_items else: diff --git a/mathics/builtin/compilation.py b/mathics/builtin/compilation.py index 016884767..f5d59b476 100644 --- a/mathics/builtin/compilation.py +++ b/mathics/builtin/compilation.py @@ -13,6 +13,7 @@ from mathics.builtin.base import Builtin from mathics.builtin.box.compilation import CompiledCodeBox +from mathics.builtin.numeric import apply_N from mathics.core.evaluation import Evaluation from mathics.core.expression import ( Atom, @@ -148,7 +149,7 @@ def _pythonized_mathics_expr(*x): inner_evaluation = Evaluation(definitions=evaluation.definitions) vars = dict(list(zip(names, x[: len(names)]))) pyexpr = expr.replace_vars(vars) - pyexpr = Expression("N", pyexpr).evaluate(inner_evaluation) + pyexpr = apply_N(pyexpr, inner_evaluation) res = pyexpr.to_python(n_evaluation=inner_evaluation) return res diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 3bf3876f2..2526e5fed 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -18,6 +18,7 @@ Style, ) from mathics.builtin.lists import List +from mathics.builtin.numeric import apply_N def coords3D(value): @@ -294,7 +295,7 @@ def apply_check(self, positions, radius, evaluation): # The number of points is odd, so abort. evaluation.error("Cylinder", "oddn", positions) if not isinstance(radius, (Integer, Rational, Real)): - nradius = Expression(SymbolN, radius).evaluate(evaluation) + nradius = apply_N(radius, evaluation) if not isinstance(nradius, (Integer, Rational, Real)): evaluation.error("Cylinder", "nrr", radius) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index ca25612a8..01cefac9b 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -29,7 +29,7 @@ from mathics.builtin.base import Builtin from mathics.builtin.graphics import Graphics from mathics.builtin.drawing.graphics3d import Graphics3D -from mathics.builtin.numeric import chop +from mathics.builtin.numeric import chop, apply_N from mathics.builtin.options import options_to_rules from mathics.builtin.scoping import dynamic_scoping @@ -907,7 +907,7 @@ def _draw(self, data, color, evaluation, options): sector_origin = self.get_option(options, "SectorOrigin", evaluation) if not sector_origin.has_form("List", 2): return - sector_origin = Expression(SymbolN, sector_origin).evaluate(evaluation) + sector_origin = apply_N(sector_origin, evaluation) orientation = sector_origin.leaves[0] if ( diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 94da7ca0c..2f482db76 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -15,6 +15,8 @@ BoxConstructError, ) +from mathics.builtin.numeric import apply_N + from mathics.builtin.drawing.graphics_internals import ( _GraphicsElement, GLOBALS, @@ -40,7 +42,6 @@ Real, Symbol, SymbolList, - SymbolN, SymbolMakeBoxes, system_symbols, system_symbols_dict, @@ -224,9 +225,7 @@ def apply(self, graphics, evaluation, options): for option in options: if option not in ("System`ImageSize",): - options[option] = Expression(SymbolN, options[option]).evaluate( - evaluation - ) + options[option] = apply_N(options[option], evaluation) # The below could probably be done with graphics.filter.. new_leaves = [] @@ -327,13 +326,11 @@ def convert(content): inset._leaves[0], evaluation, opts ) n_leaves = [inset] + [ - Expression(SymbolN, leaf).evaluate(evaluation) - for leaf in content.leaves[1:] + apply_N(leaf, evaluation) for leaf in content.leaves[1:] ] else: n_leaves = ( - Expression(SymbolN, leaf).evaluate(evaluation) - for leaf in content.leaves + apply_N(leaf, evaluation) for leaf in content.leaves ) else: n_leaves = content.leaves @@ -342,9 +339,7 @@ def convert(content): for option in options: if option not in ("System`ImageSize",): - options[option] = Expression(SymbolN, options[option]).evaluate( - evaluation - ) + options[option] = apply_N(options[option], evaluation) from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index a468ce2a8..d229c7c99 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -41,6 +41,7 @@ from mathics.builtin.numbers.algebra import cancel from mathics.builtin.options import options_to_rules from mathics.builtin.scoping import dynamic_scoping +from mathics.builtin.numeric import apply_N from mathics.core.convert import from_sympy from mathics.core.evaluation import BreakInterrupt, ContinueInterrupt, ReturnInterrupt @@ -57,7 +58,6 @@ SymbolFailed, SymbolList, SymbolMakeBoxes, - SymbolN, SymbolRule, SymbolSequence, from_python, @@ -2493,9 +2493,7 @@ class _PrecomputedDistances(PrecomputedDistances): def __init__(self, df, p, evaluation): distances_form = [df(p[i], p[j]) for i in range(len(p)) for j in range(i)] - distances = Expression( - SymbolN, Expression(SymbolList, *distances_form) - ).evaluate(evaluation) + distances = apply_N(Expression(SymbolList, *distances_form), evaluation) mpmath_distances = [_to_real_distance(d) for d in distances.leaves] super(_PrecomputedDistances, self).__init__(mpmath_distances) @@ -2511,7 +2509,7 @@ def __init__(self, df, p, evaluation): def _compute_distance(self, i, j): p = self._p - d = Expression(SymbolN, self._df(p[i], p[j])).evaluate(self._evaluation) + d = apply_N(self._df(p[i], p[j]), self._evaluation) return _to_real_distance(d) diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 2bdae465e..4e74af778 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -883,7 +883,7 @@ def apply_with_prec(self, expr, prec, evaluation): ) if result is not None: if not result.sameQ(nexpr): - result = Expression(SymbolN, result, prec).evaluate(evaluation) + result = apply_N(result, evaluation, prec) return result if expr.is_atom(): @@ -901,12 +901,10 @@ def apply_with_prec(self, expr, prec, evaluation): eval_range = () else: eval_range = range(len(expr.leaves)) - head = Expression(SymbolN, expr.head, prec).evaluate(evaluation) + head = apply_N(expr.head, evaluation, prec) leaves = expr.get_mutable_leaves() for index in eval_range: - leaves[index] = Expression(SymbolN, leaves[index], prec).evaluate( - evaluation - ) + leaves[index] = apply_N(leaves[index], evaluation, prec) return Expression(head, *leaves) @@ -1151,8 +1149,8 @@ def apply_with_func_domain(self, func, domain, evaluation, options): (lambda u: a - z + z / u, lambda u: z * u ** (-2.0)) ) elif a.is_numeric(evaluation) and b.is_numeric(evaluation): - a = Expression(SymbolN, a).evaluate(evaluation).value - b = Expression(SymbolN, b).evaluate(evaluation).value + a = apply_N(a, evaluation).value + b = apply_N(b, evaluation).value subdomain2.append([a, b]) coordtransform.append(None) else: @@ -1662,7 +1660,7 @@ def apply_with_base(self, n, b, evaluation, nr_elements=None, pos=None): ).evaluate(evaluation) else: if rational_no: - n = Expression(SymbolN, n).evaluate(evaluation) + n = apply_N(n, evaluation) else: return evaluation.message("RealDigits", "ndig", expr) py_n = abs(n.value) diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 4b7563173..4c04dfb5c 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -41,6 +41,7 @@ from mathics.builtin.base import Builtin, BinaryOperator, PostfixOperator, AtomBuiltin from mathics.builtin.base import PatternObject, PatternError from mathics.builtin.lists import python_levelspec, InvalidLevelspecError +from mathics.builtin.numeric import apply_N from mathics.core.expression import ( Atom, @@ -53,7 +54,6 @@ Real, SymbolFalse, SymbolList, - SymbolN, SymbolTrue, ) from mathics.core.rules import Rule @@ -487,7 +487,7 @@ def quick_pattern_test(self, candidate, test, evaluation): elif test == "System`RealNumberQ": if isinstance(candidate, (Integer, Rational, Real)): return True - candidate = Expression(SymbolN, candidate).evaluate(evaluation) + candidate = apply_N(candidate, evaluation) return isinstance(candidate, Real) # pass elif test == "System`Positive": From 4953aff9190e74ab732cd74fd82e3814be02a901 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 11:24:13 -0400 Subject: [PATCH 130/193] Add more type annotations --- mathics/builtin/graphics.py | 2 +- mathics/core/formatter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 2f482db76..2df191422 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -586,7 +586,7 @@ def do_init(self, graphics, points): [graphics.coords(graphics, point) for point in line] for line in lines ] - def extent(self): + def extent(self) -> list: l = self.style.get_line_width(face_element=False) result = [] for line in self.lines: diff --git a/mathics/core/formatter.py b/mathics/core/formatter.py index ad73a4dd3..715693ab3 100644 --- a/mathics/core/formatter.py +++ b/mathics/core/formatter.py @@ -2,7 +2,7 @@ from typing import Callable # key is str: (to_xxx name, value) is formatter function to call -format2fn = {} +format2fn: dict = {} def lookup_method(self, format: str, module_fn_name=None) -> Callable: From 806835dbac718e323c6a057fb2d55fcf4d75e471 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 11:28:08 -0400 Subject: [PATCH 131/193] Set syntax tweaks set([]) => set() set([...]) -> {...} {**{...}} -> {...} --- mathics/builtin/drawing/plot.py | 6 +++--- mathics/builtin/files_io/importexport.py | 4 ++-- mathics/builtin/graphics.py | 16 +++++++++------- mathics/builtin/recurrence.py | 2 +- mathics/builtin/scoping.py | 2 +- mathics/builtin/system.py | 2 +- mathics/doc/common_doc.py | 2 +- mathics/format/json.py | 10 ++++------ 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 01cefac9b..76ff8ad69 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -1727,7 +1727,7 @@ def eval_f(x_value, y_value): triangles = [] - split_edges = set([]) # subdivided edges + split_edges = set() # subdivided edges def triangle(x1, y1, x2, y2, x3, y3, depth=0): v1, v2, v3 = eval_f(x1, y1), eval_f(x2, y2), eval_f(x3, y3) @@ -1831,7 +1831,7 @@ def triangle(x1, y1, x2, y2, x3, y3, depth=0): # Cos of the maximum angle between successive line segments ang_thresh = cos(20 * pi / 180) for depth in range(1, max_depth): - needs_removal = set([]) + needs_removal = set() lent = len(triangles) # number of initial triangles for i1 in range(lent): for i2 in range(lent): @@ -2031,7 +2031,7 @@ def triangle(x1, y1, x2, y2, x3, y3, depth=0): if not any(x[2] is None for x in mesh_line) ] elif mesh == "System`All": - mesh_points = set([]) + mesh_points = set() for t in triangles: mesh_points.add((t[0], t[1]) if t[1] > t[0] else (t[1], t[0])) mesh_points.add((t[1], t[2]) if t[2] > t[1] else (t[2], t[1])) diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index fb038c52b..e7d2439d4 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -2117,10 +2117,10 @@ def apply(self, filename, evaluation): mime = set(FileFormat.detector.match(path)) # If match fails match on extension only - if mime == set([]): + if mime == set(): mime, encoding = mimetypes.guess_type(path) if mime is None: - mime = set([]) + mime = set() else: mime = set([mime]) result = [] diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 2df191422..d6d170c9e 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -1412,7 +1412,7 @@ class Large(Builtin): style_heads = frozenset(styles.keys()) style_and_form_heads = frozenset( - style_heads.union(set(["System`EdgeForm", "System`FaceForm"])) + style_heads.union({"System`EdgeForm", "System`FaceForm"}) ) GLOBALS.update( @@ -1429,9 +1429,11 @@ class Large(Builtin): GLOBALS.update(styles) -GRAPHICS_SYMBOLS = set( - ["System`List", "System`Rule", "System`VertexColors"] - + list(element_heads) - + [element + "Box" for element in element_heads] - + list(style_heads) -) +GRAPHICS_SYMBOLS = { + "System`List", + "System`Rule", + "System`VertexColors", + *element_heads, + *[element + "Box" for element in element_heads], + *style_heads, +} diff --git a/mathics/builtin/recurrence.py b/mathics/builtin/recurrence.py index 0667e82d3..5d9f4d654 100644 --- a/mathics/builtin/recurrence.py +++ b/mathics/builtin/recurrence.py @@ -122,7 +122,7 @@ def is_relation(eqn): evaluation ) - sym_eq = relation.to_sympy(converted_functions=set([func.get_head_name()])) + sym_eq = relation.to_sympy(converted_functions={func.get_head_name()}) if sym_eq is None: return sym_n = sympy.core.symbols(str(sympy_symbol_prefix + n.name)) diff --git a/mathics/builtin/scoping.py b/mathics/builtin/scoping.py index c010f1d05..36e5c780c 100644 --- a/mathics/builtin/scoping.py +++ b/mathics/builtin/scoping.py @@ -492,7 +492,7 @@ class Contexts(Builtin): def apply(self, evaluation): "Contexts[]" - contexts = set([]) + contexts = set() for name in evaluation.definitions.get_names(): contexts.add(String(name[: name.rindex("`") + 1])) diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index f8e1ba914..8983bf2e0 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -238,7 +238,7 @@ def apply(self, pattern, evaluation): if pattern is None: return - names = set([]) + names = set() for full_name in evaluation.definitions.get_matching_names(pattern): short_name = strip_context(full_name) names.add(short_name if short_name not in names else full_name) diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py index 87ccedc24..b6ad88e52 100644 --- a/mathics/doc/common_doc.py +++ b/mathics/doc/common_doc.py @@ -794,7 +794,7 @@ def __init__(self): # optional.optional_builtins_by_module, False)]: builtin_part = DocPart(self, title, is_reference=start) - modules_seen = set([]) + modules_seen = set() for module in modules: # FIXME add an additional mechanism in the module # to allow a docstring and indicate it is not to go in the diff --git a/mathics/format/json.py b/mathics/format/json.py index 1a6128b08..77f6bdfef 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -38,12 +38,10 @@ def convert_coord_collection( data = [ { **default_values, - **{ - "type": object_type, - "coords": [coords.pos() for coords in items], - "opacity": opacity, - "color": color[:3], - }, + "type": object_type, + "coords": [coords.pos() for coords in items], + "opacity": opacity, + "color": color[:3], } for items in collection ] From 4582220857d2b84523e26fab167939d9fb6ada83 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 11:17:25 -0400 Subject: [PATCH 132/193] Revove duplicated function The function `_clusters` was declared twice, with the exactly same body --- mathics/algorithm/clusters.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/mathics/algorithm/clusters.py b/mathics/algorithm/clusters.py index 599bb0725..3740375bf 100644 --- a/mathics/algorithm/clusters.py +++ b/mathics/algorithm/clusters.py @@ -1188,14 +1188,6 @@ def _squared_euclidean_distance(a, b): return s -def _clusters(x, a, k): - clusters = [[] for _ in range(k)] - add = [c.append for c in clusters] - for i, j in enumerate(a): - add[j](x[i]) - return clusters - - def kmeans(x, x_repr, k, mode, seed, epsilon): assert len(x) == len(x_repr) From 7cce5633fd2ea79d28daa3cfa67069bd9707f26f Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 11:23:07 -0400 Subject: [PATCH 133/193] Remove duplicated function: create_css --- mathics/builtin/graphics.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index d6d170c9e..ecd273b04 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -119,31 +119,6 @@ def cut(value): return value -def create_css( - edge_color=None, face_color=None, stroke_width=None, font_color=None, opacity=1.0 -): - css = [] - if edge_color is not None: - color, stroke_opacity = edge_color.to_css() - css.append("stroke: %s" % color) - css.append("stroke-opacity: %s" % stroke_opacity) - else: - css.append("stroke: none") - if stroke_width is not None: - css.append("stroke-width: %fpx" % stroke_width) - if face_color is not None: - color, fill_opacity = face_color.to_css() - css.append("fill: %s" % color) - css.append("fill-opacity: %s" % fill_opacity) - else: - css.append("fill: none") - if font_color is not None: - color, _ = font_color.to_css() - css.append("color: %s" % color) - css.append("opacity: %s" % opacity) - return "; ".join(css) - - def _to_float(x): x = x.round_to_float() if x is None: From e635193d7877cb8fcdb0a63ae13f034f82cc69e2 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 11:18:47 -0400 Subject: [PATCH 134/193] Remove useless sort The tick numbers were being sorted, but as they are independent, it isn't necessary --- mathics/builtin/box/graphics3d.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index 14e87478e..61f62e7db 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -652,13 +652,10 @@ def create_axes( # Add zero if required, since axis_ticks does not if xmin <= 0 <= xmax: ticks[0][0].append(0.0) - ticks[0][0].sort() if ymin <= 0 <= ymax: ticks[1][0].append(0.0) - ticks[1][0].sort() if zmin <= 0 <= zmax: ticks[2][0].append(0.0) - ticks[2][0].sort() # Convert ticks to nice strings e.g 0.100000000000002 -> '0.1' and # scale ticks appropriately From b0423829c38b14aebb5a2adb694e0e99a19696c8 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 11:24:46 -0400 Subject: [PATCH 135/193] Remove useless argument In the method `extent` from the class `Style`, there were an argument `pre`, which by default was `True`. It was never set to `False`. Anyway, the order of the style doesn't matter --- mathics/builtin/graphics.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index ecd273b04..8519bb8dc 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -1059,11 +1059,8 @@ def append(self, item, allow_forms=True): def set_option(self, name, value): self.options[name] = value - def extend(self, style, pre=True): - if pre: - self.styles = style.styles + self.styles - else: - self.styles.extend(style.styles) + def extend(self, style): + self.styles.extend(style.styles) def clone(self): result = self.klass(self.graphics, edge=self.edge, face=self.face) From 96f46d2be742d7b6a4a2a38efcb2cbbabae0252e Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 12:04:26 -0400 Subject: [PATCH 136/193] Remove useless None checking Stop checking if the color is None, this already was started before, but this place was forgotten --- mathics/format/json.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mathics/format/json.py b/mathics/format/json.py index 77f6bdfef..e5d4f4736 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -180,9 +180,7 @@ def polygon_3d_box(self) -> list: def sphere_3d_box(self) -> list: - face_color = self.face_color - if face_color is not None: - face_color = face_color.to_js() + face_color = self.face_color.to_js() data = convert_coord_collection( [self.points], "sphere", From 75cf17c79eb1d0bd5780c344f8e847f51e6227d1 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 12:05:34 -0400 Subject: [PATCH 137/193] Stop converting color into list --- mathics/format/json.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/format/json.py b/mathics/format/json.py index e5d4f4736..c7adf619b 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -137,8 +137,8 @@ def point_3d_box(self) -> list: # Tempoary bug fix: default Point color should be black not white face_color = self.face_color.to_rgba() - if list(face_color[:3]) == [1, 1, 1]: - face_color = RGBColor(components=(0, 0, 0, face_color[3])).to_rgba() + if face_color[:3] == (1, 1, 1): + face_color = (0, 0, 0, face_color[3]) point_size, _ = self.style.get_style(PointSize, face_element=False) relative_point_size = 0.01 if point_size is None else point_size.value From 66142da71f3f4be79cecbdfd4247ab7f39da3d5a Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 13:33:49 -0400 Subject: [PATCH 138/193] get_style_class() -> style_class --- mathics/builtin/drawing/graphics3d.py | 4 +--- mathics/builtin/graphics.py | 13 ++++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 2526e5fed..daf01ef5c 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -159,6 +159,7 @@ def total_extent_3d(extents): class Graphics3DElements(_GraphicsElements): coords = Coords3D + style_class = Style3D def __init__(self, content, evaluation, neg_y=False): super(Graphics3DElements, self).__init__(content, evaluation) @@ -178,9 +179,6 @@ def _apply_boxscaling(self, boxscale): for element in self.elements: element._apply_boxscaling(boxscale) - def get_style_class(self): - return Style3D - class Sphere(Builtin): """ diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 8519bb8dc..cdc4633a8 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -1028,7 +1028,7 @@ def _style(graphics, item): klass = get_class(head) style = klass.create_as_style(klass, graphics, item) elif head in ("System`EdgeForm", "System`FaceForm"): - style = graphics.get_style_class()( + style = graphics.style_class( graphics, edge=head == "System`EdgeForm", face=head == "System`FaceForm" ) if len(item.leaves) > 1: @@ -1051,7 +1051,7 @@ def __init__(self, graphics, edge=False, face=False): self.graphics = graphics self.edge = edge self.face = face - self.klass = graphics.get_style_class() + self.klass = graphics.style_class def append(self, item, allow_forms=True): self.styles.append(_style(self.graphics, item)) @@ -1135,6 +1135,8 @@ def _flatten(leaves): class _GraphicsElements(object): + style_class = Style + def __init__(self, content, evaluation): self.evaluation = evaluation self.elements = [] @@ -1206,10 +1208,10 @@ def convert(content, style): else: raise BoxConstructError - self.elements = list(convert(content, self.get_style_class()(self))) + self.elements = list(convert(content, self.style_class(self))) def create_style(self, expr): - style = self.get_style_class()(self) + style = self.style_class(self) def convert(expr): if expr.has_form(("List", "Directive"), None): @@ -1221,9 +1223,6 @@ def convert(expr): convert(expr) return style - def get_style_class(self): - return Style - class GraphicsElements(_GraphicsElements): coords = Coords From 8fe5a330e5ba07c920cc19490694401eb4e2f596 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 13:34:20 -0400 Subject: [PATCH 139/193] Remove useless check The code was checking if a builtin which ends with "Box" is None, in this case it can't be None, because "Box" is only added if the non-box version of that builtin exists --- mathics/builtin/graphics.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index cdc4633a8..16f09eac1 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -1191,17 +1191,14 @@ def convert(content, style): yield element elif head[-3:] == "Box": # and head[:-3] in element_heads: element_class = get_class(head) - if element_class is not None: - options = get_options(head[:-3]) - if options: - data, options = _data_and_options(item.leaves, options) - new_item = Expression(head, *data) - element = get_class(head)(self, style, new_item, options) - else: - element = get_class(head)(self, style, item) - yield element + options = get_options(head[:-3]) + if options: + data, options = _data_and_options(item.leaves, options) + new_item = Expression(head, *data) + element = element_class(self, style, new_item, options) else: - raise BoxConstructError + element = element_class(self, style, item) + yield element elif head == "System`List": for element in convert(item, style): yield element From 0b86c1d4058868eebfbebac241bac6ae56a09c91 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 14:09:32 -0400 Subject: [PATCH 140/193] Remove useless imports --- examples/symbolic_logic/gries_schneider/test_gs.py | 1 - mathics/builtin/box/inout.py | 6 +----- mathics/builtin/box/uniform_polyhedra.py | 2 -- mathics/builtin/compile/compile.py | 1 - mathics/builtin/compile/ir.py | 2 +- mathics/builtin/drawing/graphics3d.py | 5 ++--- mathics/builtin/drawing/uniform_polyhedra.py | 1 - mathics/builtin/inference.py | 3 --- mathics/builtin/numbers/numbertheory.py | 3 +-- mathics/builtin/physchemdata.py | 1 - mathics/builtin/pymimesniffer/magic.py | 3 --- mathics/builtin/specialfns/expintegral.py | 1 - mathics/core/definitions.py | 7 +------ mathics/core/evaluation.py | 4 +--- mathics/format/json.py | 2 +- mathics/system_info.py | 1 - 16 files changed, 8 insertions(+), 35 deletions(-) diff --git a/examples/symbolic_logic/gries_schneider/test_gs.py b/examples/symbolic_logic/gries_schneider/test_gs.py index 9e4211cd6..2ff8bcf1a 100644 --- a/examples/symbolic_logic/gries_schneider/test_gs.py +++ b/examples/symbolic_logic/gries_schneider/test_gs.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- -import unittest from mathics.core.expression import Expression, Integer, Rational, Symbol from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation diff --git a/mathics/builtin/box/inout.py b/mathics/builtin/box/inout.py index 85671d872..7df71f71c 100644 --- a/mathics/builtin/box/inout.py +++ b/mathics/builtin/box/inout.py @@ -1,10 +1,6 @@ # -*- coding: utf-8 -*- -from mathics.builtin.base import ( - BoxConstruct, - BoxConstructError, - Builtin, -) +from mathics.builtin.base import Builtin class ButtonBox(Builtin): diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py index 1a0df962c..1e295cb2b 100644 --- a/mathics/builtin/box/uniform_polyhedra.py +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -4,8 +4,6 @@ from mathics.builtin.drawing.graphics_internals import GLOBALS3D from mathics.builtin.colors.color_directives import _Color -from mathics.builtin.drawing.uniform_polyhedra import uniform_polyhedra_set - import numbers diff --git a/mathics/builtin/compile/compile.py b/mathics/builtin/compile/compile.py index 17c556832..13f82525c 100644 --- a/mathics/builtin/compile/compile.py +++ b/mathics/builtin/compile/compile.py @@ -1,5 +1,4 @@ import llvmlite.binding as llvm -from llvmlite.llvmpy.core import Type from ctypes import CFUNCTYPE from mathics.builtin.compile.utils import llvm_to_ctype diff --git a/mathics/builtin/compile/ir.py b/mathics/builtin/compile/ir.py index 59d0e5c96..63275c9c1 100644 --- a/mathics/builtin/compile/ir.py +++ b/mathics/builtin/compile/ir.py @@ -4,7 +4,7 @@ from llvmlite import ir import ctypes -from mathics.core.expression import Expression, Integer, Symbol, Real, String +from mathics.core.expression import Expression, Integer, Symbol, Real from mathics.builtin.compile.types import int_type, real_type, bool_type, void_type from mathics.builtin.compile.utils import pairwise, llvm_to_ctype from mathics.builtin.compile.base import CompileError diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index daf01ef5c..56335be8e 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -7,9 +7,9 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.core.expression import Expression, Real, Integer, Rational +from mathics.core.expression import Real, Integer, Rational -from mathics.builtin.base import BoxConstructError, Builtin, InstanceableBuiltin +from mathics.builtin.base import Builtin from mathics.builtin.colors.color_directives import RGBColor from mathics.builtin.graphics import ( _GraphicsElements, @@ -17,7 +17,6 @@ Graphics, Style, ) -from mathics.builtin.lists import List from mathics.builtin.numeric import apply_N diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py index 2ffb4c9d0..6a864c728 100644 --- a/mathics/builtin/drawing/uniform_polyhedra.py +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -9,7 +9,6 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin -from mathics.core.expression import Expression uniform_polyhedra_names = "tetrahedron, octahedron, dodecahedron, icosahedron" uniform_polyhedra_set = frozenset(uniform_polyhedra_names.split(", ")) diff --git a/mathics/builtin/inference.py b/mathics/builtin/inference.py index a2e45b1dc..c467d8d59 100644 --- a/mathics/builtin/inference.py +++ b/mathics/builtin/inference.py @@ -4,7 +4,6 @@ from mathics.core.expression import ( Expression, - Symbol, SymbolTrue, SymbolFalse, ) @@ -298,10 +297,8 @@ def get_assumption_rules_dispatch(evaluation): if value: symbol_value = SymbolTrue - symbol_negate_value = SymbolFalse else: symbol_value = SymbolFalse - symbol_negate_value = SymbolTrue if pat.has_form("Equal", 2): if value: diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index 7c53f015f..739175d1c 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -5,10 +5,9 @@ """ import sympy -from itertools import combinations from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.base import Builtin, Test, SympyFunction +from mathics.builtin.base import Builtin, SympyFunction from mathics.core.expression import ( Expression, Integer, diff --git a/mathics/builtin/physchemdata.py b/mathics/builtin/physchemdata.py index 3984aebdc..52b11d1b4 100644 --- a/mathics/builtin/physchemdata.py +++ b/mathics/builtin/physchemdata.py @@ -19,7 +19,6 @@ String, strip_context, ) -from mathics.settings import ROOT_DIR class NoElementDataFile(Exception): diff --git a/mathics/builtin/pymimesniffer/magic.py b/mathics/builtin/pymimesniffer/magic.py index 723d4aefa..3fe4cf977 100644 --- a/mathics/builtin/pymimesniffer/magic.py +++ b/mathics/builtin/pymimesniffer/magic.py @@ -4,7 +4,6 @@ import sys import os.path import logging -import io class MagicRule(object): @@ -97,8 +96,6 @@ def __init__(self, filename=None): self.mimetypes = {} def getText(self, node, name=None): - from xml.dom.minidom import Node - text = b"" if name: diff --git a/mathics/builtin/specialfns/expintegral.py b/mathics/builtin/specialfns/expintegral.py index 6da331983..06c0d327a 100644 --- a/mathics/builtin/specialfns/expintegral.py +++ b/mathics/builtin/specialfns/expintegral.py @@ -7,7 +7,6 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.arithmetic import _MPMathFunction -from mathics.core.expression import from_mpmath class ExpIntegralE(_MPMathFunction): diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 14bdaa171..56bfdf0e3 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -178,7 +178,6 @@ def load_pymathics_module(self, module, remove_on_quit=True): newsymbols[symbol_name] = instance for name in newsymbols: - luname = self.lookup_name(name) self.user.pop(name, None) for name, item in newsymbols.items(): @@ -192,7 +191,7 @@ def load_pymathics_module(self, module, remove_on_quit=True): return loaded_module def clear_pymathics_modules(self): - from mathics.builtin import builtins, builtins_by_module + from mathics.builtin import builtins_by_module for key in list(builtins_by_module.keys()): if not key.startswith("mathics."): @@ -459,10 +458,6 @@ def get_definition(self, name, only_if_exists=False) -> "Definition": else (builtin.attributes if builtin else set()) ) ) - upvalues = ([],) - messages = ([],) - nvalues = ([],) - defaultvalues = ([],) options = {} formatvalues = { "": [], diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py index 15643f443..d4151300f 100644 --- a/mathics/core/evaluation.py +++ b/mathics/core/evaluation.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import pickle from queue import Queue import os @@ -237,7 +236,6 @@ def __init__( self, definitions=None, output=None, format="text", catch_interrupt=True ) -> None: from mathics.core.definitions import Definitions - from mathics.core.expression import Symbol if definitions is None: definitions = Definitions() @@ -301,7 +299,7 @@ def evaluate(self, query, timeout=None, format=None): exception type of result like $Aborted, Overflow, Break, or Continue. If none of the above applies self.exc_result is Null """ - from mathics.core.expression import Symbol, Expression + from mathics.core.expression import Expression from mathics.core.rules import Rule self.recursion_depth = 0 diff --git a/mathics/format/json.py b/mathics/format/json.py index c7adf619b..c35ad5ad2 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -3,7 +3,7 @@ Format a Mathics object as JSON data """ -from mathics.builtin.graphics import PointSize, RGBColor +from mathics.builtin.graphics import PointSize from mathics.builtin.drawing.graphics3d import ( Graphics3DElements, diff --git a/mathics/system_info.py b/mathics/system_info.py index 49e03e0b8..d63e212c1 100644 --- a/mathics/system_info.py +++ b/mathics/system_info.py @@ -8,7 +8,6 @@ import mathics.builtin.files_io.filesystem as filesystem import mathics.builtin.numeric as numeric -from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation From aff3eea87d6e78cac3e87a43fdc88b8d1c093471 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 21 Sep 2021 19:50:23 -0400 Subject: [PATCH 141/193] Remove useless None checking That was never None, and if that be, an error would be raised because undeclared variable --- mathics/builtin/inference.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mathics/builtin/inference.py b/mathics/builtin/inference.py index c467d8d59..817c4ec9e 100644 --- a/mathics/builtin/inference.py +++ b/mathics/builtin/inference.py @@ -272,9 +272,6 @@ def get_assumption_rules_dispatch(evaluation): if val_consistent_assumptions == SymbolFalse: evaluation.message("Inconsistent assumptions") - if assumptions_list is None: - return remove_nots_when_unnecesary(pred, evaluation).evaluate(evaluation) - # Expands Logically assumptions_list, cont = logical_expand_assumptions(assumptions_list, evaluation) while cont: From 046859b124239b6590ad15fa64b5d8fdf3ef1f23 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sat, 25 Sep 2021 18:04:19 -0300 Subject: [PATCH 142/193] first cut --- mathics/core/atoms.py | 930 ++++++++++++++++++ mathics/core/expression.py | 1727 +-------------------------------- mathics/core/formatter.py | 69 ++ mathics/core/symbols.py | 787 +++++++++++++++ mathics/core/systemsymbols.py | 25 + 5 files changed, 1853 insertions(+), 1685 deletions(-) create mode 100644 mathics/core/atoms.py create mode 100644 mathics/core/symbols.py create mode 100644 mathics/core/systemsymbols.py diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py new file mode 100644 index 000000000..1b4afe079 --- /dev/null +++ b/mathics/core/atoms.py @@ -0,0 +1,930 @@ +# cython: language_level=3 +# -*- coding: utf-8 -*- + +import sympy +import mpmath +import math +import re + +import typing +from typing import Any, Optional +from functools import lru_cache + +from mathics.core.formatter import encode_mathml, encode_tex, extra_operators +from mathics.core.symbols import ( + Atom, + Symbol, + system_symbols, + fully_qualified_symbol_name, +) +from mathics.core.systemsymbols import SymbolNull, SymbolList +from mathics.core.numbers import dps, prec, min_prec, machine_precision +import base64 + +# Imperical number that seems to work. +# We have to be able to match mpmath values with sympy values +COMPARE_PREC = 50 + + +@lru_cache(maxsize=1024) +def from_mpmath(value, prec=None): + "Converts mpf or mpc to Number." + if isinstance(value, mpmath.mpf): + if prec is None: + return MachineReal(float(value)) + else: + # HACK: use str here to prevent loss of precision + return PrecisionReal(sympy.Float(str(value), prec)) + elif isinstance(value, mpmath.mpc): + if value.imag == 0.0: + return from_mpmath(value.real, prec) + real = from_mpmath(value.real, prec) + imag = from_mpmath(value.imag, prec) + return Complex(real, imag) + else: + raise TypeError(type(value)) + + +class Number(Atom): + def __str__(self) -> str: + return str(self.value) + + def is_numeric(self, evaluation=None) -> bool: + return True + + +def _ExponentFunction(value): + n = value.get_int_value() + if -5 <= n <= 5: + return SymbolNull + else: + return value + + +def _NumberFormat(man, base, exp, options): + from mathics.core.expression import Expression + + if exp.get_string_value(): + if options["_Form"] in ( + "System`InputForm", + "System`OutputForm", + "System`FullForm", + ): + return Expression("RowBox", Expression(SymbolList, man, String("*^"), exp)) + else: + return Expression( + "RowBox", + Expression( + "List", + man, + String(options["NumberMultiplier"]), + Expression("SuperscriptBox", base, exp), + ), + ) + else: + return man + + +_number_form_options = { + "DigitBlock": [0, 0], + "ExponentFunction": _ExponentFunction, + "ExponentStep": 1, + "NumberFormat": _NumberFormat, + "NumberPadding": ["", "0"], + "NumberPoint": ".", + "NumberSigns": ["-", ""], + "SignPadding": False, + "NumberMultiplier": "\u00d7", +} + + +class Integer(Number): + value: int + + def __new__(cls, value) -> "Integer": + n = int(value) + self = super(Integer, cls).__new__(cls) + self.value = n + return self + + @lru_cache() + def __init__(self, value) -> "Integer": + super().__init__() + + def boxes_to_text(self, **options) -> str: + return str(self.value) + + def boxes_to_mathml(self, **options) -> str: + return self.make_boxes("MathMLForm").boxes_to_mathml(**options) + + def boxes_to_tex(self, **options) -> str: + return str(self.value) + + def make_boxes(self, form) -> "String": + return String(str(self.value)) + + def atom_to_boxes(self, f, evaluation): + return self.make_boxes(f.get_name()) + + def default_format(self, evaluation, form) -> str: + return str(self.value) + + def to_sympy(self, **kwargs): + return sympy.Integer(self.value) + + def to_mpmath(self): + return mpmath.mpf(self.value) + + def to_python(self, *args, **kwargs): + return self.value + + def round(self, d=None) -> typing.Union["MachineReal", "PrecisionReal"]: + if d is None: + return MachineReal(float(self.value)) + else: + return PrecisionReal(sympy.Float(self.value, d)) + + def get_int_value(self) -> int: + return self.value + + def sameQ(self, other) -> bool: + """Mathics SameQ""" + return isinstance(other, Integer) and self.value == other.value + + def evaluate(self, evaluation): + evaluation.check_stopped() + return self + + def get_sort_key(self, pattern_sort=False): + if pattern_sort: + return super().get_sort_key(True) + else: + return [0, 0, self.value, 0, 1] + + def do_copy(self) -> "Integer": + return Integer(self.value) + + def __hash__(self): + return hash(("Integer", self.value)) + + def user_hash(self, update): + update(b"System`Integer>" + str(self.value).encode("utf8")) + + def __getnewargs__(self): + return (self.value,) + + def __neg__(self) -> "Integer": + return Integer(-self.value) + + @property + def is_zero(self) -> bool: + return self.value == 0 + + +Integer0 = Integer(0) +Integer1 = Integer(1) + + +class Rational(Number): + @lru_cache() + def __new__(cls, numerator, denominator=1) -> "Rational": + self = super().__new__(cls) + self.value = sympy.Rational(numerator, denominator) + return self + + def atom_to_boxes(self, f, evaluation): + return self.format(evaluation, f.get_name()) + + def to_sympy(self, **kwargs): + return self.value + + def to_mpmath(self): + return mpmath.mpf(self.value) + + def to_python(self, *args, **kwargs) -> float: + return float(self.value) + + def round(self, d=None) -> typing.Union["MachineReal", "PrecisionReal"]: + if d is None: + return MachineReal(float(self.value)) + else: + return PrecisionReal(self.value.n(d)) + + def sameQ(self, other) -> bool: + """Mathics SameQ""" + return isinstance(other, Rational) and self.value == other.value + + def numerator(self) -> "Integer": + return Integer(self.value.as_numer_denom()[0]) + + def denominator(self) -> "Integer": + return Integer(self.value.as_numer_denom()[1]) + + def do_format(self, evaluation, form) -> "Expression": + from mathics.core.expression import Expression + + assert fully_qualified_symbol_name(form) + if form == "System`FullForm": + return Expression( + Expression("HoldForm", Symbol("Rational")), + self.numerator(), + self.denominator(), + ).do_format(evaluation, form) + else: + numerator = self.numerator() + minus = numerator.value < 0 + if minus: + numerator = Integer(-numerator.value) + result = Expression("Divide", numerator, self.denominator()) + if minus: + result = Expression("Minus", result) + result = Expression("HoldForm", result) + return result.do_format(evaluation, form) + + def default_format(self, evaluation, form) -> str: + return "Rational[%s, %s]" % self.value.as_numer_denom() + + def evaluate(self, evaluation) -> "Rational": + evaluation.check_stopped() + return self + + def get_sort_key(self, pattern_sort=False): + if pattern_sort: + return super().get_sort_key(True) + else: + # HACK: otherwise "Bus error" when comparing 1==1. + return [0, 0, sympy.Float(self.value), 0, 1] + + def do_copy(self) -> "Rational": + return Rational(self.value) + + def __hash__(self): + return hash(("Rational", self.value)) + + def user_hash(self, update) -> None: + update( + b"System`Rational>" + ("%s>%s" % self.value.as_numer_denom()).encode("utf8") + ) + + def __getnewargs__(self): + return (self.numerator().get_int_value(), self.denominator().get_int_value()) + + def __neg__(self) -> "Rational": + return Rational( + -self.numerator().get_int_value(), self.denominator().get_int_value() + ) + + @property + def is_zero(self) -> bool: + return ( + self.numerator().is_zero + ) # (implicit) and not (self.denominator().is_zero) + + +RationalOneHalf = Rational(1, 2) + + +class Real(Number): + def __new__(cls, value, p=None) -> "Real": + if isinstance(value, str): + value = str(value) + if p is None: + digits = ("".join(re.findall("[0-9]+", value))).lstrip("0") + if digits == "": # Handle weird Mathematica zero case + p = max(prec(len(value.replace("0.", ""))), machine_precision) + else: + p = prec(len(digits.zfill(dps(machine_precision)))) + elif isinstance(value, sympy.Float): + if p is None: + p = value._prec + 1 + elif isinstance(value, (Integer, sympy.Number, mpmath.mpf, float, int)): + if p is not None and p > machine_precision: + value = str(value) + else: + raise TypeError("Unknown number type: %s (type %s)" % (value, type(value))) + + # return either machine precision or arbitrary precision real + if p is None or p == machine_precision: + return MachineReal.__new__(MachineReal, value) + else: + return PrecisionReal.__new__(PrecisionReal, value) + + def boxes_to_text(self, **options) -> str: + return self.make_boxes("System`OutputForm").boxes_to_text(**options) + + def boxes_to_mathml(self, **options) -> str: + return self.make_boxes("System`MathMLForm").boxes_to_mathml(**options) + + def boxes_to_tex(self, **options) -> str: + return self.make_boxes("System`TeXForm").boxes_to_tex(**options) + + def atom_to_boxes(self, f, evaluation): + return self.make_boxes(f.get_name()) + + def evaluate(self, evaluation) -> "Real": + evaluation.check_stopped() + return self + + def get_sort_key(self, pattern_sort=False): + if pattern_sort: + return super().get_sort_key(True) + return [0, 0, self.value, 0, 1] + + def is_nan(self, d=None) -> bool: + return isinstance(self.value, sympy.core.numbers.NaN) + + def __eq__(self, other) -> bool: + if isinstance(other, Real): + # MMA Docs: "Approximate numbers that differ in their last seven + # binary digits are considered equal" + _prec = min_prec(self, other) + with mpmath.workprec(_prec): + rel_eps = 0.5 ** (_prec - 7) + return mpmath.almosteq( + self.to_mpmath(), other.to_mpmath(), abs_eps=0, rel_eps=rel_eps + ) + else: + return self.get_sort_key() == other.get_sort_key() + + def __ne__(self, other) -> bool: + # Real is a total order + return not (self == other) + + def __hash__(self): + # ignore last 7 binary digits when hashing + _prec = self.get_precision() + return hash(("Real", self.to_sympy().n(dps(_prec)))) + + def user_hash(self, update): + # ignore last 7 binary digits when hashing + _prec = self.get_precision() + update(b"System`Real>" + str(self.to_sympy().n(dps(_prec))).encode("utf8")) + + def get_atom_name(self) -> str: + return "Real" + + +class MachineReal(Real): + """ + Machine precision real number. + + Stored internally as a python float. + """ + + value: float + + def __new__(cls, value) -> "MachineReal": + self = Number.__new__(cls) + self.value = float(value) + if math.isinf(self.value) or math.isnan(self.value): + raise OverflowError + return self + + def to_python(self, *args, **kwargs) -> float: + return self.value + + def to_sympy(self, *args, **kwargs): + return sympy.Float(self.value) + + def to_mpmath(self): + return mpmath.mpf(self.value) + + def round(self, d=None) -> "MachineReal": + return self + + def sameQ(self, other) -> bool: + """Mathics SameQ""" + if isinstance(other, MachineReal): + return self.value == other.value + elif isinstance(other, PrecisionReal): + return self.to_sympy() == other.value + return False + + def is_machine_precision(self) -> bool: + return True + + def get_precision(self) -> int: + return machine_precision + + def get_float_value(self, permit_complex=False) -> float: + return self.value + + def make_boxes(self, form): + from mathics.builtin.inout import number_form + + _number_form_options["_Form"] = form # passed to _NumberFormat + if form in ("System`InputForm", "System`FullForm"): + n = None + else: + n = 6 + return number_form(self, n, None, None, _number_form_options) + + def __getnewargs__(self): + return (self.value,) + + def do_copy(self) -> "MachineReal": + return MachineReal(self.value) + + def __neg__(self) -> "MachineReal": + return MachineReal(-self.value) + + @property + def is_zero(self) -> bool: + return self.value == 0.0 + + @property + def is_approx_zero(self) -> bool: + # In WMA, Chop[10.^(-10)] == 0, + # so, lets take it. + res = abs(self.value) <= 1e-10 + return res + + +class PrecisionReal(Real): + """ + Arbitrary precision real number. + + Stored internally as a sympy.Float. + + Note: Plays nicely with the mpmath.mpf (float) type. + """ + + value: sympy.Float + + def __new__(cls, value) -> "PrecisionReal": + self = Number.__new__(cls) + self.value = sympy.Float(value) + return self + + def to_python(self, *args, **kwargs): + return float(self.value) + + def to_sympy(self, *args, **kwargs): + return self.value + + def to_mpmath(self): + return mpmath.mpf(self.value) + + def round(self, d=None) -> typing.Union["MachineReal", "PrecisionReal"]: + if d is None: + return MachineReal(float(self.value)) + else: + d = min(dps(self.get_precision()), d) + return PrecisionReal(self.value.n(d)) + + def sameQ(self, other) -> bool: + """Mathics SameQ""" + if isinstance(other, PrecisionReal): + return self.value == other.value + elif isinstance(other, MachineReal): + return self.value == other.to_sympy() + return False + + def get_precision(self) -> int: + return self.value._prec + 1 + + def make_boxes(self, form): + from mathics.builtin.inout import number_form + + _number_form_options["_Form"] = form # passed to _NumberFormat + return number_form( + self, dps(self.get_precision()), None, None, _number_form_options + ) + + def __getnewargs__(self): + return (self.value,) + + def do_copy(self) -> "PrecisionReal": + return PrecisionReal(self.value) + + def __neg__(self) -> "PrecisionReal": + return PrecisionReal(-self.value) + + @property + def is_zero(self) -> bool: + return self.value == 0.0 + + +class Complex(Number): + """ + Complex wraps two real-valued Numbers. + """ + + real: Any + imag: Any + + def __new__(cls, real, imag): + self = super().__new__(cls) + if isinstance(real, Complex) or not isinstance(real, Number): + raise ValueError("Argument 'real' must be a real number.") + if isinstance(imag, Complex) or not isinstance(imag, Number): + raise ValueError("Argument 'imag' must be a real number.") + + if imag.sameQ(Integer0): + return real + + if isinstance(real, MachineReal) and not isinstance(imag, MachineReal): + imag = imag.round() + if isinstance(imag, MachineReal) and not isinstance(real, MachineReal): + real = real.round() + + self.real = real + self.imag = imag + return self + + def atom_to_boxes(self, f, evaluation): + return self.format(evaluation, f.get_name()) + + def __str__(self) -> str: + return str(self.to_sympy()) + + def to_sympy(self, **kwargs): + return self.real.to_sympy() + sympy.I * self.imag.to_sympy() + + def to_python(self, *args, **kwargs): + return complex( + self.real.to_python(*args, **kwargs), self.imag.to_python(*args, **kwargs) + ) + + def to_mpmath(self): + return mpmath.mpc(self.real.to_mpmath(), self.imag.to_mpmath()) + + def do_format(self, evaluation, form) -> "Expression": + from mathics.core.expression import Expression + + if form == "System`FullForm": + return Expression( + Expression("HoldForm", Symbol("Complex")), self.real, self.imag + ).do_format(evaluation, form) + + parts: typing.List[Any] = [] + if self.is_machine_precision() or not self.real.is_zero: + parts.append(self.real) + if self.imag.sameQ(Integer(1)): + parts.append(Symbol("I")) + else: + parts.append(Expression("Times", self.imag, Symbol("I"))) + + if len(parts) == 1: + result = parts[0] + else: + result = Expression("Plus", *parts) + + return Expression("HoldForm", result).do_format(evaluation, form) + + def default_format(self, evaluation, form) -> str: + return "Complex[%s, %s]" % ( + self.real.default_format(evaluation, form), + self.imag.default_format(evaluation, form), + ) + + def get_sort_key(self, pattern_sort=False): + if pattern_sort: + return super().get_sort_key(True) + else: + return [0, 0, self.real.get_sort_key()[2], self.imag.get_sort_key()[2], 1] + + def sameQ(self, other) -> bool: + """Mathics SameQ""" + return ( + isinstance(other, Complex) + and self.real == other.real + and self.imag == other.imag + ) + + def evaluate(self, evaluation) -> "Complex": + evaluation.check_stopped() + return self + + def round(self, d=None) -> "Complex": + real = self.real.round(d) + imag = self.imag.round(d) + return Complex(real, imag) + + def is_machine_precision(self) -> bool: + if self.real.is_machine_precision() or self.imag.is_machine_precision(): + return True + return False + + def get_float_value(self, permit_complex=False) -> typing.Optional[complex]: + if permit_complex: + real = self.real.get_float_value() + imag = self.imag.get_float_value() + if real is not None and imag is not None: + return complex(real, imag) + else: + return None + + def get_precision(self) -> typing.Optional[int]: + real_prec = self.real.get_precision() + imag_prec = self.imag.get_precision() + if imag_prec is None or real_prec is None: + return None + return min(real_prec, imag_prec) + + def do_copy(self) -> "Complex": + return Complex(self.real.do_copy(), self.imag.do_copy()) + + def __hash__(self): + return hash(("Complex", self.real, self.imag)) + + def user_hash(self, update) -> None: + update(b"System`Complex>") + update(self.real) + update(self.imag) + + def __eq__(self, other) -> bool: + if isinstance(other, Complex): + return self.real == other.real and self.imag == other.imag + else: + return self.get_sort_key() == other.get_sort_key() + + def __getnewargs__(self): + return (self.real, self.imag) + + def __neg__(self): + return Complex(-self.real, -self.imag) + + @property + def is_zero(self) -> bool: + return self.real.is_zero and self.imag.is_zero + + @property + def is_approx_zero(self) -> bool: + real_zero = ( + self.real.is_approx_zero + if hasattr(self.real, "is_approx_zero") + else self.real.is_zero + ) + imag_zero = ( + self.imag.is_approx_zero + if hasattr(self.imag, "is_approx_zero") + else self.imag.is_zero + ) + return real_zero and imag_zero + + +class String(Atom): + value: str + + def __new__(cls, value): + self = super().__new__(cls) + self.value = str(value) + return self + + def __str__(self) -> str: + return '"%s"' % self.value + + def boxes_to_text(self, show_string_characters=False, **options) -> str: + value = self.value + + if ( + not show_string_characters + and value.startswith('"') # nopep8 + and value.endswith('"') + ): + value = value[1:-1] + + return value + + def boxes_to_mathml(self, show_string_characters=False, **options) -> str: + from mathics.core.parser import is_symbol_name + from mathics.builtin import builtins_by_module + + operators = set() + for modname, builtins in builtins_by_module.items(): + for builtin in builtins: + # name = builtin.get_name() + operator = builtin.get_operator_display() + if operator is not None: + operators.add(operator) + + text = self.value + + def render(format, string): + encoded_text = encode_mathml(string) + return format % encoded_text + + if text.startswith('"') and text.endswith('"'): + if show_string_characters: + return render("%s", text[1:-1]) + else: + outtext = "" + for line in text[1:-1].split("\n"): + outtext += render("%s", line) + return outtext + elif text and ("0" <= text[0] <= "9" or text[0] == "."): + return render("%s", text) + else: + if text in operators or text in extra_operators: + if text == "\u2146": + return render( + '%s', text + ) + if text == "\u2062": + return render( + '%s', text + ) + return render("%s", text) + elif is_symbol_name(text): + return render("%s", text) + else: + outtext = "" + for line in text.split("\n"): + outtext += render("%s", line) + return outtext + + def boxes_to_tex(self, show_string_characters=False, **options) -> str: + from mathics.builtin import builtins_by_module + + operators = set() + + for modname, builtins in builtins_by_module.items(): + for builtin in builtins: + operator = builtin.get_operator_display() + if operator is not None: + operators.add(operator) + + text = self.value + + def render(format, string, in_text=False): + return format % encode_tex(string, in_text) + + if text.startswith('"') and text.endswith('"'): + if show_string_characters: + return render(r'\text{"%s"}', text[1:-1], in_text=True) + else: + return render(r"\text{%s}", text[1:-1], in_text=True) + elif text and text[0] in "0123456789-.": + return render("%s", text) + else: + # FIXME: this should be done in a better way. + if text == "\u2032": + return "'" + elif text == "\u2032\u2032": + return "''" + elif text == "\u2062": + return " " + elif text == "\u221e": + return r"\infty " + elif text == "\u00d7": + return r"\times " + elif text in ("(", "[", "{"): + return render(r"\left%s", text) + elif text in (")", "]", "}"): + return render(r"\right%s", text) + elif text == "\u301a": + return r"\left[\left[" + elif text == "\u301b": + return r"\right]\right]" + elif text == "," or text == ", ": + return text + elif text == "\u222b": + return r"\int" + # Tolerate WL or Unicode DifferentialD + elif text in ("\u2146", "\U0001D451"): + return r"\, d" + elif text == "\u2211": + return r"\sum" + elif text == "\u220f": + return r"\prod" + elif len(text) > 1: + return render(r"\text{%s}", text, in_text=True) + else: + return render("%s", text) + + def atom_to_boxes(self, f, evaluation): + inner = str(self.value) + + if f.get_name() in system_symbols("InputForm", "FullForm"): + inner = inner.replace("\\", "\\\\") + + return String('"' + inner + '"') + + def do_copy(self) -> "String": + return String(self.value) + + def default_format(self, evaluation, form) -> str: + value = self.value.replace("\\", "\\\\").replace('"', '\\"') + return '"%s"' % value + + def get_sort_key(self, pattern_sort=False): + if pattern_sort: + return super().get_sort_key(True) + else: + return [0, 1, self.value, 0, 1] + + def sameQ(self, other) -> bool: + """Mathics SameQ""" + return isinstance(other, String) and self.value == other.value + + def get_string_value(self) -> str: + return self.value + + def to_sympy(self, **kwargs): + return None + + def to_python(self, *args, **kwargs) -> str: + if kwargs.get("string_quotes", True): + return '"%s"' % self.value # add quotes to distinguish from Symbols + else: + return self.value + + def __hash__(self): + return hash(("String", self.value)) + + def user_hash(self, update): + # hashing a String is the one case where the user gets the untampered + # hash value of the string's text. this corresponds to MMA behavior. + update(self.value.encode("utf8")) + + def __getnewargs__(self): + return (self.value,) + + +class ByteArrayAtom(Atom): + value: str + + def __new__(cls, value): + self = super().__new__(cls) + if type(value) in (bytes, bytearray): + self.value = value + elif type(value) is list: + self.value = bytearray(list) + elif type(value) is str: + self.value = base64.b64decode(value) + else: + raise Exception("value does not belongs to a valid type") + return self + + def __str__(self) -> str: + return base64.b64encode(self.value).decode("utf8") + + def boxes_to_text(self, **options) -> str: + return '"' + self.__str__() + '"' + + def boxes_to_mathml(self, **options) -> str: + return encode_mathml(String('"' + self.__str__() + '"')) + + def boxes_to_tex(self, **options) -> str: + return encode_tex(String('"' + self.__str__() + '"')) + + def atom_to_boxes(self, f, evaluation): + res = String('""' + self.__str__() + '""') + return res + + def do_copy(self) -> "ByteArrayAtom": + return ByteArrayAtom(self.value) + + def default_format(self, evaluation, form) -> str: + value = self.value + return '"' + value.__str__() + '"' + + def get_sort_key(self, pattern_sort=False): + if pattern_sort: + return super().get_sort_key(True) + else: + return [0, 1, self.value, 0, 1] + + def sameQ(self, other) -> bool: + """Mathics SameQ""" + # FIX: check + if isinstance(other, ByteArrayAtom): + return self.value == other.value + return False + + def get_string_value(self) -> str: + try: + return self.value.decode("utf-8") + except Exception: + return None + + def to_sympy(self, **kwargs): + return None + + def to_python(self, *args, **kwargs) -> str: + return self.value + + def __hash__(self): + return hash(("ByteArrayAtom", self.value)) + + def user_hash(self, update): + # hashing a String is the one case where the user gets the untampered + # hash value of the string's text. this corresponds to MMA behavior. + update(self.value) + + def __getnewargs__(self): + return (self.value,) + + +class StringFromPython(String): + def __new__(cls, value): + self = super().__new__(cls, value) + if isinstance(value, sympy.NumberSymbol): + self.value = "sympy." + str(value) + + # Note that the test is done with math.inf first. + # This is to use float's ==, which may not strictly be necessary. + if math.inf == value: + self.value = "math.inf" + return self diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 88a9dcb71..560f7720a 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -2,20 +2,35 @@ # -*- coding: utf-8 -*- import sympy -import mpmath import math -import re import typing from typing import Any, Optional from itertools import chain from bisect import bisect_left -from functools import lru_cache - -from mathics.core.numbers import get_type, dps, prec, min_prec, machine_precision +# from mathics.core.formatter import * +from mathics.core.atoms import ( + ByteArrayAtom, + Number, + Integer, + Rational, + Real, + Complex, + String, +) +from mathics.core.symbols import Atom, BaseExpression, Monomial, Symbol, system_symbols +from mathics.core.systemsymbols import ( + SymbolByteArray, + SymbolFalse, + SymbolList, + SymbolN, + SymbolNull, + SymbolSequence, + SymbolTrue, +) +from mathics.core.numbers import get_type, dps from mathics.core.convert import sympy_symbol_prefix, SympyExpression -import base64 # Imperical number that seems to work. # We have to be able to match mpmath values with sympy values @@ -60,16 +75,6 @@ def strip_context(name) -> str: return name -# system_symbols('A', 'B', ...) -> ['System`A', 'System`B', ...] -def system_symbols(*symbols) -> typing.List[str]: - return [ensure_context(s) for s in symbols] - - -# system_symbols_dict({'SomeSymbol': ...}) -> {'System`SomeSymbol': ...} -def system_symbols_dict(d): - return {ensure_context(k): v for k, v in d.items()} - - class BoxError(Exception): def __init__(self, box, form) -> None: super().__init__("Box %s cannot be formatted as %s" % (box, form)) @@ -150,32 +155,32 @@ def from_python(arg): raise NotImplementedError -class KeyComparable(object): - def get_sort_key(self): - raise NotImplementedError +# class KeyComparable(object): +# def get_sort_key(self): +# raise NotImplementedError - def __lt__(self, other) -> bool: - return self.get_sort_key() < other.get_sort_key() +# def __lt__(self, other) -> bool: +# return self.get_sort_key() < other.get_sort_key() - def __gt__(self, other) -> bool: - return self.get_sort_key() > other.get_sort_key() +# def __gt__(self, other) -> bool: +# return self.get_sort_key() > other.get_sort_key() - def __le__(self, other) -> bool: - return self.get_sort_key() <= other.get_sort_key() +# def __le__(self, other) -> bool: +# return self.get_sort_key() <= other.get_sort_key() - def __ge__(self, other) -> bool: - return self.get_sort_key() >= other.get_sort_key() +# def __ge__(self, other) -> bool: +# return self.get_sort_key() >= other.get_sort_key() - def __eq__(self, other) -> bool: - return ( - hasattr(other, "get_sort_key") - and self.get_sort_key() == other.get_sort_key() - ) +# def __eq__(self, other) -> bool: +# return ( +# hasattr(other, "get_sort_key") +# and self.get_sort_key() == other.get_sort_key() +# ) - def __ne__(self, other) -> bool: - return ( - not hasattr(other, "get_sort_key") - ) or self.get_sort_key() != other.get_sort_key() +# def __ne__(self, other) -> bool: +# return ( +# not hasattr(other, "get_sort_key") +# ) or self.get_sort_key() != other.get_sort_key() # ExpressionCache keeps track of the following attributes for one Expression instance: @@ -251,463 +256,6 @@ def union(expressions, evaluation): ) -class BaseExpression(KeyComparable): - options: Any - pattern_sequence: bool - unformatted: Any - last_evaluated: Any - - def __new__(cls, *args, **kwargs): - self = object.__new__(cls) - self.options = None - self.pattern_sequence = False - self.unformatted = self - self._cache = None - return self - - def clear_cache(self): - self._cache = None - - def equal2(self, rhs: Any) -> Optional[bool]: - """Mathics two-argument Equal (==) - returns True if self and rhs are identical. - """ - if self.sameQ(rhs): - return True - - # If the types are the same then we'll use the classes definition of == (or __eq__). - # Superclasses which need to specialized this behavior should redefine equal2() - # - # I would use `is` instead `==` here, to compare classes. - if type(self) is type(rhs): - return self == rhs - return None - - def has_changed(self, definitions): - return True - - def sequences(self): - return None - - def flatten_sequence(self, evaluation) -> "BaseExpression": - return self - - def flatten_pattern_sequence(self, evaluation) -> "BaseExpression": - return self - - def get_attributes(self, definitions): - return set() - - def evaluate_next(self, evaluation): - return self.evaluate(evaluation), False - - def evaluate(self, evaluation) -> "BaseExpression": - evaluation.check_stopped() - return self - - def get_atoms(self, include_heads=True): - return [] - - def get_name(self): - "Returns symbol's name if Symbol instance" - - return "" - - def is_symbol(self) -> bool: - return False - - def is_machine_precision(self) -> bool: - return False - - def get_lookup_name(self): - "Returns symbol name of leftmost head" - - return self.get_name() - - def get_head(self): - return None - - def get_head_name(self): - return self.get_head().get_name() - - def get_leaves(self): - return [] - - def get_int_value(self): - return None - - def get_float_value(self, permit_complex=False): - return None - - def get_string_value(self): - return None - - def is_atom(self) -> bool: - return False - - def is_true(self) -> bool: - return False - - def is_numeric(self, evaluation=None) -> bool: - # used by NumericQ and expression ordering - return False - - def has_form(self, heads, *leaf_counts): - return False - - def flatten(self, head, pattern_only=False, callback=None) -> "BaseExpression": - return self - - def __hash__(self): - """ - To allow usage of expression as dictionary keys, - as in Expression.get_pre_choices - """ - raise NotImplementedError - - def user_hash(self, update) -> None: - # whereas __hash__ is for internal Mathics purposes like using Expressions as dictionary keys and fast - # comparison of elements, user_hash is called for Hash[]. user_hash should strive to give stable results - # across versions, whereas __hash__ must not. user_hash should try to hash all the data available, whereas - # __hash__ might only hash a sample of the data available. - raise NotImplementedError - - def sameQ(self, rhs) -> bool: - """Mathics SameQ""" - return id(self) == id(rhs) - - def get_sequence(self): - if self.get_head().get_name() == "System`Sequence": - return self.leaves - else: - return [self] - - def evaluate_leaves(self, evaluation) -> "BaseExpression": - return self - - def apply_rules( - self, rules, evaluation, level=0, options=None - ) -> typing.Tuple["BaseExpression", bool]: - if options: - l1, l2 = options["levelspec"] - if level < l1: - return self, False - elif l2 is not None and level > l2: - return self, False - - for rule in rules: - result = rule.apply(self, evaluation, fully=False) - if result is not None: - return result, True - return self, False - - def do_format(self, evaluation, form): - """ - Applies formats associated to the expression and removes - superfluous enclosing formats. - """ - formats = system_symbols( - "InputForm", - "OutputForm", - "StandardForm", - "FullForm", - "TraditionalForm", - "TeXForm", - "MathMLForm", - ) - - evaluation.inc_recursion_depth() - try: - expr = self - head = self.get_head_name() - leaves = self.get_leaves() - include_form = False - # If the expression is enclosed by a Format - # takes the form from the expression and - # removes the format from the expression. - if head in formats and len(leaves) == 1: - expr = leaves[0] - if not (form == "System`OutputForm" and head == "System`StandardForm"): - form = head - include_form = True - unformatted = expr - # If form is Fullform, return it without changes - if form == "System`FullForm": - if include_form: - expr = Expression(form, expr) - expr.unformatted = unformatted - return expr - - # Repeated and RepeatedNull confuse the formatter, - # so we need to hardlink their format rules: - if head == "System`Repeated": - if len(leaves) == 1: - return Expression( - "System`HoldForm", - Expression( - "System`Postfix", - Expression("System`List", leaves[0]), - "..", - 170, - ), - ) - else: - return Expression("System`HoldForm", expr) - elif head == "System`RepeatedNull": - if len(leaves) == 1: - return Expression( - "System`HoldForm", - Expression( - "System`Postfix", - Expression("System`List", leaves[0]), - "...", - 170, - ), - ) - else: - return Expression("System`HoldForm", expr) - - # If expr is not an atom, looks for formats in its definition - # and apply them. - def format_expr(expr): - if not (expr.is_atom()) and not (expr.head.is_atom()): - # expr is of the form f[...][...] - return None - name = expr.get_lookup_name() - formats = evaluation.definitions.get_formats(name, form) - for rule in formats: - result = rule.apply(expr, evaluation) - if result is not None and result != expr: - return result.evaluate(evaluation) - return None - - formatted = format_expr(expr) - if formatted is not None: - result = formatted.do_format(evaluation, form) - if include_form: - result = Expression(form, result) - result.unformatted = unformatted - return result - - # If the expression is still enclosed by a Format, - # iterate. - # If the expression is not atomic or of certain - # specific cases, iterate over the leaves. - head = expr.get_head_name() - if head in formats: - expr = expr.do_format(evaluation, form) - elif ( - head != "System`NumberForm" - and not expr.is_atom() - and head != "System`Graphics" - and head != "System`Graphics3D" - ): - # print("Not inside graphics or numberform, and not is atom") - new_leaves = [leaf.do_format(evaluation, form) for leaf in expr.leaves] - expr = Expression(expr.head.do_format(evaluation, form), *new_leaves) - - if include_form: - expr = Expression(form, expr) - expr.unformatted = unformatted - return expr - finally: - evaluation.dec_recursion_depth() - - def format( - self, evaluation, form, **kwargs - ) -> typing.Union["Expression", "Symbol"]: - """ - Applies formats associated to the expression, and then calls Makeboxes - """ - expr = self.do_format(evaluation, form) - result = Expression("MakeBoxes", expr, Symbol(form)).evaluate(evaluation) - return result - - def is_free(self, form, evaluation) -> bool: - from mathics.builtin.patterns import item_is_free - - return item_is_free(self, form, evaluation) - - def is_inexact(self) -> bool: - return self.get_precision() is not None - - def get_precision(self): - return None - - def get_option_values(self, evaluation, allow_symbols=False, stop_on_error=True): - options = self - if options.has_form("List", None): - options = options.flatten(SymbolList) - values = options.leaves - else: - values = [options] - option_values = {} - for option in values: - symbol_name = option.get_name() - if allow_symbols and symbol_name: - options = evaluation.definitions.get_options(symbol_name) - option_values.update(options) - else: - if not option.has_form(("Rule", "RuleDelayed"), 2): - if stop_on_error: - return None - else: - continue - name = option.leaves[0].get_name() - if not name and isinstance(option.leaves[0], String): - name = ensure_context(option.leaves[0].get_string_value()) - if not name: - if stop_on_error: - return None - else: - continue - option_values[name] = option.leaves[1] - return option_values - - def get_rules_list(self): - from mathics.core.rules import Rule - - list_expr = self.flatten(SymbolList) - list = [] - if list_expr.has_form("List", None): - list.extend(list_expr.leaves) - else: - list.append(list_expr) - rules = [] - for item in list: - if not item.has_form(("Rule", "RuleDelayed"), 2): - return None - rule = Rule(item.leaves[0], item.leaves[1]) - rules.append(rule) - return rules - - def to_sympy(self, **kwargs): - raise NotImplementedError - - def to_mpmath(self): - return None - - def round_to_float(self, evaluation=None, permit_complex=False): - """ - Try to round to python float. Return None if not possible. - """ - if evaluation is None: - value = self - elif isinstance(evaluation, sympy.core.numbers.NaN): - return None - else: - value = Expression(SymbolN, self).evaluate(evaluation) - if isinstance(value, Number): - value = value.round() - return value.get_float_value(permit_complex=permit_complex) - - def __abs__(self) -> "Expression": - return Expression("Abs", self) - - def __pos__(self): - return self - - def __neg__(self): - return Expression("Times", self, -1) - - def __add__(self, other) -> "Expression": - return Expression("Plus", self, other) - - def __sub__(self, other) -> "Expression": - return Expression("Plus", self, Expression("Times", other, -1)) - - def __mul__(self, other) -> "Expression": - return Expression("Times", self, other) - - def __truediv__(self, other) -> "Expression": - return Expression("Divide", self, other) - - def __floordiv__(self, other) -> "Expression": - return Expression("Floor", Expression("Divide", self, other)) - - def __pow__(self, other) -> "Expression": - return Expression("Power", self, other) - - -class Monomial(object): - """ - An object to sort monomials, used in Expression.get_sort_key and - Symbol.get_sort_key. - """ - - def __init__(self, exps_dict): - self.exps = exps_dict - - def __lt__(self, other) -> bool: - return self.__cmp(other) < 0 - - def __gt__(self, other) -> bool: - return self.__cmp(other) > 0 - - def __le__(self, other) -> bool: - return self.__cmp(other) <= 0 - - def __ge__(self, other) -> bool: - return self.__cmp(other) >= 0 - - def __eq__(self, other) -> bool: - return self.__cmp(other) == 0 - - def __ne__(self, other) -> bool: - return self.__cmp(other) != 0 - - def __cmp(self, other) -> int: - self_exps = self.exps.copy() - other_exps = other.exps.copy() - for var in self.exps: - if var in other.exps: - dec = min(self_exps[var], other_exps[var]) - self_exps[var] -= dec - if not self_exps[var]: - del self_exps[var] - other_exps[var] -= dec - if not other_exps[var]: - del other_exps[var] - self_exps = sorted((var, exp) for var, exp in self_exps.items()) - other_exps = sorted((var, exp) for var, exp in other_exps.items()) - - index = 0 - self_len = len(self_exps) - other_len = len(other_exps) - while True: - if index >= self_len and index >= other_len: - return 0 - if index >= self_len: - return -1 # self < other - if index >= other_len: - return 1 # self > other - self_var, self_exp = self_exps[index] - other_var, other_exp = other_exps[index] - if self_var < other_var: - return -1 - if self_var > other_var: - return 1 - if self_exp != other_exp: - if index + 1 == self_len or index + 1 == other_len: - # smaller exponents first - if self_exp < other_exp: - return -1 - elif self_exp == other_exp: - return 0 - else: - return 1 - else: - # bigger exponents first - if self_exp < other_exp: - return 1 - elif self_exp == other_exp: - return 0 - else: - return -1 - index += 1 - return 0 - - class Expression(BaseExpression): head: "Symbol" leaves: typing.List[Any] @@ -1839,7 +1387,7 @@ def thread(self, evaluation, head=None) -> typing.Tuple[bool, "Expression"]: def is_numeric(self, evaluation=None) -> bool: if evaluation: - if not "System`NumericFunction" in evaluation.definitions.get_attributes( + if "System`NumericFunction" not in evaluation.definitions.get_attributes( self._head.get_name() ): return False @@ -1905,1197 +1453,6 @@ def __getnewargs__(self): return (self._head, self._leaves) -class Atom(BaseExpression): - def is_atom(self) -> bool: - return True - - def equal2(self, rhs: Any) -> Optional[bool]: - """Mathics two-argument Equal (==) - returns True if self and rhs are identical. - """ - if self.sameQ(rhs): - return True - if isinstance(rhs, Symbol) or not isinstance(rhs, Atom): - return None - return self == rhs - - def has_form(self, heads, *leaf_counts) -> bool: - if leaf_counts: - return False - name = self.get_atom_name() - if isinstance(heads, tuple): - return name in heads - else: - return heads == name - - def has_symbol(self, symbol_name) -> bool: - return False - - def get_head(self) -> "Symbol": - return Symbol(self.get_atom_name()) - - def get_atom_name(self) -> str: - return self.__class__.__name__ - - def __repr__(self) -> str: - return "<%s: %s>" % (self.get_atom_name(), self) - - def replace_vars(self, vars, options=None, in_scoping=True) -> "Atom": - return self - - def replace_slots(self, slots, evaluation) -> "Atom": - return self - - def numerify(self, evaluation) -> "Atom": - return self - - def copy(self, reevaluate=False) -> "Atom": - result = self.do_copy() - result.original = self - return result - - def set_positions(self, position=None) -> None: - self.position = position - - def get_sort_key(self, pattern_sort=False): - if pattern_sort: - return [0, 0, 1, 1, 0, 0, 0, 1] - else: - raise NotImplementedError - - def get_atoms(self, include_heads=True) -> typing.List["Atom"]: - return [self] - - def atom_to_boxes(self, f, evaluation): - raise NotImplementedError - - -class Symbol(Atom): - name: str - sympy_dummy: Any - defined_symbols = {} - - def __new__(cls, name, sympy_dummy=None): - name = ensure_context(name) - self = cls.defined_symbols.get(name, None) - if self is None: - self = super(Symbol, cls).__new__(cls) - self.name = name - self.sympy_dummy = sympy_dummy - # cls.defined_symbols[name] = self - return self - - def __str__(self) -> str: - return self.name - - def do_copy(self) -> "Symbol": - return Symbol(self.name) - - def boxes_to_text(self, **options) -> str: - return str(self.name) - - def atom_to_boxes(self, f, evaluation) -> "String": - return String(evaluation.definitions.shorten_name(self.name)) - - def to_sympy(self, **kwargs): - from mathics.builtin import mathics_to_sympy - - if self.sympy_dummy is not None: - return self.sympy_dummy - - builtin = mathics_to_sympy.get(self.name) - if ( - builtin is None - or not builtin.sympy_name - or not builtin.is_constant() # nopep8 - ): - return sympy.Symbol(sympy_symbol_prefix + self.name) - return builtin.to_sympy(self, **kwargs) - - def to_python(self, *args, **kwargs): - if self == SymbolTrue: - return True - if self == SymbolFalse: - return False - if self == SymbolNull: - return None - n_evaluation = kwargs.get("n_evaluation") - if n_evaluation is not None: - value = Expression(SymbolN, self).evaluate(n_evaluation) - return value.to_python() - - if kwargs.get("python_form", False): - return self.to_sympy(**kwargs) - else: - return self.name - - def default_format(self, evaluation, form) -> str: - return self.name - - def get_attributes(self, definitions): - return definitions.get_attributes(self.name) - - def get_name(self) -> str: - return self.name - - def is_symbol(self) -> bool: - return True - - def get_sort_key(self, pattern_sort=False): - if pattern_sort: - return super(Symbol, self).get_sort_key(True) - else: - return [ - 1 if self.is_numeric() else 2, - 2, - Monomial({self.name: 1}), - 0, - self.name, - 1, - ] - - def equal2(self, rhs: Any) -> Optional[bool]: - """Mathics two-argument Equal (==)""" - if self.sameQ(rhs): - return True - - # Booleans are treated like constants, but all other symbols - # are treated None. We could create a Bool class and - # define equal2 in that, but for just this doesn't - # seem to be worth it. If other things come up, this may change. - if self in (SymbolTrue, SymbolFalse) and rhs in (SymbolTrue, SymbolFalse): - return self == rhs - return None - - def sameQ(self, rhs: Any) -> bool: - """Mathics SameQ""" - return id(self) == id(rhs) or isinstance(rhs, Symbol) and self.name == rhs.name - - def replace_vars(self, vars, options={}, in_scoping=True): - assert all(fully_qualified_symbol_name(v) for v in vars) - var = vars.get(self.name, None) - if var is None: - return self - else: - return var - - def has_symbol(self, symbol_name) -> bool: - return self.name == ensure_context(symbol_name) - - def evaluate(self, evaluation): - rules = evaluation.definitions.get_ownvalues(self.name) - for rule in rules: - result = rule.apply(self, evaluation, fully=True) - if result is not None and not result.sameQ(self): - return result.evaluate(evaluation) - return self - - def is_true(self) -> bool: - return self == SymbolTrue - - def is_numeric(self, evaluation=None) -> bool: - return self.name in system_symbols( - "Pi", "E", "EulerGamma", "GoldenRatio", "MachinePrecision", "Catalan" - ) - - def __hash__(self): - return hash(("Symbol", self.name)) # to distinguish from String - - def user_hash(self, update) -> None: - update(b"System`Symbol>" + self.name.encode("utf8")) - - def __getnewargs__(self): - return (self.name, self.sympy_dummy) - - -# Some common Symbols. This list is sorted in alpabetic order. -SymbolAborted = Symbol("$Aborted") -SymbolAssociation = Symbol("Association") -SymbolByteArray = Symbol("ByteArray") -SymbolComplexInfinity = Symbol("ComplexInfinity") -SymbolDirectedInfinity = Symbol("DirectedInfinity") -SymbolFailed = Symbol("$Failed") -SymbolFalse = Symbol("False") -SymbolInfinity = Symbol("Infinity") -SymbolList = Symbol("List") -SymbolMachinePrecision = Symbol("MachinePrecision") -SymbolMakeBoxes = Symbol("MakeBoxes") -SymbolN = Symbol("N") -SymbolNull = Symbol("Null") -SymbolRule = Symbol("Rule") -SymbolSequence = Symbol("Sequence") -SymbolTrue = Symbol("True") -SymbolUndefined = Symbol("Undefined") -SymbolLess = Symbol("Less") -SymbolGreater = Symbol("Greater") - - -@lru_cache(maxsize=1024) -def from_mpmath(value, prec=None): - "Converts mpf or mpc to Number." - if isinstance(value, mpmath.mpf): - if prec is None: - return MachineReal(float(value)) - else: - # HACK: use str here to prevent loss of precision - return PrecisionReal(sympy.Float(str(value), prec)) - elif isinstance(value, mpmath.mpc): - if value.imag == 0.0: - return from_mpmath(value.real, prec) - real = from_mpmath(value.real, prec) - imag = from_mpmath(value.imag, prec) - return Complex(real, imag) - else: - raise TypeError(type(value)) - - -class Number(Atom): - def __str__(self) -> str: - return str(self.value) - - def is_numeric(self, evaluation=None) -> bool: - return True - - -def _ExponentFunction(value): - n = value.get_int_value() - if -5 <= n <= 5: - return SymbolNull - else: - return value - - -def _NumberFormat(man, base, exp, options): - if exp.get_string_value(): - if options["_Form"] in ( - "System`InputForm", - "System`OutputForm", - "System`FullForm", - ): - return Expression("RowBox", Expression(SymbolList, man, String("*^"), exp)) - else: - return Expression( - "RowBox", - Expression( - "List", - man, - String(options["NumberMultiplier"]), - Expression("SuperscriptBox", base, exp), - ), - ) - else: - return man - - -_number_form_options = { - "DigitBlock": [0, 0], - "ExponentFunction": _ExponentFunction, - "ExponentStep": 1, - "NumberFormat": _NumberFormat, - "NumberPadding": ["", "0"], - "NumberPoint": ".", - "NumberSigns": ["-", ""], - "SignPadding": False, - "NumberMultiplier": "\u00d7", -} - - -class Integer(Number): - value: int - - def __new__(cls, value) -> "Integer": - n = int(value) - self = super(Integer, cls).__new__(cls) - self.value = n - return self - - @lru_cache() - def __init__(self, value) -> "Integer": - super().__init__() - - def boxes_to_text(self, **options) -> str: - return str(self.value) - - def boxes_to_mathml(self, **options) -> str: - return self.make_boxes("MathMLForm").boxes_to_mathml(**options) - - def boxes_to_tex(self, **options) -> str: - return str(self.value) - - def make_boxes(self, form) -> "String": - return String(str(self.value)) - - def atom_to_boxes(self, f, evaluation): - return self.make_boxes(f.get_name()) - - def default_format(self, evaluation, form) -> str: - return str(self.value) - - def to_sympy(self, **kwargs): - return sympy.Integer(self.value) - - def to_mpmath(self): - return mpmath.mpf(self.value) - - def to_python(self, *args, **kwargs): - return self.value - - def round(self, d=None) -> typing.Union["MachineReal", "PrecisionReal"]: - if d is None: - return MachineReal(float(self.value)) - else: - return PrecisionReal(sympy.Float(self.value, d)) - - def get_int_value(self) -> int: - return self.value - - def sameQ(self, other) -> bool: - """Mathics SameQ""" - return isinstance(other, Integer) and self.value == other.value - - def evaluate(self, evaluation): - evaluation.check_stopped() - return self - - def get_sort_key(self, pattern_sort=False): - if pattern_sort: - return super().get_sort_key(True) - else: - return [0, 0, self.value, 0, 1] - - def do_copy(self) -> "Integer": - return Integer(self.value) - - def __hash__(self): - return hash(("Integer", self.value)) - - def user_hash(self, update): - update(b"System`Integer>" + str(self.value).encode("utf8")) - - def __getnewargs__(self): - return (self.value,) - - def __neg__(self) -> "Integer": - return Integer(-self.value) - - @property - def is_zero(self) -> bool: - return self.value == 0 - - -Integer0 = Integer(0) -Integer1 = Integer(1) - - -class Rational(Number): - @lru_cache() - def __new__(cls, numerator, denominator=1) -> "Rational": - self = super().__new__(cls) - self.value = sympy.Rational(numerator, denominator) - return self - - def atom_to_boxes(self, f, evaluation): - return self.format(evaluation, f.get_name()) - - def to_sympy(self, **kwargs): - return self.value - - def to_mpmath(self): - return mpmath.mpf(self.value) - - def to_python(self, *args, **kwargs) -> float: - return float(self.value) - - def round(self, d=None) -> typing.Union["MachineReal", "PrecisionReal"]: - if d is None: - return MachineReal(float(self.value)) - else: - return PrecisionReal(self.value.n(d)) - - def sameQ(self, other) -> bool: - """Mathics SameQ""" - return isinstance(other, Rational) and self.value == other.value - - def numerator(self) -> "Integer": - return Integer(self.value.as_numer_denom()[0]) - - def denominator(self) -> "Integer": - return Integer(self.value.as_numer_denom()[1]) - - def do_format(self, evaluation, form) -> "Expression": - assert fully_qualified_symbol_name(form) - if form == "System`FullForm": - return Expression( - Expression("HoldForm", Symbol("Rational")), - self.numerator(), - self.denominator(), - ).do_format(evaluation, form) - else: - numerator = self.numerator() - minus = numerator.value < 0 - if minus: - numerator = Integer(-numerator.value) - result = Expression("Divide", numerator, self.denominator()) - if minus: - result = Expression("Minus", result) - result = Expression("HoldForm", result) - return result.do_format(evaluation, form) - - def default_format(self, evaluation, form) -> str: - return "Rational[%s, %s]" % self.value.as_numer_denom() - - def evaluate(self, evaluation) -> "Rational": - evaluation.check_stopped() - return self - - def get_sort_key(self, pattern_sort=False): - if pattern_sort: - return super().get_sort_key(True) - else: - # HACK: otherwise "Bus error" when comparing 1==1. - return [0, 0, sympy.Float(self.value), 0, 1] - - def do_copy(self) -> "Rational": - return Rational(self.value) - - def __hash__(self): - return hash(("Rational", self.value)) - - def user_hash(self, update) -> None: - update( - b"System`Rational>" + ("%s>%s" % self.value.as_numer_denom()).encode("utf8") - ) - - def __getnewargs__(self): - return (self.numerator().get_int_value(), self.denominator().get_int_value()) - - def __neg__(self) -> "Rational": - return Rational( - -self.numerator().get_int_value(), self.denominator().get_int_value() - ) - - @property - def is_zero(self) -> bool: - return ( - self.numerator().is_zero - ) # (implicit) and not (self.denominator().is_zero) - - -RationalOneHalf = Rational(1, 2) - - -class Real(Number): - def __new__(cls, value, p=None) -> "Real": - if isinstance(value, str): - value = str(value) - if p is None: - digits = ("".join(re.findall("[0-9]+", value))).lstrip("0") - if digits == "": # Handle weird Mathematica zero case - p = max(prec(len(value.replace("0.", ""))), machine_precision) - else: - p = prec(len(digits.zfill(dps(machine_precision)))) - elif isinstance(value, sympy.Float): - if p is None: - p = value._prec + 1 - elif isinstance(value, (Integer, sympy.Number, mpmath.mpf, float, int)): - if p is not None and p > machine_precision: - value = str(value) - else: - raise TypeError("Unknown number type: %s (type %s)" % (value, type(value))) - - # return either machine precision or arbitrary precision real - if p is None or p == machine_precision: - return MachineReal.__new__(MachineReal, value) - else: - return PrecisionReal.__new__(PrecisionReal, value) - - def boxes_to_text(self, **options) -> str: - return self.make_boxes("System`OutputForm").boxes_to_text(**options) - - def boxes_to_mathml(self, **options) -> str: - return self.make_boxes("System`MathMLForm").boxes_to_mathml(**options) - - def boxes_to_tex(self, **options) -> str: - return self.make_boxes("System`TeXForm").boxes_to_tex(**options) - - def atom_to_boxes(self, f, evaluation): - return self.make_boxes(f.get_name()) - - def evaluate(self, evaluation) -> "Real": - evaluation.check_stopped() - return self - - def get_sort_key(self, pattern_sort=False): - if pattern_sort: - return super().get_sort_key(True) - return [0, 0, self.value, 0, 1] - - def is_nan(self, d=None) -> bool: - return isinstance(self.value, sympy.core.numbers.NaN) - - def __eq__(self, other) -> bool: - if isinstance(other, Real): - # MMA Docs: "Approximate numbers that differ in their last seven - # binary digits are considered equal" - _prec = min_prec(self, other) - with mpmath.workprec(_prec): - rel_eps = 0.5 ** (_prec - 7) - return mpmath.almosteq( - self.to_mpmath(), other.to_mpmath(), abs_eps=0, rel_eps=rel_eps - ) - else: - return self.get_sort_key() == other.get_sort_key() - - def __ne__(self, other) -> bool: - # Real is a total order - return not (self == other) - - def __hash__(self): - # ignore last 7 binary digits when hashing - _prec = self.get_precision() - return hash(("Real", self.to_sympy().n(dps(_prec)))) - - def user_hash(self, update): - # ignore last 7 binary digits when hashing - _prec = self.get_precision() - update(b"System`Real>" + str(self.to_sympy().n(dps(_prec))).encode("utf8")) - - def get_atom_name(self) -> str: - return "Real" - - -class MachineReal(Real): - """ - Machine precision real number. - - Stored internally as a python float. - """ - - value: float - - def __new__(cls, value) -> "MachineReal": - self = Number.__new__(cls) - self.value = float(value) - if math.isinf(self.value) or math.isnan(self.value): - raise OverflowError - return self - - def to_python(self, *args, **kwargs) -> float: - return self.value - - def to_sympy(self, *args, **kwargs): - return sympy.Float(self.value) - - def to_mpmath(self): - return mpmath.mpf(self.value) - - def round(self, d=None) -> "MachineReal": - return self - - def sameQ(self, other) -> bool: - """Mathics SameQ""" - if isinstance(other, MachineReal): - return self.value == other.value - elif isinstance(other, PrecisionReal): - return self.to_sympy() == other.value - return False - - def is_machine_precision(self) -> bool: - return True - - def get_precision(self) -> int: - return machine_precision - - def get_float_value(self, permit_complex=False) -> float: - return self.value - - def make_boxes(self, form): - from mathics.builtin.inout import number_form - - _number_form_options["_Form"] = form # passed to _NumberFormat - if form in ("System`InputForm", "System`FullForm"): - n = None - else: - n = 6 - return number_form(self, n, None, None, _number_form_options) - - def __getnewargs__(self): - return (self.value,) - - def do_copy(self) -> "MachineReal": - return MachineReal(self.value) - - def __neg__(self) -> "MachineReal": - return MachineReal(-self.value) - - @property - def is_zero(self) -> bool: - return self.value == 0.0 - - @property - def is_approx_zero(self) -> bool: - # In WMA, Chop[10.^(-10)] == 0, - # so, lets take it. - res = abs(self.value) <= 1e-10 - return res - - -class PrecisionReal(Real): - """ - Arbitrary precision real number. - - Stored internally as a sympy.Float. - - Note: Plays nicely with the mpmath.mpf (float) type. - """ - - value: sympy.Float - - def __new__(cls, value) -> "PrecisionReal": - self = Number.__new__(cls) - self.value = sympy.Float(value) - return self - - def to_python(self, *args, **kwargs): - return float(self.value) - - def to_sympy(self, *args, **kwargs): - return self.value - - def to_mpmath(self): - return mpmath.mpf(self.value) - - def round(self, d=None) -> typing.Union["MachineReal", "PrecisionReal"]: - if d is None: - return MachineReal(float(self.value)) - else: - d = min(dps(self.get_precision()), d) - return PrecisionReal(self.value.n(d)) - - def sameQ(self, other) -> bool: - """Mathics SameQ""" - if isinstance(other, PrecisionReal): - return self.value == other.value - elif isinstance(other, MachineReal): - return self.value == other.to_sympy() - return False - - def get_precision(self) -> int: - return self.value._prec + 1 - - def make_boxes(self, form): - from mathics.builtin.inout import number_form - - _number_form_options["_Form"] = form # passed to _NumberFormat - return number_form( - self, dps(self.get_precision()), None, None, _number_form_options - ) - - def __getnewargs__(self): - return (self.value,) - - def do_copy(self) -> "PrecisionReal": - return PrecisionReal(self.value) - - def __neg__(self) -> "PrecisionReal": - return PrecisionReal(-self.value) - - @property - def is_zero(self) -> bool: - return self.value == 0.0 - - -class Complex(Number): - """ - Complex wraps two real-valued Numbers. - """ - - real: Any - imag: Any - - def __new__(cls, real, imag): - self = super().__new__(cls) - if isinstance(real, Complex) or not isinstance(real, Number): - raise ValueError("Argument 'real' must be a real number.") - if isinstance(imag, Complex) or not isinstance(imag, Number): - raise ValueError("Argument 'imag' must be a real number.") - - if imag.sameQ(Integer0): - return real - - if isinstance(real, MachineReal) and not isinstance(imag, MachineReal): - imag = imag.round() - if isinstance(imag, MachineReal) and not isinstance(real, MachineReal): - real = real.round() - - self.real = real - self.imag = imag - return self - - def atom_to_boxes(self, f, evaluation): - return self.format(evaluation, f.get_name()) - - def __str__(self) -> str: - return str(self.to_sympy()) - - def to_sympy(self, **kwargs): - return self.real.to_sympy() + sympy.I * self.imag.to_sympy() - - def to_python(self, *args, **kwargs): - return complex( - self.real.to_python(*args, **kwargs), self.imag.to_python(*args, **kwargs) - ) - - def to_mpmath(self): - return mpmath.mpc(self.real.to_mpmath(), self.imag.to_mpmath()) - - def do_format(self, evaluation, form) -> "Expression": - if form == "System`FullForm": - return Expression( - Expression("HoldForm", Symbol("Complex")), self.real, self.imag - ).do_format(evaluation, form) - - parts: typing.List[Any] = [] - if self.is_machine_precision() or not self.real.is_zero: - parts.append(self.real) - if self.imag.sameQ(Integer(1)): - parts.append(Symbol("I")) - else: - parts.append(Expression("Times", self.imag, Symbol("I"))) - - if len(parts) == 1: - result = parts[0] - else: - result = Expression("Plus", *parts) - - return Expression("HoldForm", result).do_format(evaluation, form) - - def default_format(self, evaluation, form) -> str: - return "Complex[%s, %s]" % ( - self.real.default_format(evaluation, form), - self.imag.default_format(evaluation, form), - ) - - def get_sort_key(self, pattern_sort=False): - if pattern_sort: - return super().get_sort_key(True) - else: - return [0, 0, self.real.get_sort_key()[2], self.imag.get_sort_key()[2], 1] - - def sameQ(self, other) -> bool: - """Mathics SameQ""" - return ( - isinstance(other, Complex) - and self.real == other.real - and self.imag == other.imag - ) - - def evaluate(self, evaluation) -> "Complex": - evaluation.check_stopped() - return self - - def round(self, d=None) -> "Complex": - real = self.real.round(d) - imag = self.imag.round(d) - return Complex(real, imag) - - def is_machine_precision(self) -> bool: - if self.real.is_machine_precision() or self.imag.is_machine_precision(): - return True - return False - - def get_float_value(self, permit_complex=False) -> typing.Optional[complex]: - if permit_complex: - real = self.real.get_float_value() - imag = self.imag.get_float_value() - if real is not None and imag is not None: - return complex(real, imag) - else: - return None - - def get_precision(self) -> typing.Optional[int]: - real_prec = self.real.get_precision() - imag_prec = self.imag.get_precision() - if imag_prec is None or real_prec is None: - return None - return min(real_prec, imag_prec) - - def do_copy(self) -> "Complex": - return Complex(self.real.do_copy(), self.imag.do_copy()) - - def __hash__(self): - return hash(("Complex", self.real, self.imag)) - - def user_hash(self, update) -> None: - update(b"System`Complex>") - update(self.real) - update(self.imag) - - def __eq__(self, other) -> bool: - if isinstance(other, Complex): - return self.real == other.real and self.imag == other.imag - else: - return self.get_sort_key() == other.get_sort_key() - - def __getnewargs__(self): - return (self.real, self.imag) - - def __neg__(self): - return Complex(-self.real, -self.imag) - - @property - def is_zero(self) -> bool: - return self.real.is_zero and self.imag.is_zero - - @property - def is_approx_zero(self) -> bool: - real_zero = ( - self.real.is_approx_zero - if hasattr(self.real, "is_approx_zero") - else self.real.is_zero - ) - imag_zero = ( - self.imag.is_approx_zero - if hasattr(self.imag, "is_approx_zero") - else self.imag.is_zero - ) - return real_zero and imag_zero - - -def encode_mathml(text: str) -> str: - text = text.replace("&", "&").replace("<", "<").replace(">", ">") - text = text.replace('"', """).replace(" ", " ") - text = text.replace("\n", '') - return text - - -TEX_REPLACE = { - "{": r"\{", - "}": r"\}", - "_": r"\_", - "$": r"\$", - "%": r"\%", - "#": r"\#", - "&": r"\&", - "\\": r"\backslash{}", - "^": r"{}^{\wedge}", - "~": r"\sim{}", - "|": r"\vert{}", -} -TEX_TEXT_REPLACE = TEX_REPLACE.copy() -TEX_TEXT_REPLACE.update( - { - "<": r"$<$", - ">": r"$>$", - "~": r"$\sim$", - "|": r"$\vert$", - "\\": r"$\backslash$", - "^": r"${}^{\wedge}$", - } -) -TEX_REPLACE_RE = re.compile("([" + "".join([re.escape(c) for c in TEX_REPLACE]) + "])") - - -def encode_tex(text: str, in_text=False) -> str: - def replace(match): - c = match.group(1) - repl = TEX_TEXT_REPLACE if in_text else TEX_REPLACE - # return TEX_REPLACE[c] - return repl.get(c, c) - - text = TEX_REPLACE_RE.sub(replace, text) - text = text.replace("\n", "\\newline\n") - return text - - -extra_operators = set( - ( - ",", - "(", - ")", - "[", - "]", - "{", - "}", - "\u301a", - "\u301b", - "\u00d7", - "\u2032", - "\u2032\u2032", - " ", - "\u2062", - "\u222b", - "\u2146", - ) -) - - -class String(Atom): - value: str - - def __new__(cls, value): - self = super().__new__(cls) - self.value = str(value) - return self - - def __str__(self) -> str: - return '"%s"' % self.value - - def boxes_to_text(self, show_string_characters=False, **options) -> str: - value = self.value - - if ( - not show_string_characters - and value.startswith('"') # nopep8 - and value.endswith('"') - ): - value = value[1:-1] - - return value - - def boxes_to_mathml(self, show_string_characters=False, **options) -> str: - from mathics.core.parser import is_symbol_name - from mathics.builtin import builtins_by_module - - operators = set() - for modname, builtins in builtins_by_module.items(): - for builtin in builtins: - # name = builtin.get_name() - operator = builtin.get_operator_display() - if operator is not None: - operators.add(operator) - - text = self.value - - def render(format, string): - encoded_text = encode_mathml(string) - return format % encoded_text - - if text.startswith('"') and text.endswith('"'): - if show_string_characters: - return render("%s", text[1:-1]) - else: - outtext = "" - for line in text[1:-1].split("\n"): - outtext += render("%s", line) - return outtext - elif text and ("0" <= text[0] <= "9" or text[0] == "."): - return render("%s", text) - else: - if text in operators or text in extra_operators: - if text == "\u2146": - return render( - '%s', text - ) - if text == "\u2062": - return render( - '%s', text - ) - return render("%s", text) - elif is_symbol_name(text): - return render("%s", text) - else: - outtext = "" - for line in text.split("\n"): - outtext += render("%s", line) - return outtext - - def boxes_to_tex(self, show_string_characters=False, **options) -> str: - from mathics.builtin import builtins_by_module - - operators = set() - - for modname, builtins in builtins_by_module.items(): - for builtin in builtins: - operator = builtin.get_operator_display() - if operator is not None: - operators.add(operator) - - text = self.value - - def render(format, string, in_text=False): - return format % encode_tex(string, in_text) - - if text.startswith('"') and text.endswith('"'): - if show_string_characters: - return render(r'\text{"%s"}', text[1:-1], in_text=True) - else: - return render(r"\text{%s}", text[1:-1], in_text=True) - elif text and text[0] in "0123456789-.": - return render("%s", text) - else: - # FIXME: this should be done in a better way. - if text == "\u2032": - return "'" - elif text == "\u2032\u2032": - return "''" - elif text == "\u2062": - return " " - elif text == "\u221e": - return r"\infty " - elif text == "\u00d7": - return r"\times " - elif text in ("(", "[", "{"): - return render(r"\left%s", text) - elif text in (")", "]", "}"): - return render(r"\right%s", text) - elif text == "\u301a": - return r"\left[\left[" - elif text == "\u301b": - return r"\right]\right]" - elif text == "," or text == ", ": - return text - elif text == "\u222b": - return r"\int" - # Tolerate WL or Unicode DifferentialD - elif text in ("\u2146", "\U0001D451"): - return r"\, d" - elif text == "\u2211": - return r"\sum" - elif text == "\u220f": - return r"\prod" - elif len(text) > 1: - return render(r"\text{%s}", text, in_text=True) - else: - return render("%s", text) - - def atom_to_boxes(self, f, evaluation): - inner = str(self.value) - - if f.get_name() in system_symbols("InputForm", "FullForm"): - inner = inner.replace("\\", "\\\\") - - return String('"' + inner + '"') - - def do_copy(self) -> "String": - return String(self.value) - - def default_format(self, evaluation, form) -> str: - value = self.value.replace("\\", "\\\\").replace('"', '\\"') - return '"%s"' % value - - def get_sort_key(self, pattern_sort=False): - if pattern_sort: - return super().get_sort_key(True) - else: - return [0, 1, self.value, 0, 1] - - def sameQ(self, other) -> bool: - """Mathics SameQ""" - return isinstance(other, String) and self.value == other.value - - def get_string_value(self) -> str: - return self.value - - def to_sympy(self, **kwargs): - return None - - def to_python(self, *args, **kwargs) -> str: - if kwargs.get("string_quotes", True): - return '"%s"' % self.value # add quotes to distinguish from Symbols - else: - return self.value - - def __hash__(self): - return hash(("String", self.value)) - - def user_hash(self, update): - # hashing a String is the one case where the user gets the untampered - # hash value of the string's text. this corresponds to MMA behavior. - update(self.value.encode("utf8")) - - def __getnewargs__(self): - return (self.value,) - - -class ByteArrayAtom(Atom): - value: str - - def __new__(cls, value): - self = super().__new__(cls) - if type(value) in (bytes, bytearray): - self.value = value - elif type(value) is list: - self.value = bytearray(list) - elif type(value) is str: - self.value = base64.b64decode(value) - else: - raise Exception("value does not belongs to a valid type") - return self - - def __str__(self) -> str: - return base64.b64encode(self.value).decode("utf8") - - def boxes_to_text(self, **options) -> str: - return '"' + self.__str__() + '"' - - def boxes_to_mathml(self, **options) -> str: - return encode_mathml(String('"' + self.__str__() + '"')) - - def boxes_to_tex(self, **options) -> str: - return encode_tex(String('"' + self.__str__() + '"')) - - def atom_to_boxes(self, f, evaluation): - res = String('""' + self.__str__() + '""') - return res - - def do_copy(self) -> "ByteArrayAtom": - return ByteArrayAtom(self.value) - - def default_format(self, evaluation, form) -> str: - value = self.value - return '"' + value.__str__() + '"' - - def get_sort_key(self, pattern_sort=False): - if pattern_sort: - return super().get_sort_key(True) - else: - return [0, 1, self.value, 0, 1] - - def sameQ(self, other) -> bool: - """Mathics SameQ""" - # FIX: check - if isinstance(other, ByteArrayAtom): - return self.value == other.value - return False - - def get_string_value(self) -> str: - try: - return self.value.decode("utf-8") - except: - return None - - def to_sympy(self, **kwargs): - return None - - def to_python(self, *args, **kwargs) -> str: - return self.value - - def __hash__(self): - return hash(("ByteArrayAtom", self.value)) - - def user_hash(self, update): - # hashing a String is the one case where the user gets the untampered - # hash value of the string's text. this corresponds to MMA behavior. - update(self.value) - - def __getnewargs__(self): - return (self.value,) - - -class StringFromPython(String): - def __new__(cls, value): - self = super().__new__(cls, value) - if isinstance(value, sympy.NumberSymbol): - self.value = "sympy." + str(value) - - # Note that the test is done with math.inf first. - # This is to use float's ==, which may not strictly be necessary. - if math.inf == value: - self.value = "math.inf" - return self - - def get_default_value(name, evaluation, k=None, n=None): pos = [] if k is not None: diff --git a/mathics/core/formatter.py b/mathics/core/formatter.py index 715693ab3..49abab8ab 100644 --- a/mathics/core/formatter.py +++ b/mathics/core/formatter.py @@ -1,10 +1,79 @@ import inspect from typing import Callable +import re # key is str: (to_xxx name, value) is formatter function to call format2fn: dict = {} +def encode_mathml(text: str) -> str: + text = text.replace("&", "&").replace("<", "<").replace(">", ">") + text = text.replace('"', """).replace(" ", " ") + text = text.replace("\n", '') + return text + + +TEX_REPLACE = { + "{": r"\{", + "}": r"\}", + "_": r"\_", + "$": r"\$", + "%": r"\%", + "#": r"\#", + "&": r"\&", + "\\": r"\backslash{}", + "^": r"{}^{\wedge}", + "~": r"\sim{}", + "|": r"\vert{}", +} +TEX_TEXT_REPLACE = TEX_REPLACE.copy() +TEX_TEXT_REPLACE.update( + { + "<": r"$<$", + ">": r"$>$", + "~": r"$\sim$", + "|": r"$\vert$", + "\\": r"$\backslash$", + "^": r"${}^{\wedge}$", + } +) +TEX_REPLACE_RE = re.compile("([" + "".join([re.escape(c) for c in TEX_REPLACE]) + "])") + + +def encode_tex(text: str, in_text=False) -> str: + def replace(match): + c = match.group(1) + repl = TEX_TEXT_REPLACE if in_text else TEX_REPLACE + # return TEX_REPLACE[c] + return repl.get(c, c) + + text = TEX_REPLACE_RE.sub(replace, text) + text = text.replace("\n", "\\newline\n") + return text + + +extra_operators = set( + ( + ",", + "(", + ")", + "[", + "]", + "{", + "}", + "\u301a", + "\u301b", + "\u00d7", + "\u2032", + "\u2032\u2032", + " ", + "\u2062", + "\u222b", + "\u2146", + ) +) + + def lookup_method(self, format: str, module_fn_name=None) -> Callable: """ Find a conversion method for `format` in self's class method resolution order. diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py new file mode 100644 index 000000000..989fa0a0d --- /dev/null +++ b/mathics/core/symbols.py @@ -0,0 +1,787 @@ +# cython: language_level=3 +# -*- coding: utf-8 -*- + +import sympy + +import typing +from typing import Any, Optional + +from mathics.core.convert import sympy_symbol_prefix + +from mathics.core.atoms import Number, String + +# Imperical number that seems to work. +# We have to be able to match mpmath values with sympy values +COMPARE_PREC = 50 + + +# system_symbols('A', 'B', ...) -> ['System`A', 'System`B', ...] +def system_symbols(*symbols) -> typing.List[str]: + return [ensure_context(s) for s in symbols] + + +# system_symbols_dict({'SomeSymbol': ...}) -> {'System`SomeSymbol': ...} +def system_symbols_dict(d): + return {ensure_context(k): v for k, v in d.items()} + + +def fully_qualified_symbol_name(name) -> bool: + return ( + isinstance(name, str) + and "`" in name + and not name.startswith("`") + and not name.endswith("`") + and "``" not in name + ) + + +def valid_context_name(ctx, allow_initial_backquote=False) -> bool: + return ( + isinstance(ctx, str) + and ctx.endswith("`") + and "``" not in ctx + and (allow_initial_backquote or not ctx.startswith("`")) + ) + + +def ensure_context(name, context="System`") -> str: + assert isinstance(name, str) + assert name != "" + if "`" in name: + # Symbol has a context mark -> it came from the parser + assert fully_qualified_symbol_name(name) + return name + # Symbol came from Python code doing something like + # Expression('Plus', ...) -> use System` or more generally + # context + name + return context + name + + +def strip_context(name) -> str: + if "`" in name: + return name[name.rindex("`") + 1 :] + return name + + +class KeyComparable(object): + def get_sort_key(self): + raise NotImplementedError + + def __lt__(self, other) -> bool: + return self.get_sort_key() < other.get_sort_key() + + def __gt__(self, other) -> bool: + return self.get_sort_key() > other.get_sort_key() + + def __le__(self, other) -> bool: + return self.get_sort_key() <= other.get_sort_key() + + def __ge__(self, other) -> bool: + return self.get_sort_key() >= other.get_sort_key() + + def __eq__(self, other) -> bool: + return ( + hasattr(other, "get_sort_key") + and self.get_sort_key() == other.get_sort_key() + ) + + def __ne__(self, other) -> bool: + return ( + not hasattr(other, "get_sort_key") + ) or self.get_sort_key() != other.get_sort_key() + + +class BaseExpression(KeyComparable): + options: Any + pattern_sequence: bool + unformatted: Any + last_evaluated: Any + + def __new__(cls, *args, **kwargs): + self = object.__new__(cls) + self.options = None + self.pattern_sequence = False + self.unformatted = self + self._cache = None + return self + + def clear_cache(self): + self._cache = None + + def equal2(self, rhs: Any) -> Optional[bool]: + """Mathics two-argument Equal (==) + returns True if self and rhs are identical. + """ + if self.sameQ(rhs): + return True + + # If the types are the same then we'll use the classes definition of == (or __eq__). + # Superclasses which need to specialized this behavior should redefine equal2() + # + # I would use `is` instead `==` here, to compare classes. + if type(self) is type(rhs): + return self == rhs + return None + + def has_changed(self, definitions): + return True + + def sequences(self): + return None + + def flatten_sequence(self, evaluation) -> "BaseExpression": + return self + + def flatten_pattern_sequence(self, evaluation) -> "BaseExpression": + return self + + def get_attributes(self, definitions): + return set() + + def evaluate_next(self, evaluation): + return self.evaluate(evaluation), False + + def evaluate(self, evaluation) -> "BaseExpression": + evaluation.check_stopped() + return self + + def get_atoms(self, include_heads=True): + return [] + + def get_name(self): + "Returns symbol's name if Symbol instance" + + return "" + + def is_symbol(self) -> bool: + return False + + def is_machine_precision(self) -> bool: + return False + + def get_lookup_name(self): + "Returns symbol name of leftmost head" + + return self.get_name() + + def get_head(self): + return None + + def get_head_name(self): + return self.get_head().get_name() + + def get_leaves(self): + return [] + + def get_int_value(self): + return None + + def get_float_value(self, permit_complex=False): + return None + + def get_string_value(self): + return None + + def is_atom(self) -> bool: + return False + + def is_true(self) -> bool: + return False + + def is_numeric(self, evaluation=None) -> bool: + # used by NumericQ and expression ordering + return False + + def has_form(self, heads, *leaf_counts): + return False + + def flatten(self, head, pattern_only=False, callback=None) -> "BaseExpression": + return self + + def __hash__(self): + """ + To allow usage of expression as dictionary keys, + as in Expression.get_pre_choices + """ + raise NotImplementedError + + def user_hash(self, update) -> None: + # whereas __hash__ is for internal Mathics purposes like using Expressions as dictionary keys and fast + # comparison of elements, user_hash is called for Hash[]. user_hash should strive to give stable results + # across versions, whereas __hash__ must not. user_hash should try to hash all the data available, whereas + # __hash__ might only hash a sample of the data available. + raise NotImplementedError + + def sameQ(self, rhs) -> bool: + """Mathics SameQ""" + return id(self) == id(rhs) + + def get_sequence(self): + if self.get_head().get_name() == "System`Sequence": + return self.leaves + else: + return [self] + + def evaluate_leaves(self, evaluation) -> "BaseExpression": + return self + + def apply_rules( + self, rules, evaluation, level=0, options=None + ) -> typing.Tuple["BaseExpression", bool]: + if options: + l1, l2 = options["levelspec"] + if level < l1: + return self, False + elif l2 is not None and level > l2: + return self, False + + for rule in rules: + result = rule.apply(self, evaluation, fully=False) + if result is not None: + return result, True + return self, False + + def do_format(self, evaluation, form): + """ + Applies formats associated to the expression and removes + superfluous enclosing formats. + """ + from mathics.core.expression import Expression + + formats = system_symbols( + "InputForm", + "OutputForm", + "StandardForm", + "FullForm", + "TraditionalForm", + "TeXForm", + "MathMLForm", + ) + + evaluation.inc_recursion_depth() + try: + expr = self + head = self.get_head_name() + leaves = self.get_leaves() + include_form = False + # If the expression is enclosed by a Format + # takes the form from the expression and + # removes the format from the expression. + if head in formats and len(leaves) == 1: + expr = leaves[0] + if not (form == "System`OutputForm" and head == "System`StandardForm"): + form = head + include_form = True + unformatted = expr + # If form is Fullform, return it without changes + if form == "System`FullForm": + if include_form: + expr = Expression(form, expr) + expr.unformatted = unformatted + return expr + + # Repeated and RepeatedNull confuse the formatter, + # so we need to hardlink their format rules: + if head == "System`Repeated": + if len(leaves) == 1: + return Expression( + "System`HoldForm", + Expression( + "System`Postfix", + Expression("System`List", leaves[0]), + "..", + 170, + ), + ) + else: + return Expression("System`HoldForm", expr) + elif head == "System`RepeatedNull": + if len(leaves) == 1: + return Expression( + "System`HoldForm", + Expression( + "System`Postfix", + Expression("System`List", leaves[0]), + "...", + 170, + ), + ) + else: + return Expression("System`HoldForm", expr) + + # If expr is not an atom, looks for formats in its definition + # and apply them. + def format_expr(expr): + if not (expr.is_atom()) and not (expr.head.is_atom()): + # expr is of the form f[...][...] + return None + name = expr.get_lookup_name() + formats = evaluation.definitions.get_formats(name, form) + for rule in formats: + result = rule.apply(expr, evaluation) + if result is not None and result != expr: + return result.evaluate(evaluation) + return None + + formatted = format_expr(expr) + if formatted is not None: + result = formatted.do_format(evaluation, form) + if include_form: + result = Expression(form, result) + result.unformatted = unformatted + return result + + # If the expression is still enclosed by a Format, + # iterate. + # If the expression is not atomic or of certain + # specific cases, iterate over the leaves. + head = expr.get_head_name() + if head in formats: + expr = expr.do_format(evaluation, form) + elif ( + head != "System`NumberForm" + and not expr.is_atom() + and head != "System`Graphics" + and head != "System`Graphics3D" + ): + # print("Not inside graphics or numberform, and not is atom") + new_leaves = [leaf.do_format(evaluation, form) for leaf in expr.leaves] + expr = Expression(expr.head.do_format(evaluation, form), *new_leaves) + + if include_form: + expr = Expression(form, expr) + expr.unformatted = unformatted + return expr + finally: + evaluation.dec_recursion_depth() + + def format( + self, evaluation, form, **kwargs + ) -> typing.Union["Expression", "Symbol"]: + """ + Applies formats associated to the expression, and then calls Makeboxes + """ + from mathics.core.expression import Expression + + expr = self.do_format(evaluation, form) + result = Expression("MakeBoxes", expr, Symbol(form)).evaluate(evaluation) + return result + + def is_free(self, form, evaluation) -> bool: + from mathics.builtin.patterns import item_is_free + + return item_is_free(self, form, evaluation) + + def is_inexact(self) -> bool: + return self.get_precision() is not None + + def get_precision(self): + return None + + def get_option_values(self, evaluation, allow_symbols=False, stop_on_error=True): + from mathics.core.systemsymbols import SymbolList + + options = self + if options.has_form("List", None): + options = options.flatten(SymbolList) + values = options.leaves + else: + values = [options] + option_values = {} + for option in values: + symbol_name = option.get_name() + if allow_symbols and symbol_name: + options = evaluation.definitions.get_options(symbol_name) + option_values.update(options) + else: + if not option.has_form(("Rule", "RuleDelayed"), 2): + if stop_on_error: + return None + else: + continue + name = option.leaves[0].get_name() + if not name and isinstance(option.leaves[0], String): + name = ensure_context(option.leaves[0].get_string_value()) + if not name: + if stop_on_error: + return None + else: + continue + option_values[name] = option.leaves[1] + return option_values + + def get_rules_list(self): + from mathics.core.rules import Rule + from mathics.core.systemsymbols import SymbolList + + list_expr = self.flatten(SymbolList) + list = [] + if list_expr.has_form("List", None): + list.extend(list_expr.leaves) + else: + list.append(list_expr) + rules = [] + for item in list: + if not item.has_form(("Rule", "RuleDelayed"), 2): + return None + rule = Rule(item.leaves[0], item.leaves[1]) + rules.append(rule) + return rules + + def to_sympy(self, **kwargs): + raise NotImplementedError + + def to_mpmath(self): + return None + + def round_to_float(self, evaluation=None, permit_complex=False): + """ + Try to round to python float. Return None if not possible. + """ + from mathics.core.expression import Expression + from mathics.core.systemsymbols import SymbolN + + if evaluation is None: + value = self + elif isinstance(evaluation, sympy.core.numbers.NaN): + return None + else: + value = Expression(SymbolN, self).evaluate(evaluation) + if isinstance(value, Number): + value = value.round() + return value.get_float_value(permit_complex=permit_complex) + + def __abs__(self) -> "Expression": + from mathics.core.expression import Expression + + return Expression("Abs", self) + + def __pos__(self): + return self + + def __neg__(self): + from mathics.core.expression import Expression + + return Expression("Times", self, -1) + + def __add__(self, other) -> "Expression": + from mathics.core.expression import Expression + + return Expression("Plus", self, other) + + def __sub__(self, other) -> "Expression": + from mathics.core.expression import Expression + + return Expression("Plus", self, Expression("Times", other, -1)) + + def __mul__(self, other) -> "Expression": + from mathics.core.expression import Expression + + return Expression("Times", self, other) + + def __truediv__(self, other) -> "Expression": + from mathics.core.expression import Expression + + return Expression("Divide", self, other) + + def __floordiv__(self, other) -> "Expression": + from mathics.core.expression import Expression + + return Expression("Floor", Expression("Divide", self, other)) + + def __pow__(self, other) -> "Expression": + from mathics.core.expression import Expression + + return Expression("Power", self, other) + + +class Monomial(object): + """ + An object to sort monomials, used in Expression.get_sort_key and + Symbol.get_sort_key. + """ + + def __init__(self, exps_dict): + self.exps = exps_dict + + def __lt__(self, other) -> bool: + return self.__cmp(other) < 0 + + def __gt__(self, other) -> bool: + return self.__cmp(other) > 0 + + def __le__(self, other) -> bool: + return self.__cmp(other) <= 0 + + def __ge__(self, other) -> bool: + return self.__cmp(other) >= 0 + + def __eq__(self, other) -> bool: + return self.__cmp(other) == 0 + + def __ne__(self, other) -> bool: + return self.__cmp(other) != 0 + + def __cmp(self, other) -> int: + self_exps = self.exps.copy() + other_exps = other.exps.copy() + for var in self.exps: + if var in other.exps: + dec = min(self_exps[var], other_exps[var]) + self_exps[var] -= dec + if not self_exps[var]: + del self_exps[var] + other_exps[var] -= dec + if not other_exps[var]: + del other_exps[var] + self_exps = sorted((var, exp) for var, exp in self_exps.items()) + other_exps = sorted((var, exp) for var, exp in other_exps.items()) + + index = 0 + self_len = len(self_exps) + other_len = len(other_exps) + while True: + if index >= self_len and index >= other_len: + return 0 + if index >= self_len: + return -1 # self < other + if index >= other_len: + return 1 # self > other + self_var, self_exp = self_exps[index] + other_var, other_exp = other_exps[index] + if self_var < other_var: + return -1 + if self_var > other_var: + return 1 + if self_exp != other_exp: + if index + 1 == self_len or index + 1 == other_len: + # smaller exponents first + if self_exp < other_exp: + return -1 + elif self_exp == other_exp: + return 0 + else: + return 1 + else: + # bigger exponents first + if self_exp < other_exp: + return 1 + elif self_exp == other_exp: + return 0 + else: + return -1 + index += 1 + return 0 + + +class Atom(BaseExpression): + def is_atom(self) -> bool: + return True + + def equal2(self, rhs: Any) -> Optional[bool]: + """Mathics two-argument Equal (==) + returns True if self and rhs are identical. + """ + if self.sameQ(rhs): + return True + if isinstance(rhs, Symbol) or not isinstance(rhs, Atom): + return None + return self == rhs + + def has_form(self, heads, *leaf_counts) -> bool: + if leaf_counts: + return False + name = self.get_atom_name() + if isinstance(heads, tuple): + return name in heads + else: + return heads == name + + def has_symbol(self, symbol_name) -> bool: + return False + + def get_head(self) -> "Symbol": + return Symbol(self.get_atom_name()) + + def get_atom_name(self) -> str: + return self.__class__.__name__ + + def __repr__(self) -> str: + return "<%s: %s>" % (self.get_atom_name(), self) + + def replace_vars(self, vars, options=None, in_scoping=True) -> "Atom": + return self + + def replace_slots(self, slots, evaluation) -> "Atom": + return self + + def numerify(self, evaluation) -> "Atom": + return self + + def copy(self, reevaluate=False) -> "Atom": + result = self.do_copy() + result.original = self + return result + + def set_positions(self, position=None) -> None: + self.position = position + + def get_sort_key(self, pattern_sort=False): + if pattern_sort: + return [0, 0, 1, 1, 0, 0, 0, 1] + else: + raise NotImplementedError + + def get_atoms(self, include_heads=True) -> typing.List["Atom"]: + return [self] + + def atom_to_boxes(self, f, evaluation): + raise NotImplementedError + + +class Symbol(Atom): + name: str + sympy_dummy: Any + defined_symbols = {} + + def __new__(cls, name, sympy_dummy=None): + name = ensure_context(name) + self = cls.defined_symbols.get(name, None) + if self is None: + self = super(Symbol, cls).__new__(cls) + self.name = name + self.sympy_dummy = sympy_dummy + # cls.defined_symbols[name] = self + return self + + def __str__(self) -> str: + return self.name + + def do_copy(self) -> "Symbol": + return Symbol(self.name) + + def boxes_to_text(self, **options) -> str: + return str(self.name) + + def atom_to_boxes(self, f, evaluation) -> "String": + return String(evaluation.definitions.shorten_name(self.name)) + + def to_sympy(self, **kwargs): + from mathics.builtin import mathics_to_sympy + + if self.sympy_dummy is not None: + return self.sympy_dummy + + builtin = mathics_to_sympy.get(self.name) + if ( + builtin is None + or not builtin.sympy_name + or not builtin.is_constant() # nopep8 + ): + return sympy.Symbol(sympy_symbol_prefix + self.name) + return builtin.to_sympy(self, **kwargs) + + def to_python(self, *args, **kwargs): + from mathics.core.systemsymbols import ( + SymbolTrue, + SymbolFalse, + SymbolNull, + SymbolN, + ) + from mathics.core.expression import Expression + + if self == SymbolTrue: + return True + if self == SymbolFalse: + return False + if self == SymbolNull: + return None + n_evaluation = kwargs.get("n_evaluation") + if n_evaluation is not None: + value = Expression(SymbolN, self).evaluate(n_evaluation) + return value.to_python() + + if kwargs.get("python_form", False): + return self.to_sympy(**kwargs) + else: + return self.name + + def default_format(self, evaluation, form) -> str: + return self.name + + def get_attributes(self, definitions): + return definitions.get_attributes(self.name) + + def get_name(self) -> str: + return self.name + + def is_symbol(self) -> bool: + return True + + def get_sort_key(self, pattern_sort=False): + if pattern_sort: + return super(Symbol, self).get_sort_key(True) + else: + return [ + 1 if self.is_numeric() else 2, + 2, + Monomial({self.name: 1}), + 0, + self.name, + 1, + ] + + def equal2(self, rhs: Any) -> Optional[bool]: + """Mathics two-argument Equal (==)""" + from mathics.core.systemsymbols import SymbolTrue, SymbolFalse + + if self.sameQ(rhs): + return True + + # Booleans are treated like constants, but all other symbols + # are treated None. We could create a Bool class and + # define equal2 in that, but for just this doesn't + # seem to be worth it. If other things come up, this may change. + if self in (SymbolTrue, SymbolFalse) and rhs in (SymbolTrue, SymbolFalse): + return self == rhs + return None + + def sameQ(self, rhs: Any) -> bool: + """Mathics SameQ""" + return id(self) == id(rhs) or isinstance(rhs, Symbol) and self.name == rhs.name + + def replace_vars(self, vars, options={}, in_scoping=True): + assert all(fully_qualified_symbol_name(v) for v in vars) + var = vars.get(self.name, None) + if var is None: + return self + else: + return var + + def has_symbol(self, symbol_name) -> bool: + return self.name == ensure_context(symbol_name) + + def evaluate(self, evaluation): + rules = evaluation.definitions.get_ownvalues(self.name) + for rule in rules: + result = rule.apply(self, evaluation, fully=True) + if result is not None and not result.sameQ(self): + return result.evaluate(evaluation) + return self + + def is_true(self) -> bool: + return self == Symbol("True") + + def is_numeric(self, evaluation=None) -> bool: + return self.name in system_symbols( + "Pi", "E", "EulerGamma", "GoldenRatio", "MachinePrecision", "Catalan" + ) + + def __hash__(self): + return hash(("Symbol", self.name)) # to distinguish from String + + def user_hash(self, update) -> None: + update(b"System`Symbol>" + self.name.encode("utf8")) + + def __getnewargs__(self): + return (self.name, self.sympy_dummy) diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py new file mode 100644 index 000000000..5f577caa9 --- /dev/null +++ b/mathics/core/systemsymbols.py @@ -0,0 +1,25 @@ +# cython: language_level=3 +# -*- coding: utf-8 -*- + +from mathics.core.symbols import Symbol + +# Some common Symbols. This list is sorted in alpabetic order. +SymbolAborted = Symbol("$Aborted") +SymbolAssociation = Symbol("Association") +SymbolByteArray = Symbol("ByteArray") +SymbolComplexInfinity = Symbol("ComplexInfinity") +SymbolDirectedInfinity = Symbol("DirectedInfinity") +SymbolFailed = Symbol("$Failed") +SymbolFalse = Symbol("False") +SymbolInfinity = Symbol("Infinity") +SymbolList = Symbol("List") +SymbolMachinePrecision = Symbol("MachinePrecision") +SymbolMakeBoxes = Symbol("MakeBoxes") +SymbolN = Symbol("N") +SymbolNull = Symbol("Null") +SymbolRule = Symbol("Rule") +SymbolSequence = Symbol("Sequence") +SymbolTrue = Symbol("True") +SymbolUndefined = Symbol("Undefined") +SymbolLess = Symbol("Less") +SymbolGreater = Symbol("Greater") From f815aa1ab7b212d3a916dc3a11f442687677a4bb Mon Sep 17 00:00:00 2001 From: mmatera Date: Sat, 25 Sep 2021 23:35:16 -0300 Subject: [PATCH 143/193] fix all the imports --- mathics/__init__.py | 8 +- mathics/builtin/arithfns/basic.py | 10 ++- mathics/builtin/arithmetic.py | 12 +-- mathics/builtin/assignment.py | 14 ++-- mathics/builtin/attributes.py | 6 +- mathics/builtin/base.py | 14 ++-- mathics/builtin/box/graphics.py | 8 +- mathics/builtin/colors/color_directives.py | 9 ++- mathics/builtin/colors/color_operations.py | 13 ++-- mathics/builtin/colors/named_colors.py | 2 +- mathics/builtin/comparison.py | 8 +- mathics/builtin/compilation.py | 9 ++- mathics/builtin/compile/ir.py | 4 +- mathics/builtin/compress.py | 4 +- mathics/builtin/datentime.py | 14 ++-- mathics/builtin/distance/stringdata.py | 9 +-- mathics/builtin/drawing/graphics3d.py | 2 +- mathics/builtin/drawing/graphics_internals.py | 2 +- mathics/builtin/drawing/image.py | 11 ++- mathics/builtin/drawing/plot.py | 8 +- mathics/builtin/drawing/splines.py | 4 +- mathics/builtin/evaluation.py | 2 +- mathics/builtin/fileformats/htmlformat.py | 4 +- mathics/builtin/fileformats/xmlformat.py | 12 ++- mathics/builtin/files_io/files.py | 14 ++-- mathics/builtin/files_io/filesystem.py | 10 +-- mathics/builtin/files_io/importexport.py | 11 +-- mathics/builtin/graphics.py | 14 ++-- mathics/builtin/inference.py | 4 +- mathics/builtin/inout.py | 10 ++- mathics/builtin/intfns/combinatorial.py | 5 +- mathics/builtin/intfns/divlike.py | 8 +- mathics/builtin/intfns/recurrence.py | 2 +- mathics/builtin/list/associations.py | 8 +- mathics/builtin/list/constructing.py | 4 +- mathics/builtin/list/eol.py | 9 +-- mathics/builtin/list/rearrange.py | 4 +- mathics/builtin/lists.py | 21 +++--- mathics/builtin/logic.py | 6 +- mathics/builtin/manipulate.py | 6 +- mathics/builtin/numbers/algebra.py | 13 +++- mathics/builtin/numbers/calculus.py | 8 +- mathics/builtin/numbers/constants.py | 8 +- mathics/builtin/numbers/exptrig.py | 6 +- mathics/builtin/numbers/integer.py | 3 +- mathics/builtin/numbers/linalg.py | 4 +- mathics/builtin/numbers/numbertheory.py | 6 +- mathics/builtin/numbers/randomnumbers.py | 4 +- mathics/builtin/numeric.py | 10 ++- mathics/builtin/optimization.py | 3 +- mathics/builtin/options.py | 11 ++- mathics/builtin/patterns.py | 10 ++- mathics/builtin/physchemdata.py | 9 +-- mathics/builtin/procedural.py | 8 +- mathics/builtin/quantities.py | 6 +- mathics/builtin/scoping.py | 11 ++- mathics/builtin/sparse.py | 15 ++-- mathics/builtin/specialfns/bessel.py | 2 +- mathics/builtin/specialfns/gamma.py | 3 +- mathics/builtin/specialfns/othogonal.py | 2 +- mathics/builtin/specialfns/zeta.py | 2 +- mathics/builtin/string/characters.py | 8 +- mathics/builtin/string/charcodes.py | 6 +- mathics/builtin/string/operations.py | 15 ++-- mathics/builtin/string/patterns.py | 6 +- mathics/builtin/strings.py | 9 ++- mathics/builtin/structure.py | 12 ++- mathics/builtin/system.py | 8 +- mathics/builtin/tensors.py | 6 +- mathics/core/atoms.py | 70 +++++++++++++++++- mathics/core/convert.py | 8 +- mathics/core/definitions.py | 11 +-- mathics/core/evaluation.py | 12 ++- mathics/core/expression.py | 74 +------------------ mathics/core/numbers.py | 3 +- mathics/core/parser/convert.py | 20 ++--- mathics/core/parser/util.py | 2 +- mathics/core/pattern.py | 3 +- mathics/core/read.py | 4 +- mathics/core/rules.py | 3 +- mathics/core/symbols.py | 5 +- test/test_arithmetic.py | 4 +- test/test_compile.py | 4 +- test/test_formatter/test_asy.py | 4 +- test/test_formatter/test_svg.py | 4 +- test/test_hash.py | 9 ++- test/test_help.py | 2 +- test/test_parser/test_convert.py | 6 +- 88 files changed, 437 insertions(+), 350 deletions(-) diff --git a/mathics/__init__.py b/mathics/__init__.py index 8e3257149..14a35c9b1 100644 --- a/mathics/__init__.py +++ b/mathics/__init__.py @@ -8,9 +8,8 @@ import numpy from mathics.version import __version__ -from mathics.core.expression import ( - Expression, - Symbol, +from mathics.core.symbols import Symbol +from mathics.core.atoms import ( String, Number, Integer, @@ -21,6 +20,9 @@ MachineReal, PrecisionReal, ) + +from mathics.core.expression import Expression + from mathics.core.convert import from_sympy diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index 10148008b..417a10e1a 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -19,9 +19,9 @@ SympyFunction, ) -from mathics.core.expression import ( +from mathics.core.expression import Expression +from mathics.core.atoms import ( Complex, - Expression, Integer, Integer0, Integer1, @@ -29,13 +29,15 @@ Rational, Real, String, - Symbol, + from_mpmath, +) +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( SymbolComplexInfinity, SymbolDirectedInfinity, SymbolInfinity, SymbolNull, SymbolSequence, - from_mpmath, ) from mathics.core.numbers import min_prec, dps diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index b3c9fddd1..93c9150c5 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -22,9 +22,9 @@ Test, ) -from mathics.core.expression import ( +from mathics.core.expression import Expression +from mathics.core.atoms import ( Complex, - Expression, Integer, Integer0, Integer1, @@ -32,13 +32,15 @@ Rational, Real, String, - Symbol, + from_mpmath, + from_python, +) +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( SymbolTrue, SymbolFalse, SymbolUndefined, SymbolList, - from_mpmath, - from_python, ) from mathics.core.numbers import min_prec, dps, SpecialValueError diff --git a/mathics/builtin/assignment.py b/mathics/builtin/assignment.py index f8579f2df..358f4556a 100644 --- a/mathics/builtin/assignment.py +++ b/mathics/builtin/assignment.py @@ -9,15 +9,19 @@ PrefixOperator, ) from mathics.core.rules import Rule -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.symbols import ( Symbol, - SymbolFailed, - SymbolNull, valid_context_name, system_symbols, - String, ) + +from mathics.core.systemsymbols import ( + SymbolFailed, + SymbolNull, +) +from mathics.core.atoms import String + from mathics.core.definitions import PyMathicsLoadException from mathics.builtin.lists import walk_parts from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit diff --git a/mathics/builtin/attributes.py b/mathics/builtin/attributes.py index 569835915..197d2a9e9 100644 --- a/mathics/builtin/attributes.py +++ b/mathics/builtin/attributes.py @@ -12,7 +12,11 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Predefined, Builtin -from mathics.core.expression import Expression, Symbol, SymbolNull, String +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import SymbolNull +from mathics.core.atoms import String + from mathics.builtin.assignment import get_symbol_list diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 7d6bcce56..283387334 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -15,18 +15,22 @@ from mathics.core.definitions import Definition from mathics.core.parser.util import SystemDefinitions, PyMathicsDefinitions from mathics.core.rules import Rule, BuiltinRule, Pattern -from mathics.core.expression import ( +from mathics.core.symbols import ( BaseExpression, - Expression, + Symbol, + ensure_context, + strip_context, +) +from mathics.core.atoms import ( Integer, MachineReal, PrecisionReal, String, - Symbol, +) +from mathics.core.expression import Expression +from mathics.core.systemsymbols import ( SymbolFalse, SymbolTrue, - ensure_context, - strip_context, ) from mathics.core.numbers import get_precision, PrecisionValueError diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index 4002fb979..2ce1254b6 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -35,14 +35,14 @@ from mathics.format.asy_fns import asy_color, asy_number -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.atoms import ( Integer, Real, String, - Symbol, - SymbolList, ) +from mathics.core.systemsymbols import SymbolList # Note: has to come before _ArcBox class _RoundBox(_GraphicsElement): diff --git a/mathics/builtin/colors/color_directives.py b/mathics/builtin/colors/color_directives.py index a24a66e4a..5edb6d9bc 100644 --- a/mathics/builtin/colors/color_directives.py +++ b/mathics/builtin/colors/color_directives.py @@ -15,16 +15,17 @@ BoxConstructError, ) from mathics.builtin.drawing.graphics_internals import _GraphicsElement, get_class -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.atoms import ( Integer, Real, String, - Symbol, - SymbolList, from_python, ) +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import SymbolList + from mathics.core.numbers import machine_epsilon diff --git a/mathics/builtin/colors/color_operations.py b/mathics/builtin/colors/color_operations.py index 94580a119..6abc6d4fa 100644 --- a/mathics/builtin/colors/color_operations.py +++ b/mathics/builtin/colors/color_operations.py @@ -8,9 +8,7 @@ import itertools from math import floor -from mathics.builtin.base import ( - Builtin, -) +from mathics.builtin.base import Builtin from mathics.builtin.colors.color_directives import ( _Color, ColorError, @@ -18,14 +16,15 @@ ) from mathics.builtin.colors.color_internals import convert_color -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.atoms import ( Integer, Rational, Real, - Symbol, - SymbolList, ) +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import SymbolList + from mathics.builtin.drawing.image import _ImageBuiltin, Image _image_requires = ("numpy", "PIL") diff --git a/mathics/builtin/colors/named_colors.py b/mathics/builtin/colors/named_colors.py index 5d8cc3191..eddba4ef8 100644 --- a/mathics/builtin/colors/named_colors.py +++ b/mathics/builtin/colors/named_colors.py @@ -6,7 +6,7 @@ from mathics.builtin.base import Builtin -from mathics.core.expression import strip_context +from mathics.core.symbols import strip_context from mathics.version import __version__ # noqa used in loading to check consistency. diff --git a/mathics/builtin/comparison.py b/mathics/builtin/comparison.py index dbbd5bd88..156bad948 100644 --- a/mathics/builtin/comparison.py +++ b/mathics/builtin/comparison.py @@ -16,15 +16,17 @@ from mathics.builtin.numeric import apply_N from mathics.builtin.numbers.constants import mp_convert_constant -from mathics.core.expression import ( +from mathics.core.expression import Expression +from mathics.core.atoms import ( COMPARE_PREC, Complex, - Expression, Integer, Integer0, Integer1, Number, - Symbol, +) +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( SymbolFalse, SymbolTrue, SymbolDirectedInfinity, diff --git a/mathics/builtin/compilation.py b/mathics/builtin/compilation.py index f5d59b476..f8308ae29 100644 --- a/mathics/builtin/compilation.py +++ b/mathics/builtin/compilation.py @@ -15,14 +15,15 @@ from mathics.builtin.box.compilation import CompiledCodeBox from mathics.builtin.numeric import apply_N from mathics.core.evaluation import Evaluation -from mathics.core.expression import ( - Atom, - Expression, +from mathics.core.expression import Expression +from mathics.core.symbols import Atom, Symbol + +from mathics.core.atoms import ( Integer, String, - Symbol, from_python, ) + from types import FunctionType diff --git a/mathics/builtin/compile/ir.py b/mathics/builtin/compile/ir.py index 63275c9c1..aefe95d30 100644 --- a/mathics/builtin/compile/ir.py +++ b/mathics/builtin/compile/ir.py @@ -4,7 +4,9 @@ from llvmlite import ir import ctypes -from mathics.core.expression import Expression, Integer, Symbol, Real +from mathics.core.expression import Expression +from mathics.core.atoms import Integer, Real +from mathics.core.symbols import Symbol from mathics.builtin.compile.types import int_type, real_type, bool_type, void_type from mathics.builtin.compile.utils import pairwise, llvm_to_ctype from mathics.builtin.compile.base import CompileError diff --git a/mathics/builtin/compress.py b/mathics/builtin/compress.py index 693e8865d..64976c21f 100644 --- a/mathics/builtin/compress.py +++ b/mathics/builtin/compress.py @@ -6,9 +6,7 @@ import zlib from mathics.builtin.base import Builtin -from mathics.core.expression import ( - String, -) +from mathics.core.atoms import String class Compress(Builtin): diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index 459493c4a..383423337 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -16,15 +16,17 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.core.expression import ( - Expression, - Real, - Symbol, - SymbolAborted, - SymbolInfinity, +from mathics.core.expression import Expression +from mathics.core.atoms import ( String, Integer, from_python, + Real, +) +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( + SymbolAborted, + SymbolInfinity, ) from mathics.core.evaluation import TimeoutInterrupt, run_with_timeout_and_stack diff --git a/mathics/builtin/distance/stringdata.py b/mathics/builtin/distance/stringdata.py index d65c874ef..6fb4ee6be 100644 --- a/mathics/builtin/distance/stringdata.py +++ b/mathics/builtin/distance/stringdata.py @@ -11,12 +11,9 @@ from mathics.builtin.base import Builtin -from mathics.core.expression import ( - Expression, - Integer, - String, - SymbolTrue, -) +from mathics.core.expression import Expression +from mathics.core.atoms import Integer, String +from mathics.core.systemsymbols import SymbolTrue # Levenshtein's algorithm is defined by the following construction: diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 56335be8e..f2c07fa36 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -7,7 +7,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.core.expression import Real, Integer, Rational +from mathics.core.atoms import Real, Integer, Rational from mathics.builtin.base import Builtin from mathics.builtin.colors.color_directives import RGBColor diff --git a/mathics/builtin/drawing/graphics_internals.py b/mathics/builtin/drawing/graphics_internals.py index 313b266e9..b54b8dde1 100644 --- a/mathics/builtin/drawing/graphics_internals.py +++ b/mathics/builtin/drawing/graphics_internals.py @@ -13,7 +13,7 @@ # Signals to Mathics doc processing not to include this module in its documentation. no_doc = True -from mathics.core.expression import system_symbols_dict +from mathics.core.symbols import system_symbols_dict class _GraphicsElement(InstanceableBuiltin): diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 96d943507..40f4663c2 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -8,21 +8,24 @@ from mathics.builtin.base import Builtin, AtomBuiltin, Test, String from mathics.builtin.box.image import ImageBox -from mathics.core.expression import ( +from mathics.core.atoms import ( Atom, - Expression, Integer, Integer0, Integer1, Rational, Real, MachineReal, - Symbol, + from_python, +) +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( SymbolNull, SymbolList, SymbolRule, - from_python, ) + from mathics.builtin.colors.color_internals import ( convert_color, colorspaces as known_colorspaces, diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 76ff8ad69..a340bb498 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -12,15 +12,17 @@ import palettable from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.atoms import ( Real, MachineReal, - Symbol, String, Integer, Integer0, from_python, +) +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( SymbolList, SymbolN, SymbolRule, diff --git a/mathics/builtin/drawing/splines.py b/mathics/builtin/drawing/splines.py index 7ec6346d3..62d8ab147 100644 --- a/mathics/builtin/drawing/splines.py +++ b/mathics/builtin/drawing/splines.py @@ -5,9 +5,7 @@ A Spline is a mathematical function used for interpolation or smoothing. Splines are used both in graphics and computations """ -from mathics.builtin.base import ( - Builtin, -) +from mathics.builtin.base import Builtin from mathics.version import __version__ # noqa used in loading to check consistency. diff --git a/mathics/builtin/evaluation.py b/mathics/builtin/evaluation.py index 708698fd4..e6abd1cab 100644 --- a/mathics/builtin/evaluation.py +++ b/mathics/builtin/evaluation.py @@ -3,7 +3,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Predefined, Builtin -from mathics.core.expression import Integer +from mathics.core.atoms import Integer from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit diff --git a/mathics/builtin/fileformats/htmlformat.py b/mathics/builtin/fileformats/htmlformat.py index afc087644..59b5b700d 100644 --- a/mathics/builtin/fileformats/htmlformat.py +++ b/mathics/builtin/fileformats/htmlformat.py @@ -13,7 +13,9 @@ from mathics.builtin.base import Builtin from mathics.builtin.files_io.files import MathicsOpen -from mathics.core.expression import Expression, String, Symbol, from_python +from mathics.core.expression import Expression +from mathics.core.atoms import String, from_python +from mathics.core.symbols import Symbol from mathics.builtin.base import MessageException from io import BytesIO diff --git a/mathics/builtin/fileformats/xmlformat.py b/mathics/builtin/fileformats/xmlformat.py index 5254cf9dc..3bd7e5f06 100644 --- a/mathics/builtin/fileformats/xmlformat.py +++ b/mathics/builtin/fileformats/xmlformat.py @@ -9,13 +9,11 @@ from mathics.builtin.base import Builtin from mathics.builtin.files_io.files import MathicsOpen -from mathics.core.expression import ( - Expression, - String, - Symbol, - SymbolFailed, - from_python, -) +from mathics.core.expression import Expression +from mathics.core.atoms import String, from_python +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import SymbolFailed + from mathics.builtin.base import MessageException from io import BytesIO diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 0a496506f..856768ef2 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -32,21 +32,23 @@ ) -from mathics.core.expression import ( - BoxError, +from mathics.core.expression import BoxError, Expression +from mathics.core.atoms import ( Complex, - Expression, Integer, MachineReal, Real, String, - Symbol, + from_mpmath, + from_python, +) +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( SymbolFailed, SymbolNull, SymbolTrue, - from_mpmath, - from_python, ) + from mathics.core.numbers import dps from mathics.core.streams import ( path_search, diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py index d67ca154b..be8af5d3c 100644 --- a/mathics/builtin/files_io/filesystem.py +++ b/mathics/builtin/files_io/filesystem.py @@ -14,17 +14,15 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.core.expression import ( - Expression, - String, - Symbol, +from mathics.core.expression import Expression +from mathics.core.atoms import String, from_python +from mathics.core.symbols import Symbol, valid_context_name +from mathics.core.systemsymbols import ( SymbolFailed, SymbolFalse, SymbolNull, SymbolTrue, SymbolList, - from_python, - valid_context_name, ) import mathics.core.streams diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index e7d2439d4..24807bf40 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -6,14 +6,15 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.core.expression import ( +from mathics.core.atoms import ( ByteArrayAtom, + from_python, +) +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol, strip_context +from mathics.core.systemsymbols import ( SymbolList, SymbolRule, - Expression, - from_python, - strip_context, - Symbol, SymbolFailed, SymbolNull, ) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 16f09eac1..d6c97a079 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -35,16 +35,20 @@ ) from mathics.builtin.options import options_to_rules -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.symbols import ( + Symbol, + system_symbols, + system_symbols_dict, +) +from mathics.core.atoms import ( Integer, Rational, Real, - Symbol, +) +from mathics.core.systemsymbols import ( SymbolList, SymbolMakeBoxes, - system_symbols, - system_symbols_dict, ) from mathics.core.formatter import lookup_method diff --git a/mathics/builtin/inference.py b/mathics/builtin/inference.py index 817c4ec9e..8d70087ab 100644 --- a/mathics/builtin/inference.py +++ b/mathics/builtin/inference.py @@ -2,8 +2,8 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.systemsymbols import ( SymbolTrue, SymbolFalse, ) diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index 95b999122..522a8314f 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -25,17 +25,19 @@ from mathics.builtin.comparison import expr_min from mathics.builtin.lists import list_boxes from mathics.builtin.options import options_to_rules -from mathics.core.expression import ( - Expression, + +from mathics.core.expression import Expression, BoxError +from mathics.core.symbols import Symbol +from mathics.core.atoms import ( String, StringFromPython, - Symbol, Integer, Real, - BoxError, from_python, MachineReal, PrecisionReal, +) +from mathics.core.systemsymbols import ( SymbolList, SymbolMakeBoxes, SymbolRule, diff --git a/mathics/builtin/intfns/combinatorial.py b/mathics/builtin/intfns/combinatorial.py index dc8f69210..54a0c9b56 100644 --- a/mathics/builtin/intfns/combinatorial.py +++ b/mathics/builtin/intfns/combinatorial.py @@ -11,7 +11,10 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin -from mathics.core.expression import Expression, Integer, Symbol, SymbolTrue, SymbolFalse +from mathics.core.expression import Expression +from mathics.core.atoms import Integer +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import SymbolTrue, SymbolFalse from mathics.builtin.arithmetic import _MPMathFunction from itertools import combinations diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index 9a6c6d4bc..c26d958ba 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -9,11 +9,9 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin, Test, SympyFunction -from mathics.core.expression import ( - Expression, - Integer, - Symbol, -) +from mathics.core.expression import Expression +from mathics.core.atoms import Integer +from mathics.core.symbols import Symbol class CoprimeQ(Builtin): diff --git a/mathics/builtin/intfns/recurrence.py b/mathics/builtin/intfns/recurrence.py index 3cfa61fed..b4e70905b 100644 --- a/mathics/builtin/intfns/recurrence.py +++ b/mathics/builtin/intfns/recurrence.py @@ -10,7 +10,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin -from mathics.core.expression import Integer +from mathics.core.atoms import Integer from mathics.builtin.arithmetic import _MPMathFunction diff --git a/mathics/builtin/list/associations.py b/mathics/builtin/list/associations.py index 8226b5080..51d152c8d 100644 --- a/mathics/builtin/list/associations.py +++ b/mathics/builtin/list/associations.py @@ -16,11 +16,11 @@ from mathics.builtin.lists import list_boxes -from mathics.core.expression import ( - Expression, - Integer, +from mathics.core.expression import Expression +from mathics.core.atoms import Integer +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( SymbolAssociation, - Symbol, SymbolMakeBoxes, SymbolList, ) diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index a071bf43b..cfa96be6f 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -21,10 +21,10 @@ from mathics.core.expression import ( Expression, - Integer, - SymbolList, structure, ) +from mathics.core.atoms import Integer +from mathics.core.systemsymbols import SymbolList class Array(Builtin): diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index 62d80c3ba..e3dbde763 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -31,11 +31,10 @@ from mathics.builtin.base import MessageException -from mathics.core.expression import ( - Expression, - Integer, - Integer0, - Symbol, +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.atoms import Integer, Integer0 +from mathics.core.systemsymbols import ( SymbolFailed, SymbolList, SymbolMakeBoxes, diff --git a/mathics/builtin/list/rearrange.py b/mathics/builtin/list/rearrange.py index 561f20ed8..1fc1f2cc8 100644 --- a/mathics/builtin/list/rearrange.py +++ b/mathics/builtin/list/rearrange.py @@ -22,10 +22,10 @@ from mathics.core.expression import ( Expression, - Integer, - SymbolList, structure, ) +from mathics.core.atoms import Integer +from mathics.core.systemsymbols import SymbolList def _is_sameq(same_test): diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index d229c7c99..e34f58fea 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -45,26 +45,29 @@ from mathics.core.convert import from_sympy from mathics.core.evaluation import BreakInterrupt, ContinueInterrupt, ReturnInterrupt -from mathics.core.expression import ( + +from mathics.core.expression import Expression, structure +from mathics.core.atoms import ( ByteArrayAtom, - Expression, Integer, Integer0, Number, Real, String, - Symbol, + from_python, + machine_precision, + min_prec, +) + +from mathics.core.symbols import Symbol, strip_context + +from mathics.core.systemsymbols import ( SymbolByteArray, SymbolFailed, SymbolList, SymbolMakeBoxes, SymbolRule, SymbolSequence, - from_python, - machine_precision, - min_prec, - strip_context, - structure, ) @@ -1375,7 +1378,7 @@ class LeafCount(Builtin): def apply(self, expr, evaluation): "LeafCount[expr___]" - from mathics.core.expression import Rational, Complex + from mathics.core.atoms import Rational, Complex leaves = [] diff --git a/mathics/builtin/logic.py b/mathics/builtin/logic.py index c23c9703c..f35745253 100644 --- a/mathics/builtin/logic.py +++ b/mathics/builtin/logic.py @@ -3,9 +3,9 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import BinaryOperator, Predefined, PrefixOperator, Builtin from mathics.builtin.lists import InvalidLevelspecError, python_levelspec, walk_levels -from mathics.core.expression import ( - Expression, - Symbol, +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( SymbolTrue, SymbolFalse, ) diff --git a/mathics/builtin/manipulate.py b/mathics/builtin/manipulate.py index 5cf8d80da..3947ade4f 100755 --- a/mathics/builtin/manipulate.py +++ b/mathics/builtin/manipulate.py @@ -1,12 +1,14 @@ # -*- coding: utf-8 -*- from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.core.expression import String, strip_context + from mathics import settings from mathics.core.evaluation import Output from mathics.builtin.base import Builtin -from mathics.core.expression import Expression, Symbol, Integer, from_python +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol, strip_context +from mathics.core.atoms import Integer, from_python, String try: from ipykernel.kernelbase import Kernel diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index bac364fd6..202301448 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -7,15 +7,22 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin -from mathics.core.expression import ( +from mathics.core.expression import Expression + +from mathics.core.symbols import ( Atom, - Expression, + Symbol, +) + +from mathics.core.atoms import ( Integer, Integer0, Integer1, RationalOneHalf, Number, - Symbol, +) + +from mathics.core.systemsymbols import ( SymbolFalse, SymbolList, SymbolNull, diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index ad35738f0..a60e4b9f0 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -6,8 +6,8 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin, PostfixOperator, SympyFunction -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.atoms import ( String, Integer, Integer0, @@ -15,12 +15,14 @@ Number, Rational, Real, + from_python, +) +from mathics.core.systemsymbols import ( SymbolTrue, SymbolFalse, SymbolList, SymbolRule, SymbolUndefined, - from_python, ) from mathics.core.convert import sympy_symbol_prefix, SympyExpression, from_sympy from mathics.core.rules import Pattern diff --git a/mathics/builtin/numbers/constants.py b/mathics/builtin/numbers/constants.py index 21f4f5e5f..54555a873 100644 --- a/mathics/builtin/numbers/constants.py +++ b/mathics/builtin/numbers/constants.py @@ -14,12 +14,14 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Predefined, SympyObject -from mathics.core.expression import ( - MachineReal, - PrecisionReal, +from mathics.core.symbols import ( Symbol, strip_context, ) +from mathics.core.atoms import ( + MachineReal, + PrecisionReal, +) from mathics.core.numbers import get_precision, PrecisionValueError, machine_precision diff --git a/mathics/builtin/numbers/exptrig.py b/mathics/builtin/numbers/exptrig.py index d25e736ce..54d22709a 100644 --- a/mathics/builtin/numbers/exptrig.py +++ b/mathics/builtin/numbers/exptrig.py @@ -13,13 +13,13 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.atoms import ( Real, Integer, Integer0, - Symbol, ) +from mathics.core.symbols import Symbol from mathics.builtin.numeric import Fold from mathics.builtin.arithmetic import _MPMathFunction diff --git a/mathics/builtin/numbers/integer.py b/mathics/builtin/numbers/integer.py index 1bdb68916..c0982a6e0 100644 --- a/mathics/builtin/numbers/integer.py +++ b/mathics/builtin/numbers/integer.py @@ -12,7 +12,8 @@ from mathics.builtin.base import Builtin, SympyFunction from mathics.core.convert import from_sympy -from mathics.core.expression import Integer, Integer0, String, Expression +from mathics.core.expression import Expression +from mathics.core.atoms import Integer, Integer0, String class Floor(SympyFunction): diff --git a/mathics/builtin/numbers/linalg.py b/mathics/builtin/numbers/linalg.py index d4be70eed..fad41dbc0 100644 --- a/mathics/builtin/numbers/linalg.py +++ b/mathics/builtin/numbers/linalg.py @@ -11,7 +11,9 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin from mathics.core.convert import from_sympy -from mathics.core.expression import Expression, Integer, Symbol, Real, from_mpmath +from mathics.core.expression import Expression +from mathics.core.atoms import Integer, Real, from_mpmath +from mathics.core.symbols import Symbol def matrix_data(m): diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index 739175d1c..fdf66ed74 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -8,12 +8,12 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin, SympyFunction -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.atoms import ( Integer, Integer0, Rational, - Symbol, from_python, ) from mathics.core.convert import from_sympy, SympyPrime diff --git a/mathics/builtin/numbers/randomnumbers.py b/mathics/builtin/numbers/randomnumbers.py index e45e1dc28..4542abf39 100644 --- a/mathics/builtin/numbers/randomnumbers.py +++ b/mathics/builtin/numbers/randomnumbers.py @@ -16,7 +16,9 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin from mathics.builtin.numpy_utils import instantiate_elements, stack -from mathics.core.expression import Integer, String, Symbol, Real, Expression, Complex +from mathics.core.atoms import Integer, String, Real, Complex +from mathics.core.symbols import Symbol +from mathics.core.expression import Expression try: import numpy diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 4e74af778..70abf6658 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -28,9 +28,10 @@ from mathics.builtin.base import Builtin, Predefined from mathics.core.convert import from_sympy -from mathics.core.expression import ( +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.atoms import ( Complex, - Expression, Integer, Integer0, MachineReal, @@ -38,13 +39,14 @@ Rational, Real, String, - Symbol, + from_python, +) +from mathics.core.systemsymbols import ( SymbolFalse, SymbolTrue, SymbolList, SymbolMachinePrecision, SymbolN, - from_python, ) from mathics.core.numbers import ( diff --git a/mathics/builtin/optimization.py b/mathics/builtin/optimization.py index f0f9729bf..cf2e34323 100644 --- a/mathics/builtin/optimization.py +++ b/mathics/builtin/optimization.py @@ -10,7 +10,8 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin -from mathics.core.expression import Expression, from_python +from mathics.core.expression import Expression +from mathics.core.atoms import from_python from mathics.core.convert import from_sympy import sympy diff --git a/mathics/builtin/options.py b/mathics/builtin/options.py index 877945149..0bcaaa34e 100644 --- a/mathics/builtin/options.py +++ b/mathics/builtin/options.py @@ -7,14 +7,17 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin, Test, get_option -from mathics.core.expression import ( +from mathics.core.symbols import ( Symbol, - String, - Expression, - get_default_value, ensure_context, strip_context, ) +from mathics.core.expression import ( + Expression, + get_default_value, +) +from mathics.core.atoms import String + from mathics.builtin.drawing.image import Image diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 4c04dfb5c..657bb1e1c 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -43,15 +43,19 @@ from mathics.builtin.lists import python_levelspec, InvalidLevelspecError from mathics.builtin.numeric import apply_N -from mathics.core.expression import ( +from mathics.core.symbols import ( Atom, - String, Symbol, - Expression, +) +from mathics.core.expression import Expression +from mathics.core.atoms import ( + String, Number, Integer, Rational, Real, +) +from mathics.core.systemsymbols import ( SymbolFalse, SymbolList, SymbolTrue, diff --git a/mathics/builtin/physchemdata.py b/mathics/builtin/physchemdata.py index 52b11d1b4..b59b466eb 100644 --- a/mathics/builtin/physchemdata.py +++ b/mathics/builtin/physchemdata.py @@ -11,13 +11,12 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin -from mathics.core.expression import ( - Expression, - from_python, +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol, strip_context +from mathics.core.atoms import ( Integer, - Symbol, String, - strip_context, + from_python, ) diff --git a/mathics/builtin/procedural.py b/mathics/builtin/procedural.py index e7c2937ca..146ea4d26 100644 --- a/mathics/builtin/procedural.py +++ b/mathics/builtin/procedural.py @@ -14,10 +14,10 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin, BinaryOperator -from mathics.core.expression import ( - Expression, - Symbol, - from_python, +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.atoms import from_python +from mathics.core.systemsymbols import ( SymbolTrue, SymbolFalse, ) diff --git a/mathics/builtin/quantities.py b/mathics/builtin/quantities.py index 7dc421480..a37b1fa52 100644 --- a/mathics/builtin/quantities.py +++ b/mathics/builtin/quantities.py @@ -2,14 +2,14 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin, Test -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.atoms import ( String, Integer, Real, - Symbol, Number, ) +from mathics.core.symbols import Symbol from pint import UnitRegistry diff --git a/mathics/builtin/scoping.py b/mathics/builtin/scoping.py index 36e5c780c..8e48c3630 100644 --- a/mathics/builtin/scoping.py +++ b/mathics/builtin/scoping.py @@ -2,13 +2,16 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin, Predefined -from mathics.core.expression import ( - Expression, - String, +from mathics.core.expression import Expression +from mathics.core.symbols import ( Symbol, - Integer, fully_qualified_symbol_name, ) +from mathics.core.atoms import ( + String, + Integer, +) + from mathics.core.rules import Rule from mathics.core.evaluation import Evaluation diff --git a/mathics/builtin/sparse.py b/mathics/builtin/sparse.py index c1a11be79..92d6bf27d 100644 --- a/mathics/builtin/sparse.py +++ b/mathics/builtin/sparse.py @@ -8,16 +8,11 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.lists import walk_parts -from mathics.builtin.base import ( - Builtin, -) - -from mathics.core.expression import ( - Expression, - Integer, - Integer0, - Symbol, -) +from mathics.builtin.base import Builtin + +from mathics.core.expression import Expression +from mathics.core.atoms import Integer, Integer0 +from mathics.core.symbols import Symbol class SparseArray(Builtin): diff --git a/mathics/builtin/specialfns/bessel.py b/mathics/builtin/specialfns/bessel.py index 8ca6aa06c..10cac9d89 100644 --- a/mathics/builtin/specialfns/bessel.py +++ b/mathics/builtin/specialfns/bessel.py @@ -9,7 +9,7 @@ from mathics.builtin.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin -from mathics.core.expression import from_mpmath +from mathics.core.atoms import from_mpmath from mathics.core.numbers import machine_precision, get_precision, PrecisionValueError from mathics.core.numbers import prec as _prec diff --git a/mathics/builtin/specialfns/gamma.py b/mathics/builtin/specialfns/gamma.py index 0c05bde36..4e821e79b 100644 --- a/mathics/builtin/specialfns/gamma.py +++ b/mathics/builtin/specialfns/gamma.py @@ -6,7 +6,8 @@ from mathics.builtin.arithmetic import _MPMathMultiFunction from mathics.builtin.base import SympyFunction -from mathics.core.expression import Expression, Integer0 +from mathics.core.expression import Expression +from mathics.core.atoms import Integer0 class Gamma(_MPMathMultiFunction): diff --git a/mathics/builtin/specialfns/othogonal.py b/mathics/builtin/specialfns/othogonal.py index e1dd16fbe..ba0488b83 100644 --- a/mathics/builtin/specialfns/othogonal.py +++ b/mathics/builtin/specialfns/othogonal.py @@ -5,7 +5,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.arithmetic import _MPMathFunction -from mathics.core.expression import Integer0 +from mathics.core.atoms import Integer0 class ChebyshevT(_MPMathFunction): diff --git a/mathics/builtin/specialfns/zeta.py b/mathics/builtin/specialfns/zeta.py index 38cfc6ba4..4563cefd9 100644 --- a/mathics/builtin/specialfns/zeta.py +++ b/mathics/builtin/specialfns/zeta.py @@ -9,7 +9,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.arithmetic import _MPMathFunction -from mathics.core.expression import from_mpmath +from mathics.core.atoms import from_mpmath class LerchPhi(_MPMathFunction): diff --git a/mathics/builtin/string/characters.py b/mathics/builtin/string/characters.py index 78fae72aa..692cf9951 100644 --- a/mathics/builtin/string/characters.py +++ b/mathics/builtin/string/characters.py @@ -7,11 +7,9 @@ from mathics.builtin.base import Builtin, Test -from mathics.core.expression import ( - Expression, - String, - SymbolList, -) +from mathics.core.expression import Expression +from mathics.core.atoms import String +from mathics.core.systemsymbols import SymbolList class Characters(Builtin): diff --git a/mathics/builtin/string/charcodes.py b/mathics/builtin/string/charcodes.py index d62150d49..9014f719c 100644 --- a/mathics/builtin/string/charcodes.py +++ b/mathics/builtin/string/charcodes.py @@ -8,13 +8,13 @@ from mathics.builtin.base import Builtin -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.atoms import ( Integer, Integer1, String, - SymbolList, ) +from mathics.core.systemsymbols import SymbolList from mathics.builtin.strings import to_python_encoding diff --git a/mathics/builtin/string/operations.py b/mathics/builtin/string/operations.py index 7abe64804..d4867e977 100644 --- a/mathics/builtin/string/operations.py +++ b/mathics/builtin/string/operations.py @@ -12,17 +12,18 @@ BinaryOperator, Builtin, ) -from mathics.core.expression import ( - Expression, - Integer, - Integer1, - String, - Symbol, +from mathics.core.expression import Expression, string_list +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( SymbolFalse, SymbolList, SymbolTrue, +) +from mathics.core.atoms import ( + Integer, + Integer1, + String, from_python, - string_list, ) from mathics.builtin.lists import python_seq, convert_seq from mathics.builtin.strings import ( diff --git a/mathics/builtin/string/patterns.py b/mathics/builtin/string/patterns.py index b1cccc7f3..ef6cd22e0 100644 --- a/mathics/builtin/string/patterns.py +++ b/mathics/builtin/string/patterns.py @@ -9,10 +9,12 @@ from mathics.builtin.base import BinaryOperator, Builtin -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.atoms import ( Integer1, String, +) +from mathics.core.systemsymbols import ( SymbolFalse, SymbolList, SymbolTrue, diff --git a/mathics/builtin/strings.py b/mathics/builtin/strings.py index 0b0434a44..15804bf26 100644 --- a/mathics/builtin/strings.py +++ b/mathics/builtin/strings.py @@ -18,18 +18,21 @@ Predefined, PrefixOperator, ) -from mathics.core.expression import ( - Expression, - Symbol, +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( SymbolFailed, SymbolFalse, SymbolTrue, SymbolList, +) +from mathics.core.atoms import ( String, Integer, Integer0, Integer1, ) + from mathics.core.parser import MathicsFileLineFeeder, parse from mathics.settings import SYSTEM_CHARACTER_ENCODING from mathics_scanner import TranslateError diff --git a/mathics/builtin/structure.py b/mathics/builtin/structure.py index 638d45cf7..efd13643f 100644 --- a/mathics/builtin/structure.py +++ b/mathics/builtin/structure.py @@ -12,19 +12,23 @@ MessageException, PartRangeError, ) -from mathics.core.expression import ( - Expression, - String, +from mathics.core.expression import Expression +from mathics.core.symbols import ( Symbol, + strip_context, +) +from mathics.core.systemsymbols import ( SymbolNull, SymbolFalse, SymbolTrue, SymbolList, +) +from mathics.core.atoms import ( + String, Integer, Integer0, Integer1, Rational, - strip_context, ) from mathics.core.rules import Pattern diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index 8983bf2e0..f7510c742 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -13,16 +13,18 @@ import subprocess from mathics.version import __version__ -from mathics.core.expression import ( - Expression, +from mathics.core.symbols import strip_context +from mathics.core.expression import Expression +from mathics.core.atoms import ( Integer, Integer0, Real, String, +) +from mathics.core.systemsymbols import ( SymbolFailed, SymbolList, SymbolRule, - strip_context, ) from mathics.builtin.base import Builtin, Predefined from mathics import version_string diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index d9c3a9bc0..c3c2f58fe 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -13,11 +13,13 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin, BinaryOperator -from mathics.core.expression import ( - Expression, +from mathics.core.expression import Expression +from mathics.core.atoms import ( Integer, Integer0, String, +) +from mathics.core.systemsymbols import ( SymbolTrue, SymbolFalse, ) diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index 1b4afe079..421a1300f 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -13,12 +13,19 @@ from mathics.core.formatter import encode_mathml, encode_tex, extra_operators from mathics.core.symbols import ( Atom, + BaseExpression, Symbol, system_symbols, fully_qualified_symbol_name, ) -from mathics.core.systemsymbols import SymbolNull, SymbolList -from mathics.core.numbers import dps, prec, min_prec, machine_precision +from mathics.core.systemsymbols import ( + SymbolTrue, + SymbolFalse, + SymbolNull, + SymbolList, + SymbolByteArray, +) +from mathics.core.numbers import dps, get_type, prec, min_prec, machine_precision import base64 # Imperical number that seems to work. @@ -928,3 +935,62 @@ def __new__(cls, value): if math.inf == value: self.value = "math.inf" return self + + +def from_python(arg): + """Converts a Python expression into a Mathics expression. + + TODO: I think there are number of subtleties to be explained here. + In particular, the expression might beeen the result of evaluation + a sympy expression which contains sympy symbols. + + If the end result is to go back into Mathics for further + evaluation, then probably no problem. However if the end result + is produce say a Python string, then at a minimum we may want to + convert backtick (context) symbols into some Python identifier + symbol like underscore. + """ + from mathics.builtin.base import BoxConstruct + from mathics.core.expression import Expression + + number_type = get_type(arg) + if arg is None: + return SymbolNull + if isinstance(arg, bool): + return SymbolTrue if arg else SymbolFalse + if isinstance(arg, int) or number_type == "z": + return Integer(arg) + elif isinstance(arg, float) or number_type == "f": + return Real(arg) + elif number_type == "q": + return Rational(arg) + elif isinstance(arg, complex): + return Complex(Real(arg.real), Real(arg.imag)) + elif number_type == "c": + return Complex(arg.real, arg.imag) + elif isinstance(arg, str): + return String(arg) + # if arg[0] == arg[-1] == '"': + # return String(arg[1:-1]) + # else: + # return Symbol(arg) + elif isinstance(arg, dict): + entries = [ + Expression( + "Rule", + from_python(key), + from_python(arg[key]), + ) + for key in arg + ] + return Expression(SymbolList, *entries) + elif isinstance(arg, BaseExpression): + return arg + elif isinstance(arg, BoxConstruct): + return arg + elif isinstance(arg, list) or isinstance(arg, tuple): + return Expression(SymbolList, *[from_python(leaf) for leaf in arg]) + elif isinstance(arg, bytearray) or isinstance(arg, bytes): + return Expression(SymbolByteArray, ByteArrayAtom(arg)) + else: + raise NotImplementedError diff --git a/mathics/core/convert.py b/mathics/core/convert.py index c8a04aa42..9d43cfda9 100644 --- a/mathics/core/convert.py +++ b/mathics/core/convert.py @@ -113,8 +113,9 @@ def eval(cls, n): def from_sympy(expr): from mathics.builtin import sympy_to_mathics - from mathics.core.expression import ( - Symbol, + from mathics.core.expression import Expression + from mathics.core.symbols import Symbol + from mathics.core.atoms import ( Integer, Integer0, Integer1, @@ -122,8 +123,9 @@ def from_sympy(expr): Real, Complex, String, - Expression, MachineReal, + ) + from mathics.core.systemsymbols import ( SymbolNull, SymbolList, ) diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 56bfdf0e3..475051d55 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -11,14 +11,9 @@ from collections import defaultdict import typing - -from mathics.core.expression import ( - Expression, - Symbol, - String, - fully_qualified_symbol_name, - strip_context, -) +from mathics.core.symbols import fully_qualified_symbol_name, strip_context, Symbol +from mathics.core.expression import Expression +from mathics.core.atoms import String from mathics_scanner.tokeniser import full_names_pattern type_compiled_pattern = type(re.compile("a.a")) diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py index d4151300f..88693732f 100644 --- a/mathics/core/evaluation.py +++ b/mathics/core/evaluation.py @@ -12,9 +12,11 @@ from mathics_scanner import TranslateError from mathics import settings -from mathics.core.expression import ( +from mathics.core.symbols import ( ensure_context, KeyComparable, +) +from mathics.core.systemsymbols import ( SymbolAborted, SymbolList, SymbolNull, @@ -482,7 +484,9 @@ def get_quiet_messages(self): return value.leaves def message(self, symbol, tag, *args) -> None: - from mathics.core.expression import String, Symbol, Expression, from_python + from mathics.core.expression import Expression + from mathics.core.atoms import String, from_python + from mathics.core.symbols import Symbol # Allow evaluation.message('MyBuiltin', ...) (assume # System`MyBuiltin) @@ -521,7 +525,7 @@ def message(self, symbol, tag, *args) -> None: self.output.out(self.out[-1]) def print_out(self, text) -> None: - from mathics.core.expression import from_python + from mathics.core.atoms import from_python text = self.format_output(from_python(text), "text") @@ -545,7 +549,7 @@ def error_args(self, symbol, given, *needed) -> None: raise AbortInterrupt def message_args(self, symbol, given, *needed) -> None: - from mathics.core.expression import Symbol + from mathics.core.symbols import Symbol if len(needed) == 1: needed = needed[0] diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 560f7720a..b870cefe8 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -10,26 +10,14 @@ from bisect import bisect_left # from mathics.core.formatter import * -from mathics.core.atoms import ( - ByteArrayAtom, - Number, - Integer, - Rational, - Real, - Complex, - String, -) +from mathics.core.atoms import from_python, Number, Integer from mathics.core.symbols import Atom, BaseExpression, Monomial, Symbol, system_symbols from mathics.core.systemsymbols import ( - SymbolByteArray, - SymbolFalse, SymbolList, SymbolN, - SymbolNull, SymbolSequence, - SymbolTrue, ) -from mathics.core.numbers import get_type, dps +from mathics.core.numbers import dps from mathics.core.convert import sympy_symbol_prefix, SympyExpression # Imperical number that seems to work. @@ -97,64 +85,6 @@ def __str__(self) -> str: return "%s[[%s]]" % (self.parent, self.position) -def from_python(arg): - """Converts a Python expression into a Mathics expression. - - TODO: I think there are number of subtleties to be explained here. - In particular, the expression might beeen the result of evaluation - a sympy expression which contains sympy symbols. - - If the end result is to go back into Mathics for further - evaluation, then probably no problem. However if the end result - is produce say a Python string, then at a minimum we may want to - convert backtick (context) symbols into some Python identifier - symbol like underscore. - """ - from mathics.builtin.base import BoxConstruct - - number_type = get_type(arg) - if arg is None: - return SymbolNull - if isinstance(arg, bool): - return SymbolTrue if arg else SymbolFalse - if isinstance(arg, int) or number_type == "z": - return Integer(arg) - elif isinstance(arg, float) or number_type == "f": - return Real(arg) - elif number_type == "q": - return Rational(arg) - elif isinstance(arg, complex): - return Complex(Real(arg.real), Real(arg.imag)) - elif number_type == "c": - return Complex(arg.real, arg.imag) - elif isinstance(arg, str): - return String(arg) - # if arg[0] == arg[-1] == '"': - # return String(arg[1:-1]) - # else: - # return Symbol(arg) - elif isinstance(arg, dict): - entries = [ - Expression( - "Rule", - from_python(key), - from_python(arg[key]), - ) - for key in arg - ] - return Expression(SymbolList, *entries) - elif isinstance(arg, BaseExpression): - return arg - elif isinstance(arg, BoxConstruct): - return arg - elif isinstance(arg, list) or isinstance(arg, tuple): - return Expression(SymbolList, *[from_python(leaf) for leaf in arg]) - elif isinstance(arg, bytearray) or isinstance(arg, bytes): - return Expression(SymbolByteArray, ByteArrayAtom(arg)) - else: - raise NotImplementedError - - # class KeyComparable(object): # def get_sort_key(self): # raise NotImplementedError diff --git a/mathics/core/numbers.py b/mathics/core/numbers.py index 0fec5eda4..2e41ef9cd 100644 --- a/mathics/core/numbers.py +++ b/mathics/core/numbers.py @@ -54,7 +54,8 @@ def get_precision(value, evaluation) -> typing.Optional[int]: if value.get_name() == "System`MachinePrecision": return None else: - from mathics.core.expression import Symbol, MachineReal + from mathics.core.symbols import Symbol + from mathics.core.atoms import MachineReal dmin = _get_float_inf(Symbol("$MinPrecision"), evaluation) dmax = _get_float_inf(Symbol("$MaxPrecision"), evaluation) diff --git a/mathics/core/parser/convert.py b/mathics/core/parser/convert.py index 86f92dc9c..2ec6d3150 100644 --- a/mathics/core/parser/convert.py +++ b/mathics/core/parser/convert.py @@ -5,7 +5,9 @@ from math import log10 import sympy -import mathics.core.expression as ma +import mathics.core.atoms as maa +import mathics.core.symbols as mas +import mathics.core.expression as mae from mathics.core.parser.ast import Symbol, String, Number, Filename from mathics.core.numbers import machine_precision, reconstruct_digits @@ -169,23 +171,23 @@ def do_convert(self, node): return getattr(self, "_make_" + result[0])(*result[1:]) def _make_Symbol(self, s): - return ma.Symbol(s) + return mas.Symbol(s) def _make_Lookup(self, s): value = self.definitions.lookup_name(s) - return ma.Symbol(value) + return mas.Symbol(value) def _make_String(self, s): - return ma.String(s) + return maa.String(s) def _make_Integer(self, x): - return ma.Integer(x) + return maa.Integer(x) def _make_Rational(self, x, y): - return ma.Rational(x, y) + return maa.Rational(x, y) def _make_MachineReal(self, x): - return ma.MachineReal(x) + return maa.MachineReal(x) def _make_PrecisionReal(self, value, prec): if value[0] == "Rational": @@ -196,10 +198,10 @@ def _make_PrecisionReal(self, value, prec): x = value[1] else: assert False - return ma.PrecisionReal(sympy.Float(x, prec)) + return maa.PrecisionReal(sympy.Float(x, prec)) def _make_Expression(self, head, children): - return ma.Expression(head, *children) + return mae.Expression(head, *children) converter = Converter() diff --git a/mathics/core/parser/util.py b/mathics/core/parser/util.py index add8a3932..281329b05 100644 --- a/mathics/core/parser/util.py +++ b/mathics/core/parser/util.py @@ -6,7 +6,7 @@ from mathics.core.parser.parser import Parser from mathics.core.parser.feed import MathicsSingleLineFeeder from mathics.core.parser.convert import convert -from mathics.core.expression import ensure_context +from mathics.core.symbols import ensure_context parser = Parser() diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py index 8c1035a50..286dd369f 100644 --- a/mathics/core/pattern.py +++ b/mathics/core/pattern.py @@ -3,7 +3,8 @@ # -*- coding: utf-8 -*- -from mathics.core.expression import Expression, system_symbols, ensure_context +from mathics.core.expression import Expression +from mathics.core.symbols import system_symbols, ensure_context from mathics.core.util import subsets, subranges, permutations from itertools import chain diff --git a/mathics/core/read.py b/mathics/core/read.py index 89ded08a3..22553b5e6 100644 --- a/mathics/core/read.py +++ b/mathics/core/read.py @@ -7,7 +7,9 @@ from mathics.builtin.base import MessageException from mathics.builtin.strings import to_python_encoding -from mathics.core.expression import Expression, Integer, String, Symbol +from mathics.core.expression import Expression +from mathics.core.atoms import Integer, String +from mathics.core.symbols import Symbol from mathics.core.streams import Stream, path_search, stream_manager SymbolEndOfFile = Symbol("EndOfFile") diff --git a/mathics/core/rules.py b/mathics/core/rules.py index 829acaff4..beea8cfd9 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -3,7 +3,8 @@ # -*- coding: utf-8 -*- -from mathics.core.expression import Expression, strip_context, KeyComparable +from mathics.core.expression import Expression +from mathics.core.symbols import strip_context, KeyComparable from mathics.core.pattern import Pattern, StopGenerator from mathics.core.util import function_arguments diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 989fa0a0d..ce913a47f 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -8,7 +8,6 @@ from mathics.core.convert import sympy_symbol_prefix -from mathics.core.atoms import Number, String # Imperical number that seems to work. # We have to be able to match mpmath values with sympy values @@ -380,6 +379,7 @@ def get_precision(self): def get_option_values(self, evaluation, allow_symbols=False, stop_on_error=True): from mathics.core.systemsymbols import SymbolList + from mathics.core.atoms import String options = self if options.has_form("List", None): @@ -440,6 +440,7 @@ def round_to_float(self, evaluation=None, permit_complex=False): """ from mathics.core.expression import Expression from mathics.core.systemsymbols import SymbolN + from mathics.core.atoms import String, Number if evaluation is None: value = self @@ -664,6 +665,8 @@ def boxes_to_text(self, **options) -> str: return str(self.name) def atom_to_boxes(self, f, evaluation) -> "String": + from mathics.core.atoms import String + return String(evaluation.definitions.shorten_name(self.name)) def to_sympy(self, **kwargs): diff --git a/test/test_arithmetic.py b/test/test_arithmetic.py index a12344868..b8763a3d0 100644 --- a/test/test_arithmetic.py +++ b/test/test_arithmetic.py @@ -3,7 +3,9 @@ import unittest -from mathics.core.expression import Expression, Integer, Rational, Symbol +from mathics.core.expression import Expression +from mathics.core.atoms import Integer, Rational +from mathics.core.symbols import Symbol from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation diff --git a/test/test_compile.py b/test/test_compile.py index 978d6688a..8a42c3359 100644 --- a/test/test_compile.py +++ b/test/test_compile.py @@ -6,7 +6,9 @@ import io import math -from mathics.core.expression import Expression, Symbol, Integer, MachineReal, String +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.atoms import Integer, MachineReal, String from mathics.builtin.compile import has_llvmlite diff --git a/test/test_formatter/test_asy.py b/test/test_formatter/test_asy.py index e3b2e81cb..992a02ddf 100644 --- a/test/test_formatter/test_asy.py +++ b/test/test_formatter/test_asy.py @@ -1,5 +1,7 @@ import re -from mathics.core.expression import Symbol, Integer0, Integer1, Expression +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.atoms import Integer0, Integer1 from mathics.core.evaluation import Evaluation from mathics.session import MathicsSession from mathics.builtin.inout import MakeBoxes diff --git a/test/test_formatter/test_svg.py b/test/test_formatter/test_svg.py index c9b944484..3c9071c94 100644 --- a/test/test_formatter/test_svg.py +++ b/test/test_formatter/test_svg.py @@ -1,5 +1,7 @@ import re -from mathics.core.expression import Symbol, Integer0, Integer1, Expression +from mathics.core.symbols import Symbol +from mathics.core.atoms import Integer0, Integer1 +from mathics.core.expression import Expression from mathics.core.evaluation import Evaluation from mathics.session import MathicsSession from mathics.builtin.inout import MakeBoxes diff --git a/test/test_hash.py b/test/test_hash.py index dd40bb956..e43857957 100644 --- a/test/test_hash.py +++ b/test/test_hash.py @@ -3,17 +3,18 @@ from mathics.core.evaluation import Evaluation -from mathics.core.expression import ( +from mathics.core.expression import Expression +from mathics.core.atoms import ( Complex, - Expression, Integer, MachineReal, Rational, Real, String, - Symbol, - SymbolFalse, ) +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import SymbolFalse + from mathics.core.definitions import Definitions import sys import unittest diff --git a/test/test_help.py b/test/test_help.py index 96b6b2bc1..1c16a447e 100755 --- a/test/test_help.py +++ b/test/test_help.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from .helper import check_evaluation, evaluate from mathics.builtin.base import Builtin -from mathics.core.expression import Integer0 +from mathics.core.atoms import Integer0 class Builtin1(Builtin): diff --git a/test/test_parser/test_convert.py b/test/test_parser/test_convert.py index cc361938c..52ab0f27e 100644 --- a/test/test_parser/test_convert.py +++ b/test/test_parser/test_convert.py @@ -11,12 +11,12 @@ from mathics.core.definitions import Definitions from mathics.core.parser import parse -from mathics.core.expression import ( - Symbol, +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.atoms import ( Integer, Integer0, Integer1, - Expression, Real, Rational, String, From acb196614bf46cf94ea83d40842aef1dc1cdd161 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 26 Sep 2021 07:21:22 -0300 Subject: [PATCH 144/193] fix gs test --- examples/symbolic_logic/gries_schneider/test_gs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/symbolic_logic/gries_schneider/test_gs.py b/examples/symbolic_logic/gries_schneider/test_gs.py index 2ff8bcf1a..902c7e10c 100644 --- a/examples/symbolic_logic/gries_schneider/test_gs.py +++ b/examples/symbolic_logic/gries_schneider/test_gs.py @@ -2,7 +2,9 @@ # -*- coding: utf-8 -*- -from mathics.core.expression import Expression, Integer, Rational, Symbol +from mathics.core.expression import Expression +from mathics.core.atoms import Integer, Rational +from mathics.core.symbols import Symbol from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation from mathics.core.parser import MathicsSingleLineFeeder, parse From 635d6232cf1c29b11b61e59a5219048f60ca7cae Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 26 Sep 2021 07:35:17 -0300 Subject: [PATCH 145/193] removing redefinitions of variables --- mathics/core/expression.py | 4 ---- mathics/core/symbols.py | 5 ----- 2 files changed, 9 deletions(-) diff --git a/mathics/core/expression.py b/mathics/core/expression.py index b870cefe8..cd6e52216 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -20,10 +20,6 @@ from mathics.core.numbers import dps from mathics.core.convert import sympy_symbol_prefix, SympyExpression -# Imperical number that seems to work. -# We have to be able to match mpmath values with sympy values -COMPARE_PREC = 50 - def fully_qualified_symbol_name(name) -> bool: return ( diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index ce913a47f..8bd12108d 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -9,11 +9,6 @@ from mathics.core.convert import sympy_symbol_prefix -# Imperical number that seems to work. -# We have to be able to match mpmath values with sympy values -COMPARE_PREC = 50 - - # system_symbols('A', 'B', ...) -> ['System`A', 'System`B', ...] def system_symbols(*symbols) -> typing.List[str]: return [ensure_context(s) for s in symbols] From 82d978041d6d0267ebc600ae151d53f9e399f5db Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 26 Sep 2021 08:50:10 -0300 Subject: [PATCH 146/193] SymbolMakeBoxes goes into symbols.py --- mathics/core/symbols.py | 5 ++++- mathics/core/systemsymbols.py | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 8bd12108d..697542bb0 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -358,7 +358,7 @@ def format( from mathics.core.expression import Expression expr = self.do_format(evaluation, form) - result = Expression("MakeBoxes", expr, Symbol(form)).evaluate(evaluation) + result = Expression(SymbolMakeBoxes, expr, Symbol(form)).evaluate(evaluation) return result def is_free(self, form, evaluation) -> bool: @@ -783,3 +783,6 @@ def user_hash(self, update) -> None: def __getnewargs__(self): return (self.name, self.sympy_dummy) + + +SymbolMakeBoxes = Symbol("System`MakeBoxes") diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 5f577caa9..978c6a57d 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -1,7 +1,7 @@ # cython: language_level=3 # -*- coding: utf-8 -*- -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolMakeBoxes # Some common Symbols. This list is sorted in alpabetic order. SymbolAborted = Symbol("$Aborted") @@ -14,7 +14,6 @@ SymbolInfinity = Symbol("Infinity") SymbolList = Symbol("List") SymbolMachinePrecision = Symbol("MachinePrecision") -SymbolMakeBoxes = Symbol("MakeBoxes") SymbolN = Symbol("N") SymbolNull = Symbol("Null") SymbolRule = Symbol("Rule") From 12fc916183b60007c637ded7f6f08dfa232f7605 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 26 Sep 2021 08:55:27 -0300 Subject: [PATCH 147/193] move systemsymbols used in symbols.py to symbols.py --- mathics/core/symbols.py | 15 +++++---------- mathics/core/systemsymbols.py | 21 ++++++++++++--------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 697542bb0..1e177be1a 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -373,7 +373,6 @@ def get_precision(self): return None def get_option_values(self, evaluation, allow_symbols=False, stop_on_error=True): - from mathics.core.systemsymbols import SymbolList from mathics.core.atoms import String options = self @@ -407,7 +406,6 @@ def get_option_values(self, evaluation, allow_symbols=False, stop_on_error=True) def get_rules_list(self): from mathics.core.rules import Rule - from mathics.core.systemsymbols import SymbolList list_expr = self.flatten(SymbolList) list = [] @@ -434,7 +432,6 @@ def round_to_float(self, evaluation=None, permit_complex=False): Try to round to python float. Return None if not possible. """ from mathics.core.expression import Expression - from mathics.core.systemsymbols import SymbolN from mathics.core.atoms import String, Number if evaluation is None: @@ -680,12 +677,6 @@ def to_sympy(self, **kwargs): return builtin.to_sympy(self, **kwargs) def to_python(self, *args, **kwargs): - from mathics.core.systemsymbols import ( - SymbolTrue, - SymbolFalse, - SymbolNull, - SymbolN, - ) from mathics.core.expression import Expression if self == SymbolTrue: @@ -731,7 +722,6 @@ def get_sort_key(self, pattern_sort=False): def equal2(self, rhs: Any) -> Optional[bool]: """Mathics two-argument Equal (==)""" - from mathics.core.systemsymbols import SymbolTrue, SymbolFalse if self.sameQ(rhs): return True @@ -785,4 +775,9 @@ def __getnewargs__(self): return (self.name, self.sympy_dummy) +SymbolFalse = Symbol("System`False") +SymbolList = Symbol("System`List") SymbolMakeBoxes = Symbol("System`MakeBoxes") +SymbolN = Symbol("System`N") +SymbolNull = Symbol("System`Null") +SymbolTrue = Symbol("System`True") diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 978c6a57d..a1688ac7c 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -1,24 +1,27 @@ # cython: language_level=3 # -*- coding: utf-8 -*- -from mathics.core.symbols import Symbol, SymbolMakeBoxes +from mathics.core.symbols import ( + Symbol, + SymbolList, + SymbolMakeBoxes, + SymbolTrue, + SymbolFalse, + SymbolN, + SymbolNull, +) -# Some common Symbols. This list is sorted in alpabetic order. +# Some other common Symbols. This list is sorted in alpabetic order. SymbolAborted = Symbol("$Aborted") SymbolAssociation = Symbol("Association") SymbolByteArray = Symbol("ByteArray") SymbolComplexInfinity = Symbol("ComplexInfinity") SymbolDirectedInfinity = Symbol("DirectedInfinity") SymbolFailed = Symbol("$Failed") -SymbolFalse = Symbol("False") +SymbolGreater = Symbol("Greater") SymbolInfinity = Symbol("Infinity") -SymbolList = Symbol("List") +SymbolLess = Symbol("Less") SymbolMachinePrecision = Symbol("MachinePrecision") -SymbolN = Symbol("N") -SymbolNull = Symbol("Null") SymbolRule = Symbol("Rule") SymbolSequence = Symbol("Sequence") -SymbolTrue = Symbol("True") SymbolUndefined = Symbol("Undefined") -SymbolLess = Symbol("Less") -SymbolGreater = Symbol("Greater") From d4ef46231e83f179e72f1f0aa11f1f15d6ba57a9 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 26 Sep 2021 10:54:03 -0300 Subject: [PATCH 148/193] improving handling of circular reference --- mathics/autoload/formats/Asy/Export.m | 2 +- mathics/core/expression.py | 7 +++ mathics/core/symbols.py | 77 ++++++++++++--------------- 3 files changed, 41 insertions(+), 45 deletions(-) diff --git a/mathics/autoload/formats/Asy/Export.m b/mathics/autoload/formats/Asy/Export.m index 4d6204c2e..52d9f52c9 100644 --- a/mathics/autoload/formats/Asy/Export.m +++ b/mathics/autoload/formats/Asy/Export.m @@ -3,7 +3,7 @@ Begin["System`Convert`Asy`"] Options[AsyExport] = { - "CharacterEncoding" :> $CharacterEncoding, (*Not used by now...*) + "CharacterEncoding" :> $CharacterEncoding (*Not used by now...*) }; AsyExport[strm_OutputStream, expr_, OptionsPattern[]]:= diff --git a/mathics/core/expression.py b/mathics/core/expression.py index cd6e52216..db150decc 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -1379,6 +1379,13 @@ def __getnewargs__(self): return (self._head, self._leaves) +def _create_expression(self, head, *leaves): + return Expression(head, *leaves) + + +BaseExpression.create_expression = _create_expression + + def get_default_value(name, evaluation, k=None, n=None): pos = [] if k is not None: diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 1e177be1a..2ca73ea46 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -90,6 +90,8 @@ class BaseExpression(KeyComparable): pattern_sequence: bool unformatted: Any last_evaluated: Any + # this variable holds a function defined in mathics.core.expression that creates an expression + create_expression: Any def __new__(cls, *args, **kwargs): self = object.__new__(cls) @@ -240,7 +242,6 @@ def do_format(self, evaluation, form): Applies formats associated to the expression and removes superfluous enclosing formats. """ - from mathics.core.expression import Expression formats = system_symbols( "InputForm", @@ -270,7 +271,7 @@ def do_format(self, evaluation, form): # If form is Fullform, return it without changes if form == "System`FullForm": if include_form: - expr = Expression(form, expr) + expr = self.create_expression(form, expr) expr.unformatted = unformatted return expr @@ -278,30 +279,30 @@ def do_format(self, evaluation, form): # so we need to hardlink their format rules: if head == "System`Repeated": if len(leaves) == 1: - return Expression( + return self.create_expression( "System`HoldForm", - Expression( + self.create_expression( "System`Postfix", - Expression("System`List", leaves[0]), + self.create_expression("System`List", leaves[0]), "..", 170, ), ) else: - return Expression("System`HoldForm", expr) + return self.create_expression("System`HoldForm", expr) elif head == "System`RepeatedNull": if len(leaves) == 1: - return Expression( + return self.create_expression( "System`HoldForm", - Expression( + self.create_expression( "System`Postfix", - Expression("System`List", leaves[0]), + self.create_expression("System`List", leaves[0]), "...", 170, ), ) else: - return Expression("System`HoldForm", expr) + return self.create_expression("System`HoldForm", expr) # If expr is not an atom, looks for formats in its definition # and apply them. @@ -321,7 +322,7 @@ def format_expr(expr): if formatted is not None: result = formatted.do_format(evaluation, form) if include_form: - result = Expression(form, result) + result = self.create_expression(form, result) result.unformatted = unformatted return result @@ -340,10 +341,12 @@ def format_expr(expr): ): # print("Not inside graphics or numberform, and not is atom") new_leaves = [leaf.do_format(evaluation, form) for leaf in expr.leaves] - expr = Expression(expr.head.do_format(evaluation, form), *new_leaves) + expr = self.create_expression( + expr.head.do_format(evaluation, form), *new_leaves + ) if include_form: - expr = Expression(form, expr) + expr = self.create_expression(form, expr) expr.unformatted = unformatted return expr finally: @@ -355,10 +358,11 @@ def format( """ Applies formats associated to the expression, and then calls Makeboxes """ - from mathics.core.expression import Expression expr = self.do_format(evaluation, form) - result = Expression(SymbolMakeBoxes, expr, Symbol(form)).evaluate(evaluation) + result = self.create_expression(SymbolMakeBoxes, expr, Symbol(form)).evaluate( + evaluation + ) return result def is_free(self, form, evaluation) -> bool: @@ -431,7 +435,6 @@ def round_to_float(self, evaluation=None, permit_complex=False): """ Try to round to python float. Return None if not possible. """ - from mathics.core.expression import Expression from mathics.core.atoms import String, Number if evaluation is None: @@ -439,53 +442,41 @@ def round_to_float(self, evaluation=None, permit_complex=False): elif isinstance(evaluation, sympy.core.numbers.NaN): return None else: - value = Expression(SymbolN, self).evaluate(evaluation) + value = self.create_expression(SymbolN, self).evaluate(evaluation) if isinstance(value, Number): value = value.round() return value.get_float_value(permit_complex=permit_complex) def __abs__(self) -> "Expression": - from mathics.core.expression import Expression - - return Expression("Abs", self) + return self.create_expression("Abs", self) def __pos__(self): return self def __neg__(self): - from mathics.core.expression import Expression - - return Expression("Times", self, -1) + return self.create_expression("Times", self, -1) def __add__(self, other) -> "Expression": - from mathics.core.expression import Expression - - return Expression("Plus", self, other) + return self.create_expression("Plus", self, other) def __sub__(self, other) -> "Expression": - from mathics.core.expression import Expression - - return Expression("Plus", self, Expression("Times", other, -1)) + return self.create_expression( + "Plus", self, self.create_expression("Times", other, -1) + ) def __mul__(self, other) -> "Expression": - from mathics.core.expression import Expression - - return Expression("Times", self, other) + return self.create_expression("Times", self, other) def __truediv__(self, other) -> "Expression": - from mathics.core.expression import Expression - - return Expression("Divide", self, other) + return self.create_expression("Divide", self, other) def __floordiv__(self, other) -> "Expression": - from mathics.core.expression import Expression - - return Expression("Floor", Expression("Divide", self, other)) + return self.create_expression( + "Floor", self.create_expression("Divide", self, other) + ) def __pow__(self, other) -> "Expression": - from mathics.core.expression import Expression - - return Expression("Power", self, other) + return self.create_expression("Power", self, other) class Monomial(object): @@ -677,8 +668,6 @@ def to_sympy(self, **kwargs): return builtin.to_sympy(self, **kwargs) def to_python(self, *args, **kwargs): - from mathics.core.expression import Expression - if self == SymbolTrue: return True if self == SymbolFalse: @@ -687,7 +676,7 @@ def to_python(self, *args, **kwargs): return None n_evaluation = kwargs.get("n_evaluation") if n_evaluation is not None: - value = Expression(SymbolN, self).evaluate(n_evaluation) + value = self.create_expression(SymbolN, self).evaluate(n_evaluation) return value.to_python() if kwargs.get("python_form", False): From 98e184a110ef9efcafa3e24accef11ad24db92e7 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 26 Sep 2021 11:05:33 -0300 Subject: [PATCH 149/193] flake8 --- examples/symbolic_logic/gries_schneider/test_gs.py | 3 --- mathics/core/atoms.py | 2 +- mathics/core/convert.py | 4 ++-- mathics/core/read.py | 4 ++-- mathics/core/streams.py | 4 ++-- mathics/core/symbols.py | 6 ++---- 6 files changed, 9 insertions(+), 14 deletions(-) diff --git a/examples/symbolic_logic/gries_schneider/test_gs.py b/examples/symbolic_logic/gries_schneider/test_gs.py index 902c7e10c..36802bbe4 100644 --- a/examples/symbolic_logic/gries_schneider/test_gs.py +++ b/examples/symbolic_logic/gries_schneider/test_gs.py @@ -2,9 +2,6 @@ # -*- coding: utf-8 -*- -from mathics.core.expression import Expression -from mathics.core.atoms import Integer, Rational -from mathics.core.symbols import Symbol from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation from mathics.core.parser import MathicsSingleLineFeeder, parse diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index 421a1300f..f0bfced05 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -7,7 +7,7 @@ import re import typing -from typing import Any, Optional +from typing import Any from functools import lru_cache from mathics.core.formatter import encode_mathml, encode_tex, extra_operators diff --git a/mathics/core/convert.py b/mathics/core/convert.py index 9d43cfda9..14fd43a47 100644 --- a/mathics/core/convert.py +++ b/mathics/core/convert.py @@ -106,7 +106,7 @@ def eval(cls, n): if n.is_Integer and n > 0: try: return sympy.prime(n) - except: + except Exception: # n is too big, SymPy doesn't know the n-th prime pass @@ -272,7 +272,7 @@ def from_sympy(expr): try: e = sympy.PurePoly(e) - except: + except Exception: pass return Expression("Root", from_sympy(e), i + 1) diff --git a/mathics/core/read.py b/mathics/core/read.py index 22553b5e6..072372dc2 100644 --- a/mathics/core/read.py +++ b/mathics/core/read.py @@ -30,8 +30,8 @@ ] -### FIXME: All of this is related to Read[] -### it can be moved somewhere else. +# ### FIXME: All of this is related to Read[] +# ### it can be moved somewhere else. class MathicsOpen(Stream): diff --git a/mathics/core/streams.py b/mathics/core/streams.py index d93373bd7..6563172f7 100644 --- a/mathics/core/streams.py +++ b/mathics/core/streams.py @@ -90,13 +90,13 @@ class StreamsManager(object): @staticmethod def get_instance(): """Static access method.""" - if StreamsManager.__instance == None: + if StreamsManager.__instance is None: StreamsManager() return StreamsManager.__instance def __init__(self): """Virtually private constructor.""" - if StreamsManager.__instance != None: + if StreamsManager.__instance is not None: raise Exception("this class is a singleton!") else: StreamsManager.__instance = self diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 2ca73ea46..b81ee0a5f 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -352,9 +352,7 @@ def format_expr(expr): finally: evaluation.dec_recursion_depth() - def format( - self, evaluation, form, **kwargs - ) -> typing.Union["Expression", "Symbol"]: + def format(self, evaluation, form, **kwargs) -> "BaseExpression": """ Applies formats associated to the expression, and then calls Makeboxes """ @@ -435,7 +433,7 @@ def round_to_float(self, evaluation=None, permit_complex=False): """ Try to round to python float. Return None if not possible. """ - from mathics.core.atoms import String, Number + from mathics.core.atoms import Number if evaluation is None: value = self From d4deb5b1d1562135590cfa107e61f6dc9c962397 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 26 Sep 2021 11:12:25 -0300 Subject: [PATCH 150/193] flake8 --- mathics/core/symbols.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index b81ee0a5f..7115d56f5 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -445,7 +445,7 @@ def round_to_float(self, evaluation=None, permit_complex=False): value = value.round() return value.get_float_value(permit_complex=permit_complex) - def __abs__(self) -> "Expression": + def __abs__(self) -> "BaseExpression": return self.create_expression("Abs", self) def __pos__(self): @@ -454,26 +454,26 @@ def __pos__(self): def __neg__(self): return self.create_expression("Times", self, -1) - def __add__(self, other) -> "Expression": + def __add__(self, other) -> "BaseExpression": return self.create_expression("Plus", self, other) - def __sub__(self, other) -> "Expression": + def __sub__(self, other) -> "BaseExpression": return self.create_expression( "Plus", self, self.create_expression("Times", other, -1) ) - def __mul__(self, other) -> "Expression": + def __mul__(self, other) -> "BaseExpression": return self.create_expression("Times", self, other) - def __truediv__(self, other) -> "Expression": + def __truediv__(self, other) -> "BaseExpression": return self.create_expression("Divide", self, other) - def __floordiv__(self, other) -> "Expression": + def __floordiv__(self, other) -> "BaseExpression": return self.create_expression( "Floor", self.create_expression("Divide", self, other) ) - def __pow__(self, other) -> "Expression": + def __pow__(self, other) -> "BaseExpression": return self.create_expression("Power", self, other) From 76de7d15f16ae27317ca7e4bf4d0f5da6105debf Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 26 Sep 2021 11:21:14 -0400 Subject: [PATCH 151/193] Additional import splitting Rename numbers.py to to avoid a numpy conflict. (numpy is probably wrong) --- mathics/__init__.py | 17 ----------------- mathics/builtin/__init__.py | 3 +++ mathics/builtin/arithfns/basic.py | 5 ++--- mathics/builtin/arithmetic.py | 7 ++----- mathics/builtin/assignment.py | 6 ++---- mathics/builtin/attributes.py | 3 +-- mathics/builtin/base.py | 4 ++-- mathics/builtin/box/graphics.py | 2 +- mathics/builtin/colors/color_directives.py | 5 ++--- mathics/builtin/colors/color_operations.py | 3 +-- mathics/builtin/comparison.py | 6 ++---- mathics/builtin/distance/stringdata.py | 2 +- mathics/builtin/drawing/image.py | 4 +--- mathics/builtin/drawing/plot.py | 4 +--- mathics/builtin/files_io/files.py | 6 ++---- mathics/builtin/files_io/filesystem.py | 12 +++++++----- mathics/builtin/files_io/importexport.py | 4 +--- mathics/builtin/graphics.py | 2 +- mathics/builtin/inference.py | 2 +- mathics/builtin/inout.py | 6 +++--- mathics/builtin/intfns/combinatorial.py | 3 +-- mathics/builtin/list/associations.py | 3 +-- mathics/builtin/list/constructing.py | 2 +- mathics/builtin/list/eol.py | 4 +--- mathics/builtin/list/rearrange.py | 2 +- mathics/builtin/lists.py | 5 ++--- mathics/builtin/logic.py | 4 ++-- mathics/builtin/numbers/algebra.py | 10 ++++------ mathics/builtin/numbers/calculus.py | 18 +++++++++++------- mathics/builtin/numbers/constants.py | 2 +- mathics/builtin/numeric.py | 10 +++------- mathics/builtin/patterns.py | 2 +- mathics/builtin/procedural.py | 2 +- mathics/builtin/specialfns/bessel.py | 4 ++-- mathics/builtin/string/characters.py | 2 +- mathics/builtin/string/charcodes.py | 2 +- mathics/builtin/string/operations.py | 4 ++-- mathics/builtin/string/patterns.py | 2 +- mathics/builtin/strings.py | 8 +++++--- mathics/builtin/structure.py | 5 ++--- mathics/builtin/system.py | 4 +++- mathics/builtin/tensors.py | 2 +- mathics/core/atoms.py | 20 ++++++++++---------- mathics/core/convert.py | 4 ++-- mathics/core/evaluation.py | 6 ++++-- mathics/core/expression.py | 15 +++++++++------ mathics/core/{numbers.py => number.py} | 0 mathics/core/parser/convert.py | 2 +- mathics/core/systemsymbols.py | 17 ++++++----------- test/test_hash.py | 3 +-- 50 files changed, 117 insertions(+), 153 deletions(-) rename mathics/core/{numbers.py => number.py} (100%) diff --git a/mathics/__init__.py b/mathics/__init__.py index 14a35c9b1..0309f3431 100644 --- a/mathics/__init__.py +++ b/mathics/__init__.py @@ -8,23 +8,6 @@ import numpy from mathics.version import __version__ -from mathics.core.symbols import Symbol -from mathics.core.atoms import ( - String, - Number, - Integer, - Real, - Complex, - Rational, - from_python, - MachineReal, - PrecisionReal, -) - -from mathics.core.expression import Expression - -from mathics.core.convert import from_sympy - version_info = { "mathics": __version__, diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index 0ee7e10f5..f3cb05a25 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -108,6 +108,9 @@ def import_module(module_name: str, import_name: str): print(e) print(f" Not able to load {module_name}. Check your installation.") print(f" mathics.builtin loads from {__file__[:-11]}") + from trepan.api import debug + + debug() return None if __version__ != module.__version__: diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index 417a10e1a..3975f3c93 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -31,15 +31,14 @@ String, from_mpmath, ) -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolNull from mathics.core.systemsymbols import ( SymbolComplexInfinity, SymbolDirectedInfinity, SymbolInfinity, - SymbolNull, SymbolSequence, ) -from mathics.core.numbers import min_prec, dps +from mathics.core.number import min_prec, dps from mathics.core.convert import from_sympy diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 93c9150c5..0bd8b5007 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -35,14 +35,11 @@ from_mpmath, from_python, ) -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolFalse, SymbolList, SymbolTrue from mathics.core.systemsymbols import ( - SymbolTrue, - SymbolFalse, SymbolUndefined, - SymbolList, ) -from mathics.core.numbers import min_prec, dps, SpecialValueError +from mathics.core.number import min_prec, dps, SpecialValueError from mathics.builtin.lists import _IterationFunction from mathics.core.convert import from_sympy, SympyExpression, sympy_symbol_prefix diff --git a/mathics/builtin/assignment.py b/mathics/builtin/assignment.py index 358f4556a..6d695eef7 100644 --- a/mathics/builtin/assignment.py +++ b/mathics/builtin/assignment.py @@ -12,14 +12,12 @@ from mathics.core.expression import Expression from mathics.core.symbols import ( Symbol, + SymbolNull, valid_context_name, system_symbols, ) -from mathics.core.systemsymbols import ( - SymbolFailed, - SymbolNull, -) +from mathics.core.systemsymbols import SymbolFailed from mathics.core.atoms import String from mathics.core.definitions import PyMathicsLoadException diff --git a/mathics/builtin/attributes.py b/mathics/builtin/attributes.py index 197d2a9e9..7628929d3 100644 --- a/mathics/builtin/attributes.py +++ b/mathics/builtin/attributes.py @@ -13,8 +13,7 @@ from mathics.builtin.base import Predefined, Builtin from mathics.core.expression import Expression -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import SymbolNull +from mathics.core.symbols import Symbol, SymbolNull from mathics.core.atoms import String from mathics.builtin.assignment import get_symbol_list diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 283387334..149c460aa 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -28,11 +28,11 @@ String, ) from mathics.core.expression import Expression -from mathics.core.systemsymbols import ( +from mathics.core.number import get_precision, PrecisionValueError +from mathics.core.symbols import ( SymbolFalse, SymbolTrue, ) -from mathics.core.numbers import get_precision, PrecisionValueError def get_option(options, name, evaluation, pop=False, evaluate=True): diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index 2ce1254b6..53ebea50a 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -42,7 +42,7 @@ Real, String, ) -from mathics.core.systemsymbols import SymbolList +from mathics.core.symbols import SymbolList # Note: has to come before _ArcBox class _RoundBox(_GraphicsElement): diff --git a/mathics/builtin/colors/color_directives.py b/mathics/builtin/colors/color_directives.py index 5edb6d9bc..3a0d307e7 100644 --- a/mathics/builtin/colors/color_directives.py +++ b/mathics/builtin/colors/color_directives.py @@ -23,10 +23,9 @@ from_python, ) -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import SymbolList +from mathics.core.symbols import Symbol, SymbolList -from mathics.core.numbers import machine_epsilon +from mathics.core.number import machine_epsilon def _cie2000_distance(lab1, lab2): diff --git a/mathics/builtin/colors/color_operations.py b/mathics/builtin/colors/color_operations.py index 6abc6d4fa..a0506e03e 100644 --- a/mathics/builtin/colors/color_operations.py +++ b/mathics/builtin/colors/color_operations.py @@ -22,8 +22,7 @@ Rational, Real, ) -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import SymbolList +from mathics.core.symbols import Symbol, SymbolList from mathics.builtin.drawing.image import _ImageBuiltin, Image diff --git a/mathics/builtin/comparison.py b/mathics/builtin/comparison.py index 156bad948..ac6351969 100644 --- a/mathics/builtin/comparison.py +++ b/mathics/builtin/comparison.py @@ -25,15 +25,13 @@ Integer1, Number, ) -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue from mathics.core.systemsymbols import ( - SymbolFalse, - SymbolTrue, SymbolDirectedInfinity, SymbolInfinity, SymbolComplexInfinity, ) -from mathics.core.numbers import dps +from mathics.core.number import dps def cmp(a, b) -> int: diff --git a/mathics/builtin/distance/stringdata.py b/mathics/builtin/distance/stringdata.py index 6fb4ee6be..cc944a041 100644 --- a/mathics/builtin/distance/stringdata.py +++ b/mathics/builtin/distance/stringdata.py @@ -13,7 +13,7 @@ from mathics.core.expression import Expression from mathics.core.atoms import Integer, String -from mathics.core.systemsymbols import SymbolTrue +from mathics.core.symbols import SymbolTrue # Levenshtein's algorithm is defined by the following construction: diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 40f4663c2..e4b95b410 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -19,10 +19,8 @@ from_python, ) from mathics.core.expression import Expression -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolNull, SymbolList from mathics.core.systemsymbols import ( - SymbolNull, - SymbolList, SymbolRule, ) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index a340bb498..76e8a7748 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -21,10 +21,8 @@ Integer0, from_python, ) -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolList, SymbolN from mathics.core.systemsymbols import ( - SymbolList, - SymbolN, SymbolRule, ) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 856768ef2..4d8071d99 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -42,14 +42,12 @@ from_mpmath, from_python, ) -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolNull, SymbolTrue from mathics.core.systemsymbols import ( SymbolFailed, - SymbolNull, - SymbolTrue, ) -from mathics.core.numbers import dps +from mathics.core.number import dps from mathics.core.streams import ( path_search, stream_manager, diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py index be8af5d3c..961033438 100644 --- a/mathics/builtin/files_io/filesystem.py +++ b/mathics/builtin/files_io/filesystem.py @@ -16,16 +16,18 @@ from mathics.core.expression import Expression from mathics.core.atoms import String, from_python -from mathics.core.symbols import Symbol, valid_context_name -from mathics.core.systemsymbols import ( - SymbolFailed, +from mathics.core.symbols import ( + Symbol, SymbolFalse, + SymbolList, SymbolNull, SymbolTrue, - SymbolList, + valid_context_name, +) +from mathics.core.systemsymbols import ( + SymbolFailed, ) -import mathics.core.streams from mathics.core.streams import ( HOME_DIR, PATH_VAR, diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index 24807bf40..dd227f3d9 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -11,12 +11,10 @@ from_python, ) from mathics.core.expression import Expression -from mathics.core.symbols import Symbol, strip_context +from mathics.core.symbols import Symbol, SymbolList, SymbolNull, strip_context from mathics.core.systemsymbols import ( - SymbolList, SymbolRule, SymbolFailed, - SymbolNull, ) from mathics.core.streams import stream_manager diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index d6c97a079..7cfd201df 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -40,6 +40,7 @@ Symbol, system_symbols, system_symbols_dict, + SymbolList, ) from mathics.core.atoms import ( Integer, @@ -47,7 +48,6 @@ Real, ) from mathics.core.systemsymbols import ( - SymbolList, SymbolMakeBoxes, ) diff --git a/mathics/builtin/inference.py b/mathics/builtin/inference.py index 8d70087ab..264c5555c 100644 --- a/mathics/builtin/inference.py +++ b/mathics/builtin/inference.py @@ -3,7 +3,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.core.expression import Expression -from mathics.core.systemsymbols import ( +from mathics.core.symbols import ( SymbolTrue, SymbolFalse, ) diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index 522a8314f..860dfcaa5 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -27,7 +27,8 @@ from mathics.builtin.options import options_to_rules from mathics.core.expression import Expression, BoxError -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolList + from mathics.core.atoms import ( String, StringFromPython, @@ -38,11 +39,10 @@ PrecisionReal, ) from mathics.core.systemsymbols import ( - SymbolList, SymbolMakeBoxes, SymbolRule, ) -from mathics.core.numbers import ( +from mathics.core.number import ( dps, convert_base, machine_precision, diff --git a/mathics/builtin/intfns/combinatorial.py b/mathics/builtin/intfns/combinatorial.py index 54a0c9b56..ed66a9fa2 100644 --- a/mathics/builtin/intfns/combinatorial.py +++ b/mathics/builtin/intfns/combinatorial.py @@ -13,8 +13,7 @@ from mathics.builtin.base import Builtin from mathics.core.expression import Expression from mathics.core.atoms import Integer -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import SymbolTrue, SymbolFalse +from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue from mathics.builtin.arithmetic import _MPMathFunction from itertools import combinations diff --git a/mathics/builtin/list/associations.py b/mathics/builtin/list/associations.py index 51d152c8d..0a0cbafa3 100644 --- a/mathics/builtin/list/associations.py +++ b/mathics/builtin/list/associations.py @@ -18,11 +18,10 @@ from mathics.core.expression import Expression from mathics.core.atoms import Integer -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolList from mathics.core.systemsymbols import ( SymbolAssociation, SymbolMakeBoxes, - SymbolList, ) diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index cfa96be6f..e39a42092 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -24,7 +24,7 @@ structure, ) from mathics.core.atoms import Integer -from mathics.core.systemsymbols import SymbolList +from mathics.core.symbols import SymbolList class Array(Builtin): diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index e3dbde763..673cd6a9b 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -32,13 +32,11 @@ from mathics.builtin.base import MessageException from mathics.core.expression import Expression -from mathics.core.symbols import Symbol from mathics.core.atoms import Integer, Integer0 +from mathics.core.symbols import Symbol, SymbolList, SymbolNull from mathics.core.systemsymbols import ( SymbolFailed, - SymbolList, SymbolMakeBoxes, - SymbolNull, SymbolSequence, ) diff --git a/mathics/builtin/list/rearrange.py b/mathics/builtin/list/rearrange.py index 1fc1f2cc8..5a6781842 100644 --- a/mathics/builtin/list/rearrange.py +++ b/mathics/builtin/list/rearrange.py @@ -25,7 +25,7 @@ structure, ) from mathics.core.atoms import Integer -from mathics.core.systemsymbols import SymbolList +from mathics.core.symbols import SymbolList def _is_sameq(same_test): diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index e34f58fea..f60def536 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -59,12 +59,11 @@ min_prec, ) -from mathics.core.symbols import Symbol, strip_context +from mathics.core.symbols import Symbol, SymbolList, strip_context from mathics.core.systemsymbols import ( SymbolByteArray, SymbolFailed, - SymbolList, SymbolMakeBoxes, SymbolRule, SymbolSequence, @@ -1804,7 +1803,7 @@ class _FastEquivalence: # String, Rational (*), Expression, Image; new atoms need proper hash functions # # (*) Rational values are sympy Rationals which are always held in reduced form - # and thus are hashed correctly (see sympy/core/numbers.py:Rational.__eq__()). + # and thus are hashed correctly (see sympy/core/number.py:Rational.__eq__()). def __init__(self): self._hashes = defaultdict(list) diff --git a/mathics/builtin/logic.py b/mathics/builtin/logic.py index f35745253..1d9e77066 100644 --- a/mathics/builtin/logic.py +++ b/mathics/builtin/logic.py @@ -4,8 +4,8 @@ from mathics.builtin.base import BinaryOperator, Predefined, PrefixOperator, Builtin from mathics.builtin.lists import InvalidLevelspecError, python_levelspec, walk_levels from mathics.core.expression import Expression -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import ( +from mathics.core.symbols import ( + Symbol, SymbolTrue, SymbolFalse, ) diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index 202301448..689f20d74 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -12,6 +12,10 @@ from mathics.core.symbols import ( Atom, Symbol, + SymbolFalse, + SymbolList, + SymbolNull, + SymbolTrue, ) from mathics.core.atoms import ( @@ -22,12 +26,6 @@ Number, ) -from mathics.core.systemsymbols import ( - SymbolFalse, - SymbolList, - SymbolNull, - SymbolTrue, -) from mathics.core.convert import from_sympy, sympy_symbol_prefix from mathics.core.rules import Pattern from mathics.builtin.scoping import dynamic_scoping diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index a60e4b9f0..4339e96d7 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -17,26 +17,30 @@ Real, from_python, ) -from mathics.core.systemsymbols import ( - SymbolTrue, + +from mathics.core.symbols import ( + Symbol, SymbolFalse, SymbolList, + SymbolTrue, +) + +from mathics.core.systemsymbols import ( + SymbolPlus, + SymbolPower, SymbolRule, + SymbolTimes, SymbolUndefined, ) from mathics.core.convert import sympy_symbol_prefix, SympyExpression, from_sympy from mathics.core.rules import Pattern -from mathics.core.numbers import dps +from mathics.core.number import dps from mathics.builtin.scoping import dynamic_scoping from mathics.builtin.numeric import apply_N -from mathics import Symbol import sympy -SymbolPlus = Symbol("Plus") -SymbolTimes = Symbol("Times") -SymbolPower = Symbol("Power") IntegerZero = Integer(0) IntegerMinusOne = Integer(-1) diff --git a/mathics/builtin/numbers/constants.py b/mathics/builtin/numbers/constants.py index 54555a873..33406bdf4 100644 --- a/mathics/builtin/numbers/constants.py +++ b/mathics/builtin/numbers/constants.py @@ -22,7 +22,7 @@ MachineReal, PrecisionReal, ) -from mathics.core.numbers import get_precision, PrecisionValueError, machine_precision +from mathics.core.number import get_precision, PrecisionValueError, machine_precision def mp_constant(fn: str, d=None) -> mpmath.mpf: diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 70abf6658..7917b3f46 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -29,7 +29,7 @@ from mathics.core.convert import from_sympy from mathics.core.expression import Expression -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolFalse, SymbolList, SymbolN, SymbolTrue from mathics.core.atoms import ( Complex, Integer, @@ -42,14 +42,10 @@ from_python, ) from mathics.core.systemsymbols import ( - SymbolFalse, - SymbolTrue, - SymbolList, SymbolMachinePrecision, - SymbolN, ) -from mathics.core.numbers import ( +from mathics.core.number import ( dps, convert_int_to_digit_list, machine_precision, @@ -432,7 +428,7 @@ def fold(self, x, ll): continue if mode == self.MPMATH: - from mathics.core.numbers import min_prec + from mathics.core.number import min_prec precision = min_prec(*[t for t in chain(*s_operands) if t is not None]) working_precision = mpmath.workprec diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 657bb1e1c..f3bfae68b 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -55,7 +55,7 @@ Rational, Real, ) -from mathics.core.systemsymbols import ( +from mathics.core.symbols import ( SymbolFalse, SymbolList, SymbolTrue, diff --git a/mathics/builtin/procedural.py b/mathics/builtin/procedural.py index 146ea4d26..035eba873 100644 --- a/mathics/builtin/procedural.py +++ b/mathics/builtin/procedural.py @@ -17,7 +17,7 @@ from mathics.core.expression import Expression from mathics.core.symbols import Symbol from mathics.core.atoms import from_python -from mathics.core.systemsymbols import ( +from mathics.core.symbols import ( SymbolTrue, SymbolFalse, ) diff --git a/mathics/builtin/specialfns/bessel.py b/mathics/builtin/specialfns/bessel.py index 10cac9d89..13bd58dbb 100644 --- a/mathics/builtin/specialfns/bessel.py +++ b/mathics/builtin/specialfns/bessel.py @@ -10,8 +10,8 @@ from mathics.builtin.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin from mathics.core.atoms import from_mpmath -from mathics.core.numbers import machine_precision, get_precision, PrecisionValueError -from mathics.core.numbers import prec as _prec +from mathics.core.number import machine_precision, get_precision, PrecisionValueError +from mathics.core.number import prec as _prec class _Bessel(_MPMathFunction): diff --git a/mathics/builtin/string/characters.py b/mathics/builtin/string/characters.py index 692cf9951..1d48aeda1 100644 --- a/mathics/builtin/string/characters.py +++ b/mathics/builtin/string/characters.py @@ -9,7 +9,7 @@ from mathics.core.expression import Expression from mathics.core.atoms import String -from mathics.core.systemsymbols import SymbolList +from mathics.core.symbols import SymbolList class Characters(Builtin): diff --git a/mathics/builtin/string/charcodes.py b/mathics/builtin/string/charcodes.py index 9014f719c..db2883bc3 100644 --- a/mathics/builtin/string/charcodes.py +++ b/mathics/builtin/string/charcodes.py @@ -14,7 +14,7 @@ Integer1, String, ) -from mathics.core.systemsymbols import SymbolList +from mathics.core.symbols import SymbolList from mathics.builtin.strings import to_python_encoding diff --git a/mathics/builtin/string/operations.py b/mathics/builtin/string/operations.py index d4867e977..6316c782e 100644 --- a/mathics/builtin/string/operations.py +++ b/mathics/builtin/string/operations.py @@ -13,8 +13,8 @@ Builtin, ) from mathics.core.expression import Expression, string_list -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import ( +from mathics.core.symbols import ( + Symbol, SymbolFalse, SymbolList, SymbolTrue, diff --git a/mathics/builtin/string/patterns.py b/mathics/builtin/string/patterns.py index ef6cd22e0..3da6bbeab 100644 --- a/mathics/builtin/string/patterns.py +++ b/mathics/builtin/string/patterns.py @@ -14,7 +14,7 @@ Integer1, String, ) -from mathics.core.systemsymbols import ( +from mathics.core.symbols import ( SymbolFalse, SymbolList, SymbolTrue, diff --git a/mathics/builtin/strings.py b/mathics/builtin/strings.py index 15804bf26..b1729150c 100644 --- a/mathics/builtin/strings.py +++ b/mathics/builtin/strings.py @@ -19,13 +19,15 @@ PrefixOperator, ) from mathics.core.expression import Expression -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import ( - SymbolFailed, +from mathics.core.symbols import ( + Symbol, SymbolFalse, SymbolTrue, SymbolList, ) +from mathics.core.systemsymbols import ( + SymbolFailed, +) from mathics.core.atoms import ( String, Integer, diff --git a/mathics/builtin/structure.py b/mathics/builtin/structure.py index efd13643f..c25d625a0 100644 --- a/mathics/builtin/structure.py +++ b/mathics/builtin/structure.py @@ -15,14 +15,13 @@ from mathics.core.expression import Expression from mathics.core.symbols import ( Symbol, - strip_context, -) -from mathics.core.systemsymbols import ( SymbolNull, SymbolFalse, SymbolTrue, SymbolList, + strip_context, ) + from mathics.core.atoms import ( String, Integer, diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index f7510c742..5f1dee102 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -21,9 +21,11 @@ Real, String, ) +from mathics.core.symbols import ( + SymbolList, +) from mathics.core.systemsymbols import ( SymbolFailed, - SymbolList, SymbolRule, ) from mathics.builtin.base import Builtin, Predefined diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index c3c2f58fe..d7cebaf1b 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -19,7 +19,7 @@ Integer0, String, ) -from mathics.core.systemsymbols import ( +from mathics.core.symbols import ( SymbolTrue, SymbolFalse, ) diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index f0bfced05..be5e4ae6a 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -15,17 +15,17 @@ Atom, BaseExpression, Symbol, - system_symbols, - fully_qualified_symbol_name, -) -from mathics.core.systemsymbols import ( - SymbolTrue, SymbolFalse, - SymbolNull, SymbolList, - SymbolByteArray, + SymbolNull, + SymbolTrue, + fully_qualified_symbol_name, + system_symbols, ) -from mathics.core.numbers import dps, get_type, prec, min_prec, machine_precision + +from mathics.core.systemsymbols import SymbolByteArray + +from mathics.core.number import dps, get_type, prec, min_prec, machine_precision import base64 # Imperical number that seems to work. @@ -613,7 +613,7 @@ def is_machine_precision(self) -> bool: return True return False - def get_float_value(self, permit_complex=False) -> typing.Optional[complex]: + def get_float_value(self, permit_complex=False) -> Optional[complex]: if permit_complex: real = self.real.get_float_value() imag = self.imag.get_float_value() @@ -622,7 +622,7 @@ def get_float_value(self, permit_complex=False) -> typing.Optional[complex]: else: return None - def get_precision(self) -> typing.Optional[int]: + def get_precision(self) -> Optional[int]: real_prec = self.real.get_precision() imag_prec = self.imag.get_precision() if imag_prec is None or real_prec is None: diff --git a/mathics/core/convert.py b/mathics/core/convert.py index 14fd43a47..7cfd15592 100644 --- a/mathics/core/convert.py +++ b/mathics/core/convert.py @@ -125,11 +125,11 @@ def from_sympy(expr): String, MachineReal, ) - from mathics.core.systemsymbols import ( + from mathics.core.symbols import ( SymbolNull, SymbolList, ) - from mathics.core.numbers import machine_precision + from mathics.core.number import machine_precision if isinstance(expr, (tuple, list)): return Expression(SymbolList, *[from_sympy(item) for item in expr]) diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py index 88693732f..2ca0b131b 100644 --- a/mathics/core/evaluation.py +++ b/mathics/core/evaluation.py @@ -16,12 +16,14 @@ ensure_context, KeyComparable, ) -from mathics.core.systemsymbols import ( - SymbolAborted, + +from mathics.core.symbols import ( SymbolList, SymbolNull, ) +from mathics.core.systemsymbols import SymbolAborted + FORMATS = [ "StandardForm", "FullForm", diff --git a/mathics/core/expression.py b/mathics/core/expression.py index db150decc..2dad45035 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -9,16 +9,19 @@ from itertools import chain from bisect import bisect_left -# from mathics.core.formatter import * from mathics.core.atoms import from_python, Number, Integer -from mathics.core.symbols import Atom, BaseExpression, Monomial, Symbol, system_symbols -from mathics.core.systemsymbols import ( +from mathics.core.number import dps +from mathics.core.convert import sympy_symbol_prefix, SympyExpression +from mathics.core.symbols import ( + Atom, + BaseExpression, + Monomial, + Symbol, SymbolList, SymbolN, - SymbolSequence, + system_symbols, ) -from mathics.core.numbers import dps -from mathics.core.convert import sympy_symbol_prefix, SympyExpression +from mathics.core.systemsymbols import SymbolSequence def fully_qualified_symbol_name(name) -> bool: diff --git a/mathics/core/numbers.py b/mathics/core/number.py similarity index 100% rename from mathics/core/numbers.py rename to mathics/core/number.py diff --git a/mathics/core/parser/convert.py b/mathics/core/parser/convert.py index 2ec6d3150..6f3f7d605 100644 --- a/mathics/core/parser/convert.py +++ b/mathics/core/parser/convert.py @@ -9,7 +9,7 @@ import mathics.core.symbols as mas import mathics.core.expression as mae from mathics.core.parser.ast import Symbol, String, Number, Filename -from mathics.core.numbers import machine_precision, reconstruct_digits +from mathics.core.number import machine_precision, reconstruct_digits class GenericConverter(object): diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index a1688ac7c..19410b22c 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -1,17 +1,8 @@ -# cython: language_level=3 # -*- coding: utf-8 -*- -from mathics.core.symbols import ( - Symbol, - SymbolList, - SymbolMakeBoxes, - SymbolTrue, - SymbolFalse, - SymbolN, - SymbolNull, -) +from mathics.core.symbols import Symbol -# Some other common Symbols. This list is sorted in alpabetic order. +# Some other common Symbols. This list is sorted in alphabetic order. SymbolAborted = Symbol("$Aborted") SymbolAssociation = Symbol("Association") SymbolByteArray = Symbol("ByteArray") @@ -22,6 +13,10 @@ SymbolInfinity = Symbol("Infinity") SymbolLess = Symbol("Less") SymbolMachinePrecision = Symbol("MachinePrecision") +SymbolMakeBoxes = Symbol("MakeBoxes") +SymbolPlus = Symbol("Plus") +SymbolPower = Symbol("Power") SymbolRule = Symbol("Rule") SymbolSequence = Symbol("Sequence") +SymbolTimes = Symbol("Times") SymbolUndefined = Symbol("Undefined") diff --git a/test/test_hash.py b/test/test_hash.py index e43857957..5fd5c7c2d 100644 --- a/test/test_hash.py +++ b/test/test_hash.py @@ -12,8 +12,7 @@ Real, String, ) -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import SymbolFalse +from mathics.core.symbols import Symbol, SymbolFalse from mathics.core.definitions import Definitions import sys From e806c5d86902c7e1e5302388fd177c76db9cd8f3 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 26 Sep 2021 11:48:27 -0400 Subject: [PATCH 152/193] More import reorganization --- mathics/builtin/__init__.py | 3 - mathics/core/atoms.py | 2 +- test/test_convert.py | 117 +++++++++++++++--------------------- 3 files changed, 51 insertions(+), 71 deletions(-) diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index f3cb05a25..0ee7e10f5 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -108,9 +108,6 @@ def import_module(module_name: str, import_name: str): print(e) print(f" Not able to load {module_name}. Check your installation.") print(f" mathics.builtin loads from {__file__[:-11]}") - from trepan.api import debug - - debug() return None if __version__ != module.__version__: diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index be5e4ae6a..be9fe86e5 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -7,7 +7,7 @@ import re import typing -from typing import Any +from typing import Any, Optional from functools import lru_cache from mathics.core.formatter import encode_mathml, encode_tex, extra_operators diff --git a/test/test_convert.py b/test/test_convert.py index 54f7e16d7..2444f3bcc 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -3,7 +3,10 @@ import sympy -import mathics +from mathics.core.symbols import Symbol +from mathics.core.atoms import from_python, Complex, Integer, MachineReal, Real, String +from mathics.core.convert import from_sympy +from mathics.core.expression import Expression import random import sys import unittest @@ -11,63 +14,61 @@ class SympyConvert(unittest.TestCase): def compare_to_sympy(self, mathics_expr, sympy_expr, **kwargs): - self.assertEqual(mathics_expr.to_sympy(**kwargs), sympy_expr) + mathics_expr.to_sympy(**kwargs) == sympy_expr def compare_to_mathics(self, mathics_expr, sympy_expr, **kwargs): - self.assertEqual(mathics_expr, mathics.from_sympy(sympy_expr, **kwargs)) + mathics_expr == from_sympy(sympy_expr, **kwargs) def compare(self, mathics_expr, sympy_expr, **kwargs): self.compare_to_sympy(mathics_expr, sympy_expr, **kwargs) self.compare_to_mathics(mathics_expr, sympy_expr) def testSymbol(self): - self.compare(mathics.Symbol("Global`x"), sympy.Symbol("_Mathics_User_Global`x")) + self.compare(Symbol("Global`x"), sympy.Symbol("_Mathics_User_Global`x")) self.compare( - mathics.Symbol("_Mathics_User_x"), + Symbol("_Mathics_User_x"), sympy.Symbol("_Mathics_User_System`_Mathics_User_x"), ) def testReal(self): - self.compare(mathics.Real("1.0"), sympy.Float("1.0")) - self.compare(mathics.Real(1.0), sympy.Float(1.0)) + self.compare(Real("1.0"), sympy.Float("1.0")) + self.compare(Real(1.0), sympy.Float(1.0)) def testInteger(self): - self.compare(mathics.Integer(0), sympy.Integer(0)) - self.compare(mathics.Integer(1), sympy.Integer(1)) + self.compare(Integer(0), sympy.Integer(0)) + self.compare(Integer(1), sympy.Integer(1)) n = random.randint(-sys.maxsize, sys.maxsize) - self.compare(mathics.Integer(n), sympy.Integer(n)) + self.compare(Integer(n), sympy.Integer(n)) n = random.randint(sys.maxsize, sys.maxsize * sys.maxsize) - self.compare(mathics.Integer(n), sympy.Integer(n)) + self.compare(Integer(n), sympy.Integer(n)) def testComplex(self): self.compare( - mathics.Complex(mathics.Real("1.0"), mathics.Real("1.0")), + Complex(Real("1.0"), Real("1.0")), sympy.Add(sympy.Float("1.0"), sympy.Float("1.0") * sympy.I), ) - self.compare(mathics.Complex(mathics.Integer(0), mathics.Integer(1)), sympy.I) + self.compare(Complex(Integer(0), Integer(1)), sympy.I) self.compare( - mathics.Complex(mathics.Integer(-1), mathics.Integer(1)), + Complex(Integer(-1), Integer(1)), sympy.Integer(-1) + sympy.I, ) def testString(self): - self.assertIsNone(mathics.String("abc").to_sympy()) + String("abc").to_sympy() is None def testAdd(self): self.compare( - mathics.Expression("Plus", mathics.Integer(1), mathics.Symbol("Global`x")), + Expression("Plus", Integer(1), Symbol("Global`x")), sympy.Add(sympy.Integer(1), sympy.Symbol("_Mathics_User_Global`x")), ) def testIntegrate(self): self.compare( - mathics.Expression( - "Integrate", mathics.Symbol("Global`x"), mathics.Symbol("Global`y") - ), + Expression("Integrate", Symbol("Global`x"), Symbol("Global`y")), sympy.Integral( sympy.Symbol("_Mathics_User_Global`x"), sympy.Symbol("_Mathics_User_Global`y"), @@ -76,9 +77,7 @@ def testIntegrate(self): def testDerivative(self): self.compare( - mathics.Expression( - "D", mathics.Symbol("Global`x"), mathics.Symbol("Global`y") - ), + Expression("D", Symbol("Global`x"), Symbol("Global`y")), sympy.Derivative( sympy.Symbol("_Mathics_User_Global`x"), sympy.Symbol("_Mathics_User_Global`y"), @@ -88,15 +87,11 @@ def testDerivative(self): def testDerivative2(self): kwargs = {"converted_functions": set(["Global`f"])} - head = mathics.Expression( - mathics.Expression( - "System`Derivative", mathics.Integer(1), mathics.Integer(0) - ), - mathics.Symbol("Global`f"), - ) - expr = mathics.Expression( - head, mathics.Symbol("Global`x"), mathics.Symbol("Global`y") + head = Expression( + Expression("System`Derivative", Integer(1), Integer(0)), + Symbol("Global`f"), ) + expr = Expression(head, Symbol("Global`x"), Symbol("Global`y")) sfxy = sympy.Function(str("_Mathics_User_Global`f"))( sympy.Symbol("_Mathics_User_Global`x"), @@ -110,15 +105,13 @@ def testDerivative2(self): def testConvertedFunctions(self): kwargs = {"converted_functions": set(["Global`f"])} - marg1 = mathics.Expression("Global`f", mathics.Symbol("Global`x")) + marg1 = Expression("Global`f", Symbol("Global`x")) sarg1 = sympy.Function(str("_Mathics_User_Global`f"))( sympy.Symbol("_Mathics_User_Global`x") ) self.compare(marg1, sarg1, **kwargs) - marg2 = mathics.Expression( - "Global`f", mathics.Symbol("Global`x"), mathics.Symbol("Global`y") - ) + marg2 = Expression("Global`f", Symbol("Global`x"), Symbol("Global`y")) sarg2 = sympy.Function(str("_Mathics_User_Global`f"))( sympy.Symbol("_Mathics_User_Global`x"), sympy.Symbol("_Mathics_User_Global`y"), @@ -126,30 +119,28 @@ def testConvertedFunctions(self): self.compare(marg2, sarg2, **kwargs) self.compare( - mathics.Expression("D", marg2, mathics.Symbol("Global`x")), + Expression("D", marg2, Symbol("Global`x")), sympy.Derivative(sarg2, sympy.Symbol("_Mathics_User_Global`x")), **kwargs ) def testExpression(self): self.compare( - mathics.Expression("Sin", mathics.Symbol("Global`x")), + Expression("Sin", Symbol("Global`x")), sympy.sin(sympy.Symbol("_Mathics_User_Global`x")), ) def testConstant(self): - self.compare(mathics.Symbol("System`E"), sympy.E) - self.compare(mathics.Symbol("System`Pi"), sympy.pi) + self.compare(Symbol("System`E"), sympy.E) + self.compare(Symbol("System`Pi"), sympy.pi) def testGamma(self): self.compare( - mathics.Expression("Gamma", mathics.Symbol("Global`z")), + Expression("Gamma", Symbol("Global`z")), sympy.gamma(sympy.Symbol("_Mathics_User_Global`z")), ) self.compare( - mathics.Expression( - "Gamma", mathics.Symbol("Global`z"), mathics.Symbol("Global`x") - ), + Expression("Gamma", Symbol("Global`z"), Symbol("Global`x")), sympy.uppergamma( sympy.Symbol("_Mathics_User_Global`z"), sympy.Symbol("_Mathics_User_Global`x"), @@ -159,49 +150,41 @@ def testGamma(self): class PythonConvert(unittest.TestCase): def compare(self, mathics_expr, python_expr): - self.assertEqual(mathics_expr.to_python(), python_expr) - self.assertEqual(mathics_expr, mathics.from_python(python_expr)) + assert mathics_expr.to_python() == python_expr + assert mathics_expr == from_python(python_expr) def testReal(self): - self.compare(mathics.Real("0.0"), 0.0) - self.compare(mathics.Real("1.5"), 1.5) - self.compare(mathics.Real("-1.5"), -1.5) + self.compare(Real("0.0"), 0.0) + self.compare(Real("1.5"), 1.5) + self.compare(Real("-1.5"), -1.5) def testInteger(self): - self.compare(mathics.Integer(1), 1) + self.compare(Integer(1), 1) @unittest.expectedFailure def testString(self): - self.compare(mathics.String("abc"), '"abc"') + self.compare(String("abc"), '"abc"') @unittest.expectedFailure def testSymbol(self): - self.compare(mathics.Symbol("abc"), "abc") + self.compare(Symbol("abc"), "abc") def testComplex(self): - self.compare(mathics.Complex(mathics.Integer(1), mathics.Integer(1)), 1 + 1j) + self.compare(Complex(Integer(1), Integer(1)), 1 + 1j) self.compare( - mathics.Complex(mathics.MachineReal(1.0), mathics.MachineReal(1.0)), + Complex(MachineReal(1.0), MachineReal(1.0)), 1.0 + 1.0j, ) - self.compare( - mathics.Complex(mathics.Integer(1), mathics.MachineReal(1.0)), 1 + 1.0j - ) - self.compare( - mathics.Complex(mathics.MachineReal(1.0), mathics.Integer(1)), 1.0 + 1j - ) - self.compare( - mathics.Complex(mathics.Real("1.0", 5), mathics.Integer(1)), 1.0 + 1j - ) - self.compare( - mathics.Complex(mathics.Integer(1), mathics.Real("1.0", 20)), 1 + 1.0j - ) + self.compare(Complex(Integer(1), MachineReal(1.0)), 1 + 1.0j) + self.compare(Complex(MachineReal(1.0), Integer(1)), 1.0 + 1j) + self.compare(Complex(Real("1.0", 5), Integer(1)), 1.0 + 1j) + self.compare(Complex(Integer(1), Real("1.0", 20)), 1 + 1.0j) - self.compare(mathics.Complex(mathics.Integer(0), mathics.Integer(1)), 1j) - self.compare(mathics.Complex(mathics.Integer(1), mathics.Integer(0)), 1) + self.compare(Complex(Integer(0), Integer(1)), 1j) + self.compare(Complex(Integer(1), Integer(0)), 1) def testList(self): - self.compare(mathics.Expression("List", mathics.Integer(1)), [1]) + self.compare(Expression("List", Integer(1)), [1]) if __name__ == "__main__": From cdaa12decf8bfc4ac233c29ef90a1fec56aaf740 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 26 Sep 2021 22:02:23 -0400 Subject: [PATCH 153/193] Narrow description to just the mathics-core... and add links to mathics.org for a high-level view or mathics-omnibus for a one-stop install process. --- README.rst | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index 8a2a3e4db..e7ca6b2f4 100644 --- a/README.rst +++ b/README.rst @@ -1,28 +1,18 @@ -Welcome to Mathics! -=================== +Welcome to Mathics Core! +======================== |Pypi Installs| |Latest Version| |Supported Python Versions| |SlackStatus|_ |Packaging status| -Mathics is a general-purpose computer algebra system (CAS). It is an open-source alternative to Mathematica. It is free both as in "free beer" and as in "freedom". +Mathics is a general-purpose computer algebra system (CAS). -The home page of Mathics is https://mathics.org. +However this repository contains just the Python modules for WL Built-in functions, variables, core primitives, e.g. Symbol, a parser to create Expressions, and an evaluator to execute them. +The home page for Mathics is https://mathics.org where you will find a list of screenshots and components making up the system. -ScreenShots ------------ - -mathicsscript: a text interface -+++++++++++++++++++++++++++++++ - -|mathicsscript| - -mathicsserver: a Django-based Web interface -+++++++++++++++++++++++++++++++++++++++++++ - -|mathicssserver| +If you want to install everything locally, see the `Mathics omnibus repository `_. Installing and Running From 4b47e09707b88e3e34b61e05be23c948a9bcef5e Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 26 Sep 2021 14:53:42 -0300 Subject: [PATCH 154/193] Splitting different cases and organize calls from a dictionary --- mathics/builtin/assignment.py | 936 +++++++++++++++++++++------------- 1 file changed, 573 insertions(+), 363 deletions(-) diff --git a/mathics/builtin/assignment.py b/mathics/builtin/assignment.py index 358f4556a..e369d369c 100644 --- a/mathics/builtin/assignment.py +++ b/mathics/builtin/assignment.py @@ -18,6 +18,8 @@ from mathics.core.systemsymbols import ( SymbolFailed, + SymbolMachinePrecision, + SymbolN, SymbolNull, ) from mathics.core.atoms import String @@ -27,6 +29,13 @@ from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit +class AssignmentException(Exception): + def __init__(self, lhs, rhs) -> None: + super().__init__(" %s cannot be assigned to %s" % (rhs, lhs)) + self.lhs = lhs + self.rhs = rhs + + def repl_pattern_by_symbol(expr): leaves = expr.get_leaves() if len(leaves) == 0: @@ -39,10 +48,10 @@ def repl_pattern_by_symbol(expr): changed = False newleaves = [] for leave in leaves: - l = repl_pattern_by_symbol(leave) - if not (l is leave): + leaf = repl_pattern_by_symbol(leave) + if not (leaf is leave): changed = True - newleaves.append(l) + newleaves.append(leaf) if changed: return Expression(headname, *newleaves) else: @@ -65,376 +74,577 @@ def get_symbol_list(list, error_callback): return values -class _SetOperator(object): - def assign_elementary(self, lhs, rhs, evaluation, tags=None, upset=False): - # TODO: This function should be splitted and simplified +# Here are the functions related to assign_elementary +# Auxiliary routines + + +def rejected_because_protected(self, lhs, tag, evaluation, ignore=False): + defs = evaluation.definitions + if not ignore and is_protected(tag, defs): + if lhs.get_name() == tag: + evaluation.message(self.get_name(), "wrsym", Symbol(tag)) + else: + evaluation.message(self.get_name(), "write", Symbol(tag), lhs) + return True + return False + + +def find_tag_and_check(lhs, tags, evaluation): + name = lhs.get_head_name() + if len(lhs.leaves) != 1: + evaluation.message_args(name, len(lhs.leaves), 1) + raise AssignmentException(lhs, None) + tag = lhs.leaves[0].get_name() + if not tag: + evaluation.message(name, "sym", lhs.leaves[0], 1) + raise AssignmentException(lhs, None) + if tags is not None and tags != [tag]: + evaluation.message(name, "tag", Symbol(name), Symbol(tag)) + raise AssignmentException(lhs, None) + if is_protected(tag, evaluation.definitions): + evaluation.message(name, "wrsym", Symbol(tag)) + raise AssignmentException(lhs, None) + return tag + + +def is_protected(tag, defin): + return "System`Protected" in defin.get_attributes(tag) + + +def build_rulopc(optval): + return Rule( + Expression( + "OptionValue", + Expression("Pattern", Symbol("$cond$"), Expression("Blank")), + ), + Expression("OptionValue", optval, Symbol("$cond$")), + ) + + +def unroll_patterns(lhs, rhs, evaluation): + if type(lhs) is Symbol: + return lhs, rhs + name = lhs.get_head_name() + lhsleaves = lhs._leaves + if name == "System`Pattern": + lhs = lhsleaves[1] + rulerepl = (lhsleaves[0], repl_pattern_by_symbol(lhs)) + rhs, status = rhs.apply_rules([Rule(*rulerepl)], evaluation) name = lhs.get_head_name() - lhs._format_cache = None - condition = None - - # Maybe these first conversions should be a loop... - if name == "System`Condition" and len(lhs.leaves) == 2: - # This handle the case of many sucesive conditions: - # f[x_]/; cond1 /; cond2 ... - # is summarized to a single condition - # f[x_]/; And[cond1, cond2, ...] - condition = [lhs._leaves[1]] - lhs = lhs._leaves[0] - name = lhs.get_head_name() - while name == "System`Condition" and len(lhs.leaves) == 2: - condition.append(lhs._leaves[1]) - lhs = lhs._leaves[0] - name = lhs.get_head_name() - if len(condition) > 1: - condition = Expression("System`And", *condition) - else: - condition = condition[0] - condition = Expression("System`Condition", lhs, condition) - name = lhs.get_head_name() - lhs._format_cache = None - if name == "System`Pattern": - lhsleaves = lhs.get_leaves() - lhs = lhsleaves[1] - rulerepl = (lhsleaves[0], repl_pattern_by_symbol(lhs)) - rhs, status = rhs.apply_rules([Rule(*rulerepl)], evaluation) - name = lhs.get_head_name() - if name == "System`HoldPattern": - lhs = lhs.leaves[0] - name = lhs.get_head_name() + if name == "System`HoldPattern": + lhs = lhsleaves[0] + name = lhs.get_head_name() + return lhs, rhs - if name in system_symbols( - "OwnValues", - "DownValues", - "SubValues", - "UpValues", - "NValues", - "Options", - "DefaultValues", - "Attributes", - "Messages", - ): - if len(lhs.leaves) != 1: - evaluation.message_args(name, len(lhs.leaves), 1) - return False - tag = lhs.leaves[0].get_name() - if not tag: - evaluation.message(name, "sym", lhs.leaves[0], 1) - return False - if tags is not None and tags != [tag]: - evaluation.message(name, "tag", Symbol(name), Symbol(tag)) - return False - if ( - name != "System`Attributes" - and "System`Protected" # noqa - in evaluation.definitions.get_attributes(tag) - ): - evaluation.message(name, "wrsym", Symbol(tag)) - return False - if name == "System`Options": - option_values = rhs.get_option_values(evaluation) - if option_values is None: - evaluation.message(name, "options", rhs) - return False - evaluation.definitions.set_options(tag, option_values) - elif name == "System`Attributes": - attributes = get_symbol_list( - rhs, lambda item: evaluation.message(name, "sym", item, 1) - ) - if attributes is None: - return False - if "System`Locked" in evaluation.definitions.get_attributes(tag): - evaluation.message(name, "locked", Symbol(tag)) - return False - evaluation.definitions.set_attributes(tag, attributes) - else: - rules = rhs.get_rules_list() - if rules is None: - evaluation.message(name, "vrule", lhs, rhs) - return False - evaluation.definitions.set_values(tag, name, rules) - return True - - form = "" - nprec = None - default = False - message = False - - allow_custom_tag = False - focus = lhs - - if name == "System`N": - if len(lhs.leaves) not in (1, 2): - evaluation.message_args("N", len(lhs.leaves), 1, 2) - return False - if len(lhs.leaves) == 1: - nprec = Symbol("MachinePrecision") - else: - nprec = lhs.leaves[1] - focus = lhs.leaves[0] - lhs = Expression("N", focus, nprec) - elif name == "System`MessageName": - if len(lhs.leaves) != 2: - evaluation.message_args("MessageName", len(lhs.leaves), 2) - return False - focus = lhs.leaves[0] - message = True - elif name == "System`Default": - if len(lhs.leaves) not in (1, 2, 3): - evaluation.message_args("Default", len(lhs.leaves), 1, 2, 3) - return False - focus = lhs.leaves[0] - default = True - elif name == "System`Format": - if len(lhs.leaves) not in (1, 2): - evaluation.message_args("Format", len(lhs.leaves), 1, 2) - return False - if len(lhs.leaves) == 2: - form = lhs.leaves[1].get_name() - if not form: - evaluation.message("Format", "fttp", lhs.leaves[1]) - return False - else: - form = system_symbols( - "StandardForm", - "TraditionalForm", - "OutputForm", - "TeXForm", - "MathMLForm", - ) - lhs = focus = lhs.leaves[0] - else: - allow_custom_tag = True - - # TODO: the following provides a hacky fix for 1259. I know @rocky loves - # this kind of things, but otherwise we need to work on rebuild the pattern - # matching mechanism... - evaluation.ignore_oneidentity = True - focus = focus.evaluate_leaves(evaluation) - evaluation.ignore_oneidentity = False - if tags is None and not upset: - name = focus.get_lookup_name() - if not name: - evaluation.message(self.get_name(), "setraw", focus) - return False - tags = [name] - elif upset: - if allow_custom_tag: - tags = [] - if focus.is_atom(): - evaluation.message(self.get_name(), "normal") - return False - for leaf in focus.leaves: - name = leaf.get_lookup_name() - tags.append(name) - else: - tags = [focus.get_lookup_name()] - else: - allowed_names = [focus.get_lookup_name()] - if allow_custom_tag: - for leaf in focus.get_leaves(): - if not leaf.is_symbol() and leaf.get_head_name() in ( - "System`HoldPattern", - ): - leaf = leaf.leaves[0] - if not leaf.is_symbol() and leaf.get_head_name() in ( - "System`Pattern", - ): - leaf = leaf.leaves[1] - if not leaf.is_symbol() and leaf.get_head_name() in ( - "System`Blank", - "System`BlankSequence", - "System`BlankNullSequence", - ): - if len(leaf.leaves) == 1: - leaf = leaf.leaves[0] - - allowed_names.append(leaf.get_lookup_name()) - for name in tags: - if name not in allowed_names: - evaluation.message(self.get_name(), "tagnfd", Symbol(name)) - return False - - ignore_protection = False - rhs_int_value = rhs.get_int_value() - lhs_name = lhs.get_name() - if lhs_name == "System`$RecursionLimit": - # if (not rhs_int_value or rhs_int_value < 20) and not - # rhs.get_name() == 'System`Infinity': - if ( - not rhs_int_value - or rhs_int_value < 20 - or rhs_int_value > MAX_RECURSION_DEPTH - ): # nopep8 +def unroll_conditions(lhs): + condition = None + if type(lhs) is Symbol: + return lhs, None + else: + name, lhs_leaves = lhs.get_head_name(), lhs._leaves + condition = [] + # This handle the case of many sucesive conditions: + # f[x_]/; cond1 /; cond2 ... -> f[x_]/; And[cond1, cond2, ...] + while name == "System`Condition" and len(lhs.leaves) == 2: + condition.append(lhs_leaves[1]) + lhs = lhs_leaves[0] + name, lhs_leaves = lhs.get_head_name(), lhs._leaves + if len(condition) == 0: + return lhs, None + if len(condition) > 1: + condition = Expression("System`And", *condition) + else: + condition = condition[0] + condition = Expression("System`Condition", lhs, condition) + lhs._format_cache = None + return lhs, condition + + +def process_rhs_conditions(lhs, rhs, condition, evaluation): + # To Handle `OptionValue` in `Condition` + rulopc = build_rulopc(lhs.get_head()) + rhs_name = rhs.get_head_name() + while rhs_name == "System`Condition": + if len(rhs.leaves) != 2: + evaluation.message_args("Condition", len(rhs.leaves), 2) + raise AssignmentException(lhs, None) + lhs = Expression( + "Condition", lhs, rhs.leaves[1].apply_rules([rulopc], evaluation)[0] + ) + rhs = rhs.leaves[0] + rhs_name = rhs.get_head_name() - evaluation.message("$RecursionLimit", "limset", rhs) - return False - try: - set_python_recursion_limit(rhs_int_value) - except OverflowError: - # TODO: Message - return False - ignore_protection = True - if lhs_name == "System`$IterationLimit": - if ( - not rhs_int_value or rhs_int_value < 20 - ) and not rhs.get_name() == "System`Infinity": - evaluation.message("$IterationLimit", "limset", rhs) - return False - ignore_protection = True - elif lhs_name == "System`$ModuleNumber": - if not rhs_int_value or rhs_int_value <= 0: - evaluation.message("$ModuleNumber", "set", rhs) - return False - ignore_protection = True - elif lhs_name in ("System`$Line", "System`$HistoryLength"): - if rhs_int_value is None or rhs_int_value < 0: - evaluation.message(lhs_name, "intnn", rhs) - return False - ignore_protection = True - elif lhs_name == "System`$RandomState": - # TODO: allow setting of legal random states! - # (but consider pickle's insecurity!) - evaluation.message("$RandomState", "rndst", rhs) - return False - elif lhs_name == "System`$Context": - new_context = rhs.get_string_value() - if new_context is None or not valid_context_name( - new_context, allow_initial_backquote=True + # Now, let's add the conditions on the LHS + if condition: + lhs = Expression( + "Condition", + lhs, + condition.leaves[1].apply_rules([rulopc], evaluation)[0], + ) + return lhs, rhs + + +def process_tags_and_upset_dont_allow_custom(tags, upset, self, lhs, focus, evaluation): + # TODO: the following provides a hacky fix for 1259. I know @rocky loves + # this kind of things, but otherwise we need to work on rebuild the pattern + # matching mechanism... + flag_ioi, evaluation.ignore_oneidentity = evaluation.ignore_oneidentity, True + focus = focus.evaluate_leaves(evaluation) + evaluation.ignore_oneidentity = flag_ioi + name = lhs.get_head_name() + if tags is None and not upset: + name = focus.get_lookup_name() + if not name: + evaluation.message(self.get_name(), "setraw", focus) + raise AssignmentException(lhs, None) + tags = [name] + elif upset: + tags = [focus.get_lookup_name()] + else: + allowed_names = [focus.get_lookup_name()] + for name in tags: + if name not in allowed_names: + evaluation.message(self.get_name(), "tagnfd", Symbol(name)) + raise AssignmentException(lhs, None) + return tags + + +def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation): + # TODO: the following provides a hacky fix for 1259. I know @rocky loves + # this kind of things, but otherwise we need to work on rebuild the pattern + # matching mechanism... + name = lhs.get_head_name() + focus = lhs + flag_ioi, evaluation.ignore_oneidentity = evaluation.ignore_oneidentity, True + focus = focus.evaluate_leaves(evaluation) + evaluation.ignore_oneidentity = flag_ioi + if tags is None and not upset: + name = focus.get_lookup_name() + if not name: + evaluation.message(self.get_name(), "setraw", focus) + raise AssignmentException(lhs, None) + tags = [name] + elif upset: + tags = [] + if focus.is_atom(): + evaluation.message(self.get_name(), "normal") + raise AssignmentException(lhs, None) + for leaf in focus.leaves: + name = leaf.get_lookup_name() + tags.append(name) + else: + allowed_names = [focus.get_lookup_name()] + for leaf in focus.get_leaves(): + if not leaf.is_symbol() and leaf.get_head_name() in ("System`HoldPattern",): + leaf = leaf.leaves[0] + if not leaf.is_symbol() and leaf.get_head_name() in ("System`Pattern",): + leaf = leaf.leaves[1] + if not leaf.is_symbol() and leaf.get_head_name() in ( + "System`Blank", + "System`BlankSequence", + "System`BlankNullSequence", ): - evaluation.message(lhs_name, "cxset", rhs) - return False + if len(leaf.leaves) == 1: + leaf = leaf.leaves[0] + + allowed_names.append(leaf.get_lookup_name()) + for name in tags: + if name not in allowed_names: + evaluation.message(self.get_name(), "tagnfd", Symbol(name)) + raise AssignmentException(lhs, None) + + return tags, focus + + +# Here starts the functions that implement `assign_elementary` for different +# kind of expressions. Maybe they should be put in a separated module or +# maybe they should be member functions of _SetOperator. + + +def process_assign_messagename(self, lhs, rhs, evaluation, tags, upset): + lhs, condition = unroll_conditions(lhs) + lhs, rhs = unroll_patterns(lhs, rhs, evaluation) + count = 0 + defs = evaluation.definitions + if len(lhs.leaves) != 2: + evaluation.message_args("MessageName", len(lhs.leaves), 2) + raise AssignmentException(lhs, None) + focus = lhs.leaves[0] + tags = process_tags_and_upset_dont_allow_custom( + tags, upset, self, lhs, focus, evaluation + ) + lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) + rule = Rule(lhs, rhs) + for tag in tags: + if rejected_because_protected(self, lhs, tag, evaluation): + continue + count += 1 + defs.add_message(tag, rule) + return count > 0 + + +def process_assign_default(self, lhs, rhs, evaluation, tags, upset): + lhs, condition = unroll_conditions(lhs) + lhs, rhs = unroll_patterns(lhs, rhs, evaluation) + count = 0 + defs = evaluation.definitions + + if len(lhs.leaves) not in (1, 2, 3): + evaluation.message_args("Default", len(lhs.leaves), 1, 2, 3) + raise AssignmentException(lhs, None) + focus = lhs.leaves[0] + tags = process_tags_and_upset_dont_allow_custom( + tags, upset, self, lhs, focus, evaluation + ) + lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) + rule = Rule(lhs, rhs) + for tag in tags: + if rejected_because_protected(self, lhs, tag, evaluation): + continue + count += 1 + defs.add_default(tag, rule) + return count > 0 + + +def process_assign_format(self, lhs, rhs, evaluation, tags, upset): + lhs, condition = unroll_conditions(lhs) + lhs, rhs = unroll_patterns(lhs, rhs, evaluation) + count = 0 + defs = evaluation.definitions + + if len(lhs.leaves) not in (1, 2): + evaluation.message_args("Format", len(lhs.leaves), 1, 2) + raise AssignmentException(lhs, None) + if len(lhs.leaves) == 2: + form = lhs.leaves[1].get_name() + if not form: + evaluation.message("Format", "fttp", lhs.leaves[1]) + raise AssignmentException(lhs, None) + else: + form = system_symbols( + "StandardForm", + "TraditionalForm", + "OutputForm", + "TeXForm", + "MathMLForm", + ) + lhs = focus = lhs.leaves[0] + tags = process_tags_and_upset_dont_allow_custom( + tags, upset, self, lhs, focus, evaluation + ) + lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) + rule = Rule(lhs, rhs) + for tag in tags: + if rejected_because_protected(self, lhs, tag, evaluation): + continue + count += 1 + defs.add_format(tag, rule, form) + return count > 0 + + +def process_assign_recursion_limit(lhs, rhs, evaluation): + rhs_int_value = rhs.get_int_value() + # if (not rhs_int_value or rhs_int_value < 20) and not + # rhs.get_name() == 'System`Infinity': + if ( + not rhs_int_value or rhs_int_value < 20 or rhs_int_value > MAX_RECURSION_DEPTH + ): # nopep8 + + evaluation.message("$RecursionLimit", "limset", rhs) + raise AssignmentException(lhs, None) + try: + set_python_recursion_limit(rhs_int_value) + except OverflowError: + # TODO: Message + raise AssignmentException(lhs, None) + return False + + +def process_assign_iteration_limit(lhs, rhs, evaluation): + rhs_int_value = rhs.get_int_value() + if ( + not rhs_int_value or rhs_int_value < 20 + ) and not rhs.get_name() == "System`Infinity": + evaluation.message("$IterationLimit", "limset", rhs) + raise AssignmentException(lhs, None) + return False + + +def process_assign_module_number(lhs, rhs, evaluation): + rhs_int_value = rhs.get_int_value() + if not rhs_int_value or rhs_int_value <= 0: + evaluation.message("$ModuleNumber", "set", rhs) + raise AssignmentException(lhs, None) + return False + + +def process_assign_line_number_and_history_length( + self, lhs, rhs, evaluation, tags, upset +): + lhs_name = lhs.get_name() + rhs_int_value = rhs.get_int_value() + if rhs_int_value is None or rhs_int_value < 0: + evaluation.message(lhs_name, "intnn", rhs) + raise AssignmentException(lhs, None) + return False + + +def process_assign_random_state(self, lhs, rhs, evaluation, tags, upset): + # TODO: allow setting of legal random states! + # (but consider pickle's insecurity!) + evaluation.message("$RandomState", "rndst", rhs) + raise AssignmentException(lhs, None) + + +def process_assign_context(self, lhs, rhs, evaluation, tags, upset): + lhs_name = lhs.get_head_name() + new_context = rhs.get_string_value() + if new_context is None or not valid_context_name( + new_context, allow_initial_backquote=True + ): + evaluation.message(lhs_name, "cxset", rhs) + exit() + raise AssignmentException(lhs, None) + + # With $Context in Mathematica you can do some strange + # things: e.g. with $Context set to Global`, something + # like: + # $Context = "`test`"; newsym + # is accepted and creates Global`test`newsym. + # Implement this behaviour by interpreting + # $Context = "`test`" + # as + # $Context = $Context <> "test`" + # + if new_context.startswith("`"): + new_context = evaluation.definitions.get_current_context() + new_context.lstrip( + "`" + ) - # With $Context in Mathematica you can do some strange - # things: e.g. with $Context set to Global`, something - # like: - # $Context = "`test`"; newsym - # is accepted and creates Global`test`newsym. - # Implement this behaviour by interpreting - # $Context = "`test`" - # as - # $Context = $Context <> "test`" - # - if new_context.startswith("`"): - new_context = ( - evaluation.definitions.get_current_context() - + new_context.lstrip("`") - ) + evaluation.definitions.set_current_context(new_context) + return True - evaluation.definitions.set_current_context(new_context) - ignore_protection = True - return True - elif lhs_name == "System`$ContextPath": - currContext = evaluation.definitions.get_current_context() - context_path = [s.get_string_value() for s in rhs.get_leaves()] - context_path = [ - s if (s is None or s[0] != "`") else currContext[:-1] + s - for s in context_path - ] - if rhs.has_form("List", None) and all( - valid_context_name(s) for s in context_path - ): - evaluation.definitions.set_context_path(context_path) - ignore_protection = True - return True - else: - evaluation.message(lhs_name, "cxlist", rhs) - return False - elif lhs_name == "System`$MinPrecision": - # $MinPrecision = Infinity is not allowed - if rhs_int_value is not None and rhs_int_value >= 0: - ignore_protection = True - max_prec = evaluation.definitions.get_config_value("$MaxPrecision") - if max_prec is not None and max_prec < rhs_int_value: - evaluation.message( - "$MinPrecision", "preccon", Symbol("$MinPrecision") - ) - return True - else: - evaluation.message(lhs_name, "precset", lhs, rhs) - return False - elif lhs_name == "System`$MaxPrecision": - if ( - rhs.has_form("DirectedInfinity", 1) - and rhs.leaves[0].get_int_value() == 1 - ): - ignore_protection = True - elif rhs_int_value is not None and rhs_int_value > 0: - ignore_protection = True - min_prec = evaluation.definitions.get_config_value("$MinPrecision") - if min_prec is not None and rhs_int_value < min_prec: - evaluation.message( - "$MaxPrecision", "preccon", Symbol("$MaxPrecision") - ) - ignore_protection = True - return True - else: - evaluation.message(lhs_name, "precset", lhs, rhs) - return False - # To Handle `OptionValue` in `Condition` - rulopc = Rule( - Expression( - "OptionValue", - Expression("Pattern", Symbol("$cond$"), Expression("Blank")), - ), - Expression("OptionValue", lhs.get_head(), Symbol("$cond$")), +def process_assign_context_path(self, lhs, rhs, evaluation, tags, upset): + lhs_name = lhs.get_name() + currContext = evaluation.definitions.get_current_context() + context_path = [s.get_string_value() for s in rhs.get_leaves()] + context_path = [ + s if (s is None or s[0] != "`") else currContext[:-1] + s for s in context_path + ] + if rhs.has_form("List", None) and all(valid_context_name(s) for s in context_path): + evaluation.definitions.set_context_path(context_path) + return True + else: + evaluation.message(lhs_name, "cxlist", rhs) + raise AssignmentException(lhs, None) + + +def process_assign_minprecision(self, lhs, rhs, evaluation, tags, upset): + lhs_name = lhs.get_name() + rhs_int_value = rhs.get_int_value() + # $MinPrecision = Infinity is not allowed + if rhs_int_value is not None and rhs_int_value >= 0: + max_prec = evaluation.definitions.get_config_value("$MaxPrecision") + if max_prec is not None and max_prec < rhs_int_value: + evaluation.message("$MinPrecision", "preccon", Symbol("$MinPrecision")) + raise AssignmentException(lhs, None) + return False + else: + evaluation.message(lhs_name, "precset", lhs, rhs) + raise AssignmentException(lhs, None) + + +def process_assign_maxprecision(self, lhs, rhs, evaluation, tags, upset): + lhs_name = lhs.get_name() + rhs_int_value = rhs.get_int_value() + if rhs.has_form("DirectedInfinity", 1) and rhs.leaves[0].get_int_value() == 1: + return False + elif rhs_int_value is not None and rhs_int_value > 0: + min_prec = evaluation.definitions.get_config_value("$MinPrecision") + if min_prec is not None and rhs_int_value < min_prec: + evaluation.message("$MaxPrecision", "preccon", Symbol("$MaxPrecision")) + raise AssignmentException(lhs, None) + return False + else: + evaluation.message(lhs_name, "precset", lhs, rhs) + raise AssignmentException(lhs, None) + + +def process_assign_definition_values(self, lhs, rhs, evaluation, tags, upset): + name = lhs.get_head_name() + tag = find_tag_and_check(lhs, tags, evaluation) + rules = rhs.get_rules_list() + if rules is None: + evaluation.message(name, "vrule", lhs, rhs) + raise AssignmentException(lhs, None) + evaluation.definitions.set_values(tag, name, rules) + return True + + +def process_assign_options(self, lhs, rhs, evaluation, tags, upset): + lhs_leaves = lhs.leaves + name = lhs.get_head_name() + if len(lhs_leaves) != 1: + evaluation.message_args(name, len(lhs_leaves), 1) + raise AssignmentException(lhs, rhs) + tag = lhs_leaves[0].get_name() + if not tag: + evaluation.message(name, "sym", lhs_leaves[0], 1) + raise AssignmentException(lhs, rhs) + if tags is not None and tags != [tag]: + evaluation.message(name, "tag", Symbol(name), Symbol(tag)) + raise AssignmentException(lhs, rhs) + if is_protected(tag, evaluation.definitions): + evaluation.message(name, "wrsym", Symbol(tag)) + raise AssignmentException(lhs, None) + option_values = rhs.get_option_values(evaluation) + if option_values is None: + evaluation.message(name, "options", rhs) + raise AssignmentException(lhs, None) + evaluation.definitions.set_options(tag, option_values) + return True + + +def process_assign_attributes(self, lhs, rhs, evaluation, tags, upset): + name = lhs.get_head_name() + if len(lhs.leaves) != 1: + evaluation.message_args(name, len(lhs.leaves), 1) + raise AssignmentException(lhs, rhs) + tag = lhs.leaves[0].get_name() + if not tag: + evaluation.message(name, "sym", lhs.leaves[0], 1) + raise AssignmentException(lhs, rhs) + if tags is not None and tags != [tag]: + evaluation.message(name, "tag", Symbol(name), Symbol(tag)) + raise AssignmentException(lhs, rhs) + attributes = get_symbol_list( + rhs, lambda item: evaluation.message(name, "sym", item, 1) + ) + if attributes is None: + raise AssignmentException(lhs, rhs) + if "System`Locked" in evaluation.definitions.get_attributes(tag): + evaluation.message(name, "locked", Symbol(tag)) + raise AssignmentException(lhs, rhs) + evaluation.definitions.set_attributes(tag, attributes) + return True + + +def process_assign_n(self, lhs, rhs, evaluation, tags, upset): + lhs, condition = unroll_conditions(lhs) + lhs, rhs = unroll_patterns(lhs, rhs, evaluation) + defs = evaluation.definitions + + if len(lhs.leaves) not in (1, 2): + evaluation.message_args("N", len(lhs.leaves), 1, 2) + raise AssignmentException(lhs, None) + if len(lhs.leaves) == 1: + nprec = SymbolMachinePrecision + else: + nprec = lhs.leaves[1] + focus = lhs.leaves[0] + lhs = Expression(SymbolN, focus, nprec) + tags = process_tags_and_upset_dont_allow_custom( + tags, upset, self, lhs, focus, evaluation + ) + count = 0 + lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) + rule = Rule(lhs, rhs) + for tag in tags: + if rejected_because_protected(self, lhs, tag, evaluation): + continue + count += 1 + defs.add_nvalue(tag, rule) + return count > 0 + + +def process_assign_other(self, lhs, rhs, evaluation, tags=None, upset=False): + tags, focus = process_tags_and_upset_allow_custom( + tags, upset, self, lhs, evaluation + ) + lhs_name = lhs.get_name() + if lhs_name == "System`$RecursionLimit": + process_assign_recursion_limit(self, lhs, rhs, evaluation, tags, upset) + elif lhs_name in ("System`$Line", "System`$HistoryLength"): + process_assign_line_number_and_history_length( + self, lhs, rhs, evaluation, tags, upset ) - rhs_name = rhs.get_head_name() - while rhs_name == "System`Condition": - if len(rhs.leaves) != 2: - evaluation.message_args("Condition", len(rhs.leaves), 2) - return False - else: - lhs = Expression( - "Condition", lhs, rhs.leaves[1].apply_rules([rulopc], evaluation)[0] - ) - rhs = rhs.leaves[0] - rhs_name = rhs.get_head_name() - - # Now, let's add the conditions on the LHS - if condition: - lhs = Expression( - "Condition", - lhs, - condition.leaves[1].apply_rules([rulopc], evaluation)[0], - ) - rule = Rule(lhs, rhs) - count = 0 - defs = evaluation.definitions - for tag in tags: - if ( - not ignore_protection - and "System`Protected" # noqa - in evaluation.definitions.get_attributes(tag) - ): - if lhs.get_name() == tag: - evaluation.message(self.get_name(), "wrsym", Symbol(tag)) - else: - evaluation.message(self.get_name(), "write", Symbol(tag), lhs) - continue - count += 1 - if form: - defs.add_format(tag, rule, form) - elif nprec: - defs.add_nvalue(tag, rule) - elif default: - defs.add_default(tag, rule) - elif message: - defs.add_message(tag, rule) - else: - if upset: - defs.add_rule(tag, rule, position="up") - else: - defs.add_rule(tag, rule) - if count == 0: + elif lhs_name == "System`$IterationLimit": + process_assign_iteration_limit(self, lhs, rhs, evaluation, tags, upset) + elif lhs_name == "System`$ModuleNumber": + process_assign_module_number(self, lhs, rhs, evaluation, tags, upset) + elif lhs_name == "System`$MinPrecision": + process_assign_minprecision(self, lhs, rhs, evaluation, tags, upset) + elif lhs_name == "System`$MaxPrecision": + process_assign_maxprecision(self, lhs, rhs, evaluation, tags, upset) + else: + return False, tags + return True, tags + + +def assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset=None): + lhs, condition = unroll_conditions(lhs) + lhs, rhs = unroll_patterns(lhs, rhs, evaluation) + count = 0 + defs = evaluation.definitions + ignore_protection, tags = process_assign_other( + self, lhs, rhs, evaluation, tags, upset + ) + lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) + count = 0 + rule = Rule(lhs, rhs) + position = "up" if upset else None + for tag in tags: + if rejected_because_protected(self, lhs, tag, evaluation, ignore_protection): + continue + count += 1 + defs.add_rule(tag, rule, position=position) + return count > 0 + + +class _SetOperator(object): + special_cases = { + "System`OwnValues": process_assign_definition_values, + "System`DownValues": process_assign_definition_values, + "System`SubValues": process_assign_definition_values, + "System`UpValues": process_assign_definition_values, + "System`NValues": process_assign_definition_values, + "System`DefaultValues": process_assign_definition_values, + "System`Messages": process_assign_definition_values, + "System`Attributes": process_assign_attributes, + "System`Options": process_assign_options, + "System`$RandomState": process_assign_random_state, + "System`$Context": process_assign_context, + "System`$ContextPath": process_assign_context_path, + "System`N": process_assign_n, + "System`MessageName": process_assign_messagename, + "System`Default": process_assign_default, + "System`Format": process_assign_format, + } + + def assign_elementary(self, lhs, rhs, evaluation, tags=None, upset=False): + if type(lhs) is Symbol: + name = lhs.name + else: + name = lhs.get_head_name() + lhs._format_cache = None + try: + # Deal with direct assignation to properties of + # the definition object + func = self.special_cases.get(name, None) + if func: + return func(self, lhs, rhs, evaluation, tags, upset) + + return assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset) + except AssignmentException: return False - return True def assign(self, lhs, rhs, evaluation): lhs._format_cache = None + defs = evaluation.definitions if lhs.get_head_name() == "System`List": if not (rhs.get_head_name() == "System`List") or len(lhs.leaves) != len( rhs.leaves @@ -457,10 +667,10 @@ def assign(self, lhs, rhs, evaluation): if not name: evaluation.message(self.get_name(), "setps", symbol) return False - if "System`Protected" in evaluation.definitions.get_attributes(name): + if is_protected(name, defs): evaluation.message(self.get_name(), "wrsym", symbol) return False - rule = evaluation.definitions.get_ownvalue(name) + rule = defs.get_ownvalue(name) if rule is None: evaluation.message(self.get_name(), "noval", symbol) return False @@ -871,7 +1081,7 @@ def format_definition(self, symbol, evaluation, grid=True): lines = [] - def print_rule(rule, up=False, lhs=lambda l: l, rhs=lambda r: r): + def print_rule(rule, up=False, lhs=lambda k: k, rhs=lambda r: r): evaluation.check_stopped() if isinstance(rule, Rule): r = rhs( @@ -1094,7 +1304,7 @@ def format_definition(self, symbol, evaluation, options, grid=True): # Instead, I just copy the code from Definition def show_definitions(self, symbol, evaluation, lines): - def print_rule(rule, up=False, lhs=lambda l: l, rhs=lambda r: r): + def print_rule(rule, up=False, lhs=lambda k: k, rhs=lambda r: r): evaluation.check_stopped() if isinstance(rule, Rule): r = rhs( @@ -1271,7 +1481,7 @@ def apply(self, symbols, evaluation): names = evaluation.definitions.get_matching_names(pattern) for name in names: attributes = evaluation.definitions.get_attributes(name) - if "System`Protected" in attributes: + if is_protected(name, evaluation.definitions): evaluation.message("Clear", "wrsym", Symbol(name)) continue if not self.allow_locked and "System`Locked" in attributes: From 981d0f1c60a4cad57df53521ca8e8eab4c1f58bb Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Tue, 28 Sep 2021 01:27:08 -0300 Subject: [PATCH 155/193] Update setup.py Fix setup accordingly... --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bc4ce71c1..0c0552951 100644 --- a/setup.py +++ b/setup.py @@ -79,7 +79,7 @@ def read(*rnames): CMDCLASS = {} else: EXTENSIONS_DICT = { - "core": ("expression", "numbers", "rules", "pattern"), + "core": ("expression", "number", "rules", "pattern"), "builtin": ["arithmetic", "numeric", "patterns", "graphics"], } EXTENSIONS = [ From d86e7041f5566498654efc60fc139c8474615167 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 3 Oct 2021 10:51:09 -0300 Subject: [PATCH 156/193] List overhaul * move all the "algorithmic" routines in `mathics.core.lists` to an independent module in `mathics.algorithm.parts` * cithonize `mathics.algorithm.parts` * fix all the warnings produced by flake8 * Move `Exception` classes from `mathics.builtin.base` to `mathics.builtin.exceptions` to avoid circular references. --- mathics/algorithm/parts.py | 609 ++++++++++++++++++++++ mathics/builtin/assignment.py | 2 +- mathics/builtin/base.py | 37 +- mathics/builtin/exceptions.py | 31 ++ mathics/builtin/list/eol.py | 4 +- mathics/builtin/lists.py | 737 +++------------------------ mathics/builtin/numbers/algebra.py | 2 +- mathics/builtin/patterns.py | 3 +- mathics/builtin/sparse.py | 2 +- mathics/builtin/string/operations.py | 2 +- mathics/builtin/tensors.py | 2 +- setup.py | 1 + 12 files changed, 736 insertions(+), 696 deletions(-) create mode 100644 mathics/algorithm/parts.py create mode 100644 mathics/builtin/exceptions.py diff --git a/mathics/algorithm/parts.py b/mathics/algorithm/parts.py new file mode 100644 index 000000000..740f06022 --- /dev/null +++ b/mathics/algorithm/parts.py @@ -0,0 +1,609 @@ +# -*- coding: utf-8 -*- + +""" +Algorithms to access and manipulate elements in nested lists / expressions +""" + + +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.atoms import Integer, from_python +from mathics.core.systemsymbols import SymbolInfinity + +from mathics.builtin.exceptions import ( + InvalidLevelspecError, + MessageException, + PartDepthError, + PartRangeError, +) + + +def join_lists(lists): + new_list = [] + for list in lists: + new_list.extend(list) + return new_list + + +def get_part(varlist, indices): + "Simple part extraction. indices must be a list of python integers." + + def rec(cur, rest): + if rest: + if cur.is_atom(): + raise PartDepthError(rest[0]) + pos = rest[0] + leaves = cur.get_leaves() + try: + if pos > 0: + part = leaves[pos - 1] + elif pos == 0: + part = cur.get_head() + else: + part = leaves[pos] + except IndexError: + raise PartRangeError + return rec(part, rest[1:]) + else: + return cur + + return rec(varlist, indices).copy() + + +def set_part(varlist, indices, newval): + "Simple part replacement. indices must be a list of python integers." + + def rec(cur, rest): + if len(rest) > 1: + pos = rest[0] + if cur.is_atom(): + raise PartDepthError + try: + if pos > 0: + part = cur._leaves[pos - 1] + elif pos == 0: + part = cur.get_head() + else: + part = cur._leaves[pos] + except IndexError: + raise PartRangeError + return rec(part, rest[1:]) + elif len(rest) == 1: + pos = rest[0] + if cur.is_atom(): + raise PartDepthError + try: + if pos > 0: + cur.set_leaf(pos - 1, newval) + elif pos == 0: + cur.set_head(newval) + else: + cur.set_leaf(pos, newval) + except IndexError: + raise PartRangeError + + rec(varlist, indices) + + +def _parts_all_selector(): + start = 1 + stop = None + step = 1 + + def select(inner): + if inner.is_atom(): + raise MessageException("Part", "partd") + py_slice = python_seq(start, stop, step, len(inner.leaves)) + if py_slice is None: + raise MessageException("Part", "take", start, stop, inner) + return inner.leaves[py_slice] + + return select + + +def _parts_span_selector(pspec): + if len(pspec.leaves) > 3: + raise MessageException("Part", "span", pspec) + start = 1 + stop = None + step = 1 + if len(pspec.leaves) > 0: + start = pspec.leaves[0].get_int_value() + if len(pspec.leaves) > 1: + stop = pspec.leaves[1].get_int_value() + if stop is None: + if pspec.leaves[1].get_name() == "System`All": + stop = None + else: + raise MessageException("Part", "span", pspec) + if len(pspec.leaves) > 2: + step = pspec.leaves[2].get_int_value() + + if start == 0 or stop == 0: + # index 0 is undefined + raise MessageException("Part", "span", 0) + + if start is None or step is None: + raise MessageException("Part", "span", pspec) + + def select(inner): + if inner.is_atom(): + raise MessageException("Part", "partd") + py_slice = python_seq(start, stop, step, len(inner.leaves)) + if py_slice is None: + raise MessageException("Part", "take", start, stop, inner) + return inner.leaves[py_slice] + + return select + + +def _parts_sequence_selector(pspec): + if not isinstance(pspec, (tuple, list)): + indices = [pspec] + else: + indices = pspec + + for index in indices: + if not isinstance(index, Integer): + raise MessageException("Part", "pspec", pspec) + + def select(inner): + if inner.is_atom(): + raise MessageException("Part", "partd") + + leaves = inner.leaves + n = len(leaves) + + for index in indices: + int_index = index.value + + if int_index == 0: + yield inner.head + elif 1 <= int_index <= n: + yield leaves[int_index - 1] + elif -n <= int_index <= -1: + yield leaves[int_index] + else: + raise MessageException("Part", "partw", index, inner) + + return select + + +def _part_selectors(indices): + for index in indices: + if index.has_form("Span", None): + yield _parts_span_selector(index) + elif index.get_name() == "System`All": + yield _parts_all_selector() + elif index.has_form("List", None): + yield _parts_sequence_selector(index.leaves) + elif isinstance(index, Integer): + yield _parts_sequence_selector(index), lambda x: x[0] + else: + raise MessageException("Part", "pspec", index) + + +def _list_parts(items, selectors, heads, evaluation, assignment): + if not selectors: + for item in items: + yield item + else: + selector = selectors[0] + if isinstance(selector, tuple): + select, unwrap = selector + else: + select = selector + unwrap = None + + for item in items: + selected = list(select(item)) + + picked = list( + _list_parts(selected, selectors[1:], heads, evaluation, assignment) + ) + + if unwrap is None: + if assignment: + expr = Expression(item.head, *picked) + expr.original = None + expr.set_positions() + else: + expr = item.restructure(item.head, picked, evaluation) + + yield expr + else: + yield unwrap(picked) + + +def _parts(items, selectors, evaluation, assignment=False): + heads = {} + return list(_list_parts([items], list(selectors), heads, evaluation, assignment))[0] + + +def walk_parts(list_of_list, indices, evaluation, assign_list=None): + walk_list = list_of_list[0] + + if assign_list is not None: + # this double copying is needed to make the current logic in + # the assign_list and its access to original work. + + walk_list = walk_list.copy() + walk_list.set_positions() + list_of_list = [walk_list] + + walk_list = walk_list.copy() + walk_list.set_positions() + + indices = [index.evaluate(evaluation) for index in indices] + + try: + result = _parts( + walk_list, _part_selectors(indices), evaluation, assign_list is not None + ) + except MessageException as e: + e.message(evaluation) + return False + + if assign_list is not None: + + def replace_item(all, item, new): + if item.position is None: + all[0] = new + else: + item.position.replace(new) + + def process_level(item, assignment): + if item.is_atom(): + replace_item(list_of_list, item.original, assignment) + elif assignment.get_head_name() != "System`List" or len(item.leaves) != len( + assignment.leaves + ): + if item.original: + replace_item(list_of_list, item.original, assignment) + else: + for leaf in item.leaves: + process_level(leaf, assignment) + else: + for sub_item, sub_assignment in zip(item.leaves, assignment.leaves): + process_level(sub_item, sub_assignment) + + process_level(result, assign_list) + + result = list_of_list[0] + result.clear_cache() + + return result + + +def is_in_level(current, depth, start=1, stop=None): + if stop is None: + stop = current + if start < 0: + start += current + depth + 1 + if stop < 0: + stop += current + depth + 1 + return start <= current <= stop + + +def walk_levels( + expr, + start=1, + stop=None, + current=0, + heads=False, + callback=lambda p: p, + include_pos=False, + cur_pos=[], +): + if expr.is_atom(): + depth = 0 + new_expr = expr + else: + depth = 0 + if heads: + head, head_depth = walk_levels( + expr.head, + start, + stop, + current + 1, + heads, + callback, + include_pos, + cur_pos + [0], + ) + else: + head = expr.head + leaves = [] + for index, leaf in enumerate(expr.leaves): + leaf, leaf_depth = walk_levels( + leaf, + start, + stop, + current + 1, + heads, + callback, + include_pos, + cur_pos + [index + 1], + ) + if leaf_depth + 1 > depth: + depth = leaf_depth + 1 + leaves.append(leaf) + new_expr = Expression(head, *leaves) + if is_in_level(current, depth, start, stop): + if include_pos: + new_expr = callback(new_expr, cur_pos) + else: + new_expr = callback(new_expr) + return new_expr, depth + + +def python_levelspec(levelspec): + def value_to_level(expr): + value = expr.get_int_value() + if value is None: + if expr == Expression("DirectedInfinity", 1): + return None + else: + raise InvalidLevelspecError + else: + return value + + if levelspec.has_form("List", None): + values = [value_to_level(leaf) for leaf in levelspec.leaves] + if len(values) == 1: + return values[0], values[0] + elif len(values) == 2: + return values[0], values[1] + else: + raise InvalidLevelspecError + elif isinstance(levelspec, Symbol) and levelspec.get_name() == "System`All": + return 0, None + else: + return 1, value_to_level(levelspec) + + +def python_seq(start, stop, step, length): + """ + Converts mathematica sequence tuple to python slice object. + + Based on David Mashburn's generic slice: + https://gist.github.com/davidmashburn/9764309 + """ + if step == 0: + return None + + # special empty case + if stop is None and length is not None: + empty_stop = length + else: + empty_stop = stop + if start is not None and empty_stop + 1 == start and step > 0: + return slice(0, 0, 1) + + if start == 0 or stop == 0: + return None + + # wrap negative values to postive and convert from 1-based to 0-based + if start < 0: + start += length + else: + start -= 1 + + if stop is None: + if step < 0: + stop = 0 + else: + stop = length - 1 + elif stop < 0: + stop += length + else: + assert stop > 0 + stop -= 1 + + # check bounds + if ( + not 0 <= start < length + or not 0 <= stop < length + or step > 0 + and start - stop > 1 + or step < 0 + and stop - start > 1 + ): # nopep8 + return None + + # include the stop value + if step > 0: + stop += 1 + else: + stop -= 1 + if stop == -1: + stop = None + if start == 0: + start = None + + return slice(start, stop, step) + + +def convert_seq(seq): + """ + converts a sequence specification into a (start, stop, step) tuple. + returns None on failure + """ + start, stop, step = 1, None, 1 + name = seq.get_name() + value = seq.get_int_value() + if name == "System`All": + pass + elif name == "System`None": + stop = 0 + elif value is not None: + if value > 0: + stop = value + else: + start = value + elif seq.has_form("List", 1, 2, 3): + if len(seq.leaves) == 1: + start = stop = seq.leaves[0].get_int_value() + if stop is None: + return None + else: + start = seq.leaves[0].get_int_value() + stop = seq.leaves[1].get_int_value() + if start is None or stop is None: + return None + if len(seq.leaves) == 3: + step = seq.leaves[2].get_int_value() + if step is None: + return None + else: + return None + return (start, stop, step) + + +def _drop_take_selector(name, seq, sliced): + seq_tuple = convert_seq(seq) + if seq_tuple is None: + raise MessageException(name, "seqs", seq) + + def select(inner): + start, stop, step = seq_tuple + if inner.is_atom(): + py_slice = None + else: + py_slice = python_seq(start, stop, step, len(inner.leaves)) + if py_slice is None: + if stop is None: + stop = SymbolInfinity + raise MessageException(name, name.lower(), start, stop, inner) + return sliced(inner.leaves, py_slice) + + return select + + +def _take_span_selector(seq): + return _drop_take_selector("Take", seq, lambda x, s: x[s]) + + +def _drop_span_selector(seq): + def sliced(x, s): + y = list(x[:]) + del y[s] + return y + + return _drop_take_selector("Drop", seq, sliced) + + +def deletecases_with_levelspec(expr, pattern, evaluation, levelspec=1, n=-1): + """ + This function walks the expression `expr` and deleting occurrencies of `pattern` + + If levelspec specifies a number, only those positions with `levelspec` "coordinates" are return. By default, it just return occurences in the first level. + + If a tuple (nmin, nmax) is provided, it just return those occurences with a number of "coordinates" between nmin and nmax. + n indicates the number of occurrences to return. By default, it returns all the occurences. + """ + nothing = Symbol("System`Nothing") + from mathics.builtin.patterns import Matcher + + match = Matcher(pattern) + match = match.match + if type(levelspec) is int: + lsmin = 1 + lsmax = levelspec + 1 + else: + lsmin = levelspec[0] + if levelspec[1]: + lsmax = levelspec[1] + 1 + else: + lsmax = -1 + tree = [[expr]] + changed_marks = [ + [False], + ] + curr_index = [0] + + while curr_index[0] != 1: + # If the end of the branch is reached, or no more elements to delete out + if curr_index[-1] == len(tree[-1]) or n == 0: + leaves = tree[-1] + tree.pop() + # check if some of the leaves was changed + changed = any(changed_marks[-1]) + changed_marks.pop() + if changed: + leaves = [leaf for leaf in leaves if leaf is not nothing] + curr_index.pop() + if len(curr_index) == 0: + break + idx = curr_index[-1] + changed = changed or changed_marks[-1][idx] + changed_marks[-1][idx] = changed + if changed: + head = tree[-1][curr_index[-1]].get_head() + tree[-1][idx] = Expression(head, *leaves) + if len(curr_index) == 0: + break + curr_index[-1] = curr_index[-1] + 1 + continue + curr_leave = tree[-1][curr_index[-1]] + if match(curr_leave, evaluation) and (len(curr_index) > lsmin): + tree[-1][curr_index[-1]] = nothing + changed_marks[-1][curr_index[-1]] = True + curr_index[-1] = curr_index[-1] + 1 + n = n - 1 + continue + if curr_leave.is_atom() or lsmax == len(curr_index): + curr_index[-1] = curr_index[-1] + 1 + continue + else: + tree.append(list(curr_leave.get_leaves())) + changed_marks.append([False for s in tree[-1]]) + curr_index.append(0) + return tree[0][0] + + +def find_matching_indices_with_levelspec(expr, pattern, evaluation, levelspec=1, n=-1): + """ + This function walks the expression `expr` looking for a pattern `pattern` + and returns the positions of each occurence. + + If levelspec specifies a number, only those positions with `levelspec` "coordinates" are return. By default, it just return occurences in the first level. + + If a tuple (nmin, nmax) is provided, it just return those occurences with a number of "coordinates" between nmin and nmax. + n indicates the number of occurrences to return. By default, it returns all the occurences. + """ + from mathics.builtin.patterns import Matcher + + match = Matcher(pattern) + match = match.match + if type(levelspec) is int: + lsmin = 0 + lsmax = levelspec + else: + lsmin = levelspec[0] + lsmax = levelspec[1] + tree = [expr.get_leaves()] + curr_index = [0] + found = [] + while len(tree) > 0: + if n == 0: + break + if curr_index[-1] == len(tree[-1]): + curr_index.pop() + tree.pop() + if len(curr_index) != 0: + curr_index[-1] = curr_index[-1] + 1 + continue + curr_leave = tree[-1][curr_index[-1]] + if match(curr_leave, evaluation) and (len(curr_index) >= lsmin): + found.append([from_python(i) for i in curr_index]) + curr_index[-1] = curr_index[-1] + 1 + n = n - 1 + continue + if curr_leave.is_atom() or lsmax == len(curr_index): + curr_index[-1] = curr_index[-1] + 1 + continue + else: + tree.append(curr_leave.get_leaves()) + curr_index.append(0) + return found diff --git a/mathics/builtin/assignment.py b/mathics/builtin/assignment.py index 358f4556a..e9e194aa5 100644 --- a/mathics/builtin/assignment.py +++ b/mathics/builtin/assignment.py @@ -23,7 +23,7 @@ from mathics.core.atoms import String from mathics.core.definitions import PyMathicsLoadException -from mathics.builtin.lists import walk_parts +from mathics.algorithm.parts import walk_parts from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 283387334..aeefdadfc 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -11,6 +11,14 @@ from mathics.version import __version__ # noqa used in loading to check consistency. +from mathics.builtin.exceptions import ( + BoxConstructError, + InvalidLevelspecError, + MessageException, + PartError, + PartDepthError, + PartRangeError, +) from mathics.core.convert import from_sympy from mathics.core.definitions import Definition from mathics.core.parser.util import SystemDefinitions, PyMathicsDefinitions @@ -593,27 +601,6 @@ def prepare_mathics(self, sympy_expr): return sympy_expr -class InvalidLevelspecError(Exception): - pass - - -class PartError(Exception): - pass - - -class PartDepthError(PartError): - def __init__(self, index=0): - self.index = index - - -class PartRangeError(PartError): - pass - - -class BoxConstructError(Exception): - pass - - class BoxConstruct(InstanceableBuiltin): def __new__(cls, *leaves, **kwargs): instance = super().__new__(cls, *leaves, **kwargs) @@ -778,14 +765,6 @@ def get_attributes(self, definitions): return self.head.get_attributes(definitions) -class MessageException(Exception): - def __init__(self, *message): - self._message = message - - def message(self, evaluation): - evaluation.message(*self._message) - - class NegativeIntegerException(Exception): pass diff --git a/mathics/builtin/exceptions.py b/mathics/builtin/exceptions.py new file mode 100644 index 000000000..b2bec8ca9 --- /dev/null +++ b/mathics/builtin/exceptions.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from mathics.version import __version__ # noqa used in loading to check consistency. + + +class BoxConstructError(Exception): + pass + + +class InvalidLevelspecError(Exception): + pass + + +class PartError(Exception): + pass + + +class PartDepthError(PartError): + def __init__(self, index=0): + self.index = index + + +class PartRangeError(PartError): + pass + + +class MessageException(Exception): + def __init__(self, *message): + self._message = message + + def message(self, evaluation): + evaluation.message(*self._message) diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index e3dbde763..741ede0e4 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -17,12 +17,12 @@ PartError, ) -from mathics.builtin.lists import ( +from mathics.builtin.lists import list_boxes +from mathics.algorithm.parts import ( _drop_span_selector, _parts, _take_span_selector, deletecases_with_levelspec, - list_boxes, python_levelspec, set_part, walk_levels, diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index e34f58fea..26a2e207b 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -13,7 +13,10 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.algorithm.introselect import introselect - +from mathics.algorithm.parts import ( + python_levelspec, + walk_levels, +) from mathics.algorithm.clusters import ( AutomaticMergeCriterion, AutomaticSplitCriterion, @@ -71,122 +74,6 @@ ) -def deletecases_with_levelspec(expr, pattern, evaluation, levelspec=1, n=-1): - """ - This function walks the expression `expr` and deleting occurrencies of `pattern` - - If levelspec specifies a number, only those positions with `levelspec` "coordinates" are return. By default, it just return occurences in the first level. - - If a tuple (nmin, nmax) is provided, it just return those occurences with a number of "coordinates" between nmin and nmax. - n indicates the number of occurrences to return. By default, it returns all the occurences. - """ - nothing = Symbol("System`Nothing") - from mathics.builtin.patterns import Matcher - - match = Matcher(pattern) - match = match.match - if type(levelspec) is int: - lsmin = 1 - lsmax = levelspec + 1 - else: - lsmin = levelspec[0] - if levelspec[1]: - lsmax = levelspec[1] + 1 - else: - lsmax = -1 - tree = [[expr]] - changed_marks = [ - [False], - ] - curr_index = [0] - - while curr_index[0] != 1: - # If the end of the branch is reached, or no more elements to delete out - if curr_index[-1] == len(tree[-1]) or n == 0: - leaves = tree[-1] - tree.pop() - # check if some of the leaves was changed - changed = any(changed_marks[-1]) - changed_marks.pop() - if changed: - leaves = [leaf for leaf in leaves if leaf is not nothing] - curr_index.pop() - if len(curr_index) == 0: - break - idx = curr_index[-1] - changed = changed or changed_marks[-1][idx] - changed_marks[-1][idx] = changed - if changed: - head = tree[-1][curr_index[-1]].get_head() - tree[-1][idx] = Expression(head, *leaves) - if len(curr_index) == 0: - break - curr_index[-1] = curr_index[-1] + 1 - continue - curr_leave = tree[-1][curr_index[-1]] - if match(curr_leave, evaluation) and (len(curr_index) > lsmin): - tree[-1][curr_index[-1]] = nothing - changed_marks[-1][curr_index[-1]] = True - curr_index[-1] = curr_index[-1] + 1 - n = n - 1 - continue - if curr_leave.is_atom() or lsmax == len(curr_index): - curr_index[-1] = curr_index[-1] + 1 - continue - else: - tree.append(list(curr_leave.get_leaves())) - changed_marks.append([False for l in tree[-1]]) - curr_index.append(0) - return tree[0][0] - - -def find_matching_indices_with_levelspec(expr, pattern, evaluation, levelspec=1, n=-1): - """ - This function walks the expression `expr` looking for a pattern `pattern` - and returns the positions of each occurence. - - If levelspec specifies a number, only those positions with `levelspec` "coordinates" are return. By default, it just return occurences in the first level. - - If a tuple (nmin, nmax) is provided, it just return those occurences with a number of "coordinates" between nmin and nmax. - n indicates the number of occurrences to return. By default, it returns all the occurences. - """ - from mathics.builtin.patterns import Matcher - - match = Matcher(pattern) - match = match.match - if type(levelspec) is int: - lsmin = 0 - lsmax = levelspec - else: - lsmin = levelspec[0] - lsmax = levelspec[1] - tree = [expr.get_leaves()] - curr_index = [0] - found = [] - while len(tree) > 0: - if n == 0: - break - if curr_index[-1] == len(tree[-1]): - curr_index.pop() - tree.pop() - if len(curr_index) != 0: - curr_index[-1] = curr_index[-1] + 1 - continue - curr_leave = tree[-1][curr_index[-1]] - if match(curr_leave, evaluation) and (len(curr_index) >= lsmin): - found.append([from_python(i) for i in curr_index]) - curr_index[-1] = curr_index[-1] + 1 - n = n - 1 - continue - if curr_leave.is_atom() or lsmax == len(curr_index): - curr_index[-1] = curr_index[-1] + 1 - continue - else: - tree.append(curr_leave.get_leaves()) - curr_index.append(0) - return found - - class All(Predefined): """
@@ -251,7 +138,7 @@ def apply_list(self, values, evaluation): return try: ba = bytearray([b.get_int_value() for b in values._leaves]) - except: + except Exception: evaluation.message("ByteArray", "aotd", values) return return Expression(SymbolByteArray, ByteArrayAtom(ba)) @@ -466,7 +353,7 @@ def apply(self, expr, positions, evaluation): # Create new python list of the positions and sort it positions = ( - [l for l in positions.leaves] + [t for t in positions.leaves] if positions.leaves[0].has_form("List", None) else [positions] ) @@ -504,11 +391,11 @@ class Failure(Builtin): pass -## From backports in CellsToTeX. This functions provides compatibility to WMA 10. -## TODO: -## * Add doctests -## * Translate to python the more complex rules -## * Complete the support. +# From backports in CellsToTeX. This functions provides compatibility to WMA 10. +# TODO: +# * Add doctests +# * Translate to python the more complex rules +# * Complete the support. class Key(Builtin): @@ -727,481 +614,6 @@ class None_(Predefined): name = "None" -def join_lists(lists): - new_list = [] - for list in lists: - new_list.extend(list) - return new_list - - -def get_part(varlist, indices): - "Simple part extraction. indices must be a list of python integers." - - def rec(cur, rest): - if rest: - if cur.is_atom(): - raise PartDepthError(rest[0]) - pos = rest[0] - leaves = cur.get_leaves() - try: - if pos > 0: - part = leaves[pos - 1] - elif pos == 0: - part = cur.get_head() - else: - part = leaves[pos] - except IndexError: - raise PartRangeError - return rec(part, rest[1:]) - else: - return cur - - return rec(varlist, indices).copy() - - -def set_part(varlist, indices, newval): - "Simple part replacement. indices must be a list of python integers." - - def rec(cur, rest): - if len(rest) > 1: - pos = rest[0] - if cur.is_atom(): - raise PartDepthError - try: - if pos > 0: - part = cur._leaves[pos - 1] - elif pos == 0: - part = cur.get_head() - else: - part = cur._leaves[pos] - except IndexError: - raise PartRangeError - return rec(part, rest[1:]) - elif len(rest) == 1: - pos = rest[0] - if cur.is_atom(): - raise PartDepthError - try: - if pos > 0: - cur.set_leaf(pos - 1, newval) - elif pos == 0: - cur.set_head(newval) - else: - cur.set_leaf(pos, newval) - except IndexError: - raise PartRangeError - - rec(varlist, indices) - - -def _parts_all_selector(): - start = 1 - stop = None - step = 1 - - def select(inner): - if inner.is_atom(): - raise MessageException("Part", "partd") - py_slice = python_seq(start, stop, step, len(inner.leaves)) - if py_slice is None: - raise MessageException("Part", "take", start, stop, inner) - return inner.leaves[py_slice] - - return select - - -def _parts_span_selector(pspec): - if len(pspec.leaves) > 3: - raise MessageException("Part", "span", pspec) - start = 1 - stop = None - step = 1 - if len(pspec.leaves) > 0: - start = pspec.leaves[0].get_int_value() - if len(pspec.leaves) > 1: - stop = pspec.leaves[1].get_int_value() - if stop is None: - if pspec.leaves[1].get_name() == "System`All": - stop = None - else: - raise MessageException("Part", "span", pspec) - if len(pspec.leaves) > 2: - step = pspec.leaves[2].get_int_value() - - if start == 0 or stop == 0: - # index 0 is undefined - raise MessageException("Part", "span", 0) - - if start is None or step is None: - raise MessageException("Part", "span", pspec) - - def select(inner): - if inner.is_atom(): - raise MessageException("Part", "partd") - py_slice = python_seq(start, stop, step, len(inner.leaves)) - if py_slice is None: - raise MessageException("Part", "take", start, stop, inner) - return inner.leaves[py_slice] - - return select - - -def _parts_sequence_selector(pspec): - if not isinstance(pspec, (tuple, list)): - indices = [pspec] - else: - indices = pspec - - for index in indices: - if not isinstance(index, Integer): - raise MessageException("Part", "pspec", pspec) - - def select(inner): - if inner.is_atom(): - raise MessageException("Part", "partd") - - leaves = inner.leaves - n = len(leaves) - - for index in indices: - int_index = index.value - - if int_index == 0: - yield inner.head - elif 1 <= int_index <= n: - yield leaves[int_index - 1] - elif -n <= int_index <= -1: - yield leaves[int_index] - else: - raise MessageException("Part", "partw", index, inner) - - return select - - -def _part_selectors(indices): - for index in indices: - if index.has_form("Span", None): - yield _parts_span_selector(index) - elif index.get_name() == "System`All": - yield _parts_all_selector() - elif index.has_form("List", None): - yield _parts_sequence_selector(index.leaves) - elif isinstance(index, Integer): - yield _parts_sequence_selector(index), lambda x: x[0] - else: - raise MessageException("Part", "pspec", index) - - -def _list_parts(items, selectors, heads, evaluation, assignment): - if not selectors: - for item in items: - yield item - else: - selector = selectors[0] - if isinstance(selector, tuple): - select, unwrap = selector - else: - select = selector - unwrap = None - - for item in items: - selected = list(select(item)) - - picked = list( - _list_parts(selected, selectors[1:], heads, evaluation, assignment) - ) - - if unwrap is None: - if assignment: - expr = Expression(item.head, *picked) - expr.original = None - expr.set_positions() - else: - expr = item.restructure(item.head, picked, evaluation) - - yield expr - else: - yield unwrap(picked) - - -def _parts(items, selectors, evaluation, assignment=False): - heads = {} - return list(_list_parts([items], list(selectors), heads, evaluation, assignment))[0] - - -def walk_parts(list_of_list, indices, evaluation, assign_list=None): - walk_list = list_of_list[0] - - if assign_list is not None: - # this double copying is needed to make the current logic in - # the assign_list and its access to original work. - - walk_list = walk_list.copy() - walk_list.set_positions() - list_of_list = [walk_list] - - walk_list = walk_list.copy() - walk_list.set_positions() - - indices = [index.evaluate(evaluation) for index in indices] - - try: - result = _parts( - walk_list, _part_selectors(indices), evaluation, assign_list is not None - ) - except MessageException as e: - e.message(evaluation) - return False - - if assign_list is not None: - - def replace_item(all, item, new): - if item.position is None: - all[0] = new - else: - item.position.replace(new) - - def process_level(item, assignment): - if item.is_atom(): - replace_item(list_of_list, item.original, assignment) - elif assignment.get_head_name() != "System`List" or len(item.leaves) != len( - assignment.leaves - ): - if item.original: - replace_item(list_of_list, item.original, assignment) - else: - for leaf in item.leaves: - process_level(leaf, assignment) - else: - for sub_item, sub_assignment in zip(item.leaves, assignment.leaves): - process_level(sub_item, sub_assignment) - - process_level(result, assign_list) - - result = list_of_list[0] - result.clear_cache() - - return result - - -def is_in_level(current, depth, start=1, stop=None): - if stop is None: - stop = current - if start < 0: - start += current + depth + 1 - if stop < 0: - stop += current + depth + 1 - return start <= current <= stop - - -def walk_levels( - expr, - start=1, - stop=None, - current=0, - heads=False, - callback=lambda l: l, - include_pos=False, - cur_pos=[], -): - if expr.is_atom(): - depth = 0 - new_expr = expr - else: - depth = 0 - if heads: - head, head_depth = walk_levels( - expr.head, - start, - stop, - current + 1, - heads, - callback, - include_pos, - cur_pos + [0], - ) - else: - head = expr.head - leaves = [] - for index, leaf in enumerate(expr.leaves): - leaf, leaf_depth = walk_levels( - leaf, - start, - stop, - current + 1, - heads, - callback, - include_pos, - cur_pos + [index + 1], - ) - if leaf_depth + 1 > depth: - depth = leaf_depth + 1 - leaves.append(leaf) - new_expr = Expression(head, *leaves) - if is_in_level(current, depth, start, stop): - if include_pos: - new_expr = callback(new_expr, cur_pos) - else: - new_expr = callback(new_expr) - return new_expr, depth - - -def python_levelspec(levelspec): - def value_to_level(expr): - value = expr.get_int_value() - if value is None: - if expr == Expression("DirectedInfinity", 1): - return None - else: - raise InvalidLevelspecError - else: - return value - - if levelspec.has_form("List", None): - values = [value_to_level(leaf) for leaf in levelspec.leaves] - if len(values) == 1: - return values[0], values[0] - elif len(values) == 2: - return values[0], values[1] - else: - raise InvalidLevelspecError - elif isinstance(levelspec, Symbol) and levelspec.get_name() == "System`All": - return 0, None - else: - return 1, value_to_level(levelspec) - - -def python_seq(start, stop, step, length): - """ - Converts mathematica sequence tuple to python slice object. - - Based on David Mashburn's generic slice: - https://gist.github.com/davidmashburn/9764309 - """ - if step == 0: - return None - - # special empty case - if stop is None and length is not None: - empty_stop = length - else: - empty_stop = stop - if start is not None and empty_stop + 1 == start and step > 0: - return slice(0, 0, 1) - - if start == 0 or stop == 0: - return None - - # wrap negative values to postive and convert from 1-based to 0-based - if start < 0: - start += length - else: - start -= 1 - - if stop is None: - if step < 0: - stop = 0 - else: - stop = length - 1 - elif stop < 0: - stop += length - else: - assert stop > 0 - stop -= 1 - - # check bounds - if ( - not 0 <= start < length - or not 0 <= stop < length - or step > 0 - and start - stop > 1 - or step < 0 - and stop - start > 1 - ): # nopep8 - return None - - # include the stop value - if step > 0: - stop += 1 - else: - stop -= 1 - if stop == -1: - stop = None - if start == 0: - start = None - - return slice(start, stop, step) - - -def convert_seq(seq): - """ - converts a sequence specification into a (start, stop, step) tuple. - returns None on failure - """ - start, stop, step = 1, None, 1 - name = seq.get_name() - value = seq.get_int_value() - if name == "System`All": - pass - elif name == "System`None": - stop = 0 - elif value is not None: - if value > 0: - stop = value - else: - start = value - elif seq.has_form("List", 1, 2, 3): - if len(seq.leaves) == 1: - start = stop = seq.leaves[0].get_int_value() - if stop is None: - return None - else: - start = seq.leaves[0].get_int_value() - stop = seq.leaves[1].get_int_value() - if start is None or stop is None: - return None - if len(seq.leaves) == 3: - step = seq.leaves[2].get_int_value() - if step is None: - return None - else: - return None - return (start, stop, step) - - -def _drop_take_selector(name, seq, sliced): - seq_tuple = convert_seq(seq) - if seq_tuple is None: - raise MessageException(name, "seqs", seq) - - def select(inner): - start, stop, step = seq_tuple - if inner.is_atom(): - py_slice = None - else: - py_slice = python_seq(start, stop, step, len(inner.leaves)) - if py_slice is None: - if stop is None: - stop = Symbol("Infinity") - raise MessageException(name, name.lower(), start, stop, inner) - return sliced(inner.leaves, py_slice) - - return select - - -def _take_span_selector(seq): - return _drop_take_selector("Take", seq, lambda x, s: x[s]) - - -def _drop_span_selector(seq): - def sliced(x, s): - y = list(x[:]) - del y[s] - return y - - return _drop_take_selector("Drop", seq, sliced) - - class Split(Builtin): """
@@ -1268,7 +680,7 @@ def apply(self, mlist, test, evaluation): inner = structure("List", mlist, evaluation) outer = structure(mlist.head, inner, evaluation) - return outer([inner(l) for l in result]) + return outer([inner(t) for t in result]) class SplitBy(Builtin): @@ -1306,7 +718,7 @@ def apply(self, mlist, func, evaluation): evaluation.message("Select", "normal", 1, expr) return - plist = [l for l in mlist.leaves] + plist = [t for t in mlist.leaves] result = [[plist[0]]] prev = Expression(func, plist[0]).evaluate(evaluation) @@ -1320,7 +732,7 @@ def apply(self, mlist, func, evaluation): inner = structure("List", mlist, evaluation) outer = structure(mlist.head, inner, evaluation) - return outer([inner(l) for l in result]) + return outer([inner(t) for t in result]) def apply_multiple(self, mlist, funcs, evaluation): "SplitBy[mlist_, funcs_List]" @@ -1927,8 +1339,8 @@ class _Rectangular(Builtin): # A helper for Builtins X that allow X[{a1, a2, ...}, {b1, b2, ...}, ...] to be evaluated # as {X[{a1, b1, ...}, {a1, b2, ...}, ...]}. - def rect(self, l): - lengths = [len(leaf.leaves) for leaf in l.leaves] + def rect(self, leaf): + lengths = [len(leaf.leaves) for leaf in leaf.leaves] if all(length == 0 for length in lengths): return # leave as is, without error @@ -1936,7 +1348,9 @@ def rect(self, l): if any(length != n_columns for length in lengths[1:]): raise _NotRectangularException() - transposed = [[leaf.leaves[i] for leaf in l.leaves] for i in range(n_columns)] + transposed = [ + [sleaf.leaves[i] for sleaf in leaf.leaves] for i in range(n_columns) + ] return Expression( "List", @@ -1964,15 +1378,15 @@ class RankedMin(Builtin): "rank": "The specified rank `1` is not between 1 and `2`.", } - def apply(self, l, n, evaluation): - "RankedMin[l_List, n_Integer]" + def apply(self, leaf, n, evaluation): + "RankedMin[leaf_List, n_Integer]" py_n = n.get_int_value() if py_n < 1: - evaluation.message("RankedMin", "intpm", Expression("RankedMin", l, n)) - elif py_n > len(l.leaves): - evaluation.message("RankedMin", "rank", py_n, len(l.leaves)) + evaluation.message("RankedMin", "intpm", Expression("RankedMin", leaf, n)) + elif py_n > len(leaf.leaves): + evaluation.message("RankedMin", "rank", py_n, len(leaf.leaves)) else: - return introselect(l.get_mutable_leaves(), py_n - 1) + return introselect(leaf.get_mutable_leaves(), py_n - 1) class RankedMax(Builtin): @@ -1992,15 +1406,15 @@ class RankedMax(Builtin): "rank": "The specified rank `1` is not between 1 and `2`.", } - def apply(self, l, n, evaluation): - "RankedMax[l_List, n_Integer]" + def apply(self, leaf, n, evaluation): + "RankedMax[leaf_List, n_Integer]" py_n = n.get_int_value() if py_n < 1: - evaluation.message("RankedMax", "intpm", Expression("RankedMax", l, n)) - elif py_n > len(l.leaves): - evaluation.message("RankedMax", "rank", py_n, len(l.leaves)) + evaluation.message("RankedMax", "intpm", Expression("RankedMax", leaf, n)) + elif py_n > len(leaf.leaves): + evaluation.message("RankedMax", "rank", py_n, len(leaf.leaves)) else: - return introselect(l.get_mutable_leaves(), len(l.leaves) - py_n) + return introselect(leaf.get_mutable_leaves(), len(leaf.leaves) - py_n) class Quartiles(Builtin): @@ -2029,7 +1443,7 @@ class _RankedTake(Builtin): "ExcludedForms": "Automatic", } - def _compute(self, l, n, evaluation, options, f=None): + def _compute(self, t, n, evaluation, options, f=None): try: limit = CountableInteger.from_expression(n) except MessageException as e: @@ -2037,9 +1451,9 @@ def _compute(self, l, n, evaluation, options, f=None): return except NegativeIntegerException: if f: - args = (3, Expression(self.get_name(), l, f, n)) + args = (3, Expression(self.get_name(), t, f, n)) else: - args = (2, Expression(self.get_name(), l, n)) + args = (2, Expression(self.get_name(), t, n)) evaluation.message(self.get_name(), "intpm", *args) return @@ -2078,9 +1492,9 @@ def exclude(item): .is_true() ) - filtered = [leaf for leaf in l.leaves if not exclude(leaf)] + filtered = [leaf for leaf in t.leaves if not exclude(leaf)] else: - filtered = l.leaves + filtered = t.leaves if limit > len(filtered): if not limit.is_upper_limit(): @@ -2111,7 +1525,7 @@ def exclude(item): else: result = self._get_n(py_n, heap) - return l.restructure("List", [x[leaf_pos] for x in result], evaluation) + return t.restructure("List", [x[leaf_pos] for x in result], evaluation) class _RankedTakeSmallest(_RankedTake): @@ -2150,9 +1564,9 @@ class TakeLargest(_RankedTakeLargest): = {Missing[abc], 150} """ - def apply(self, l, n, evaluation, options): - "TakeLargest[l_List, n_, OptionsPattern[TakeLargest]]" - return self._compute(l, n, evaluation, options) + def apply(self, leaf, n, evaluation, options): + "TakeLargest[leaf_List, n_, OptionsPattern[TakeLargest]]" + return self._compute(leaf, n, evaluation, options) class TakeLargestBy(_RankedTakeLargest): @@ -2172,9 +1586,9 @@ class TakeLargestBy(_RankedTakeLargest): = {abc} """ - def apply(self, l, f, n, evaluation, options): - "TakeLargestBy[l_List, f_, n_, OptionsPattern[TakeLargestBy]]" - return self._compute(l, n, evaluation, options, f=f) + def apply(self, leaf, f, n, evaluation, options): + "TakeLargestBy[leaf_List, f_, n_, OptionsPattern[TakeLargestBy]]" + return self._compute(leaf, n, evaluation, options, f=f) class TakeSmallest(_RankedTakeSmallest): @@ -2190,9 +1604,9 @@ class TakeSmallest(_RankedTakeSmallest): = {-1, 10} """ - def apply(self, l, n, evaluation, options): - "TakeSmallest[l_List, n_, OptionsPattern[TakeSmallest]]" - return self._compute(l, n, evaluation, options) + def apply(self, leaf, n, evaluation, options): + "TakeSmallest[leaf_List, n_, OptionsPattern[TakeSmallest]]" + return self._compute(leaf, n, evaluation, options) class TakeSmallestBy(_RankedTakeSmallest): @@ -2212,9 +1626,9 @@ class TakeSmallestBy(_RankedTakeSmallest): = {x} """ - def apply(self, l, f, n, evaluation, options): - "TakeSmallestBy[l_List, f_, n_, OptionsPattern[TakeSmallestBy]]" - return self._compute(l, n, evaluation, options, f=f) + def apply(self, leaf, f, n, evaluation, options): + "TakeSmallestBy[leaf_List, f_, n_, OptionsPattern[TakeSmallestBy]]" + return self._compute(leaf, n, evaluation, options, f=f) class _IllegalPaddingDepth(Exception): @@ -2253,10 +1667,10 @@ def calc(expr, dims, level): return dims @staticmethod - def _build(l, n, x, m, level, mode): # mode < 0 for left pad, > 0 for right pad + def _build(leaf, n, x, m, level, mode): # mode < 0 for left pad, > 0 for right pad if not n: - return l - if not isinstance(l, Expression): + return leaf + if not isinstance(leaf, Expression): raise _IllegalPaddingDepth(level) if isinstance(m, (list, tuple)): @@ -2285,7 +1699,7 @@ def padding(amount, sign): else: return clip(x * (1 + amount // len(x)), amount, sign) - leaves = l.leaves + leaves = leaf.leaves d = n[0] - len(leaves) if d < 0: new_leaves = clip(leaves, d, mode) @@ -2319,7 +1733,7 @@ def padding(amount, sign): else: parts = (padding_margin, new_leaves, padding_main) - return Expression(l.get_head(), *list(chain(*parts))) + return Expression(leaf.get_head(), *list(chain(*parts))) def _pad(self, in_l, in_n, in_x, in_m, evaluation, expr): if not isinstance(in_l, Expression): @@ -2330,8 +1744,8 @@ def _pad(self, in_l, in_n, in_x, in_m, evaluation, expr): if isinstance(in_n, Symbol) and in_n.get_name() == "System`Automatic": py_n = _Pad._find_dims(in_l) elif in_n.get_head_name() == "System`List": - if all(isinstance(leaf, Integer) for leaf in in_n.leaves): - py_n = [leaf.get_int_value() for leaf in in_n.leaves] + if all(isinstance(sleaf, Integer) for sleaf in in_n.leaves): + py_n = [sleaf.get_int_value() for sleaf in in_n.leaves] elif isinstance(in_n, Integer): py_n = [in_n.get_int_value()] @@ -2372,32 +1786,37 @@ def levels(k): ) return None - def apply_zero(self, l, n, evaluation): - "%(name)s[l_, n_]" + def apply_zero(self, leaf, n, evaluation): + "%(name)s[leaf_, n_]" return self._pad( - l, + leaf, n, Integer0, Integer0, evaluation, - lambda: Expression(self.get_name(), l, n), + lambda: Expression(self.get_name(), leaf, n), ) - def apply(self, l, n, x, evaluation): - "%(name)s[l_, n_, x_]" + def apply(self, leaf, n, x, evaluation): + "%(name)s[leaf_, n_, x_]" return self._pad( - l, + leaf, n, x, Integer0, evaluation, - lambda: Expression(self.get_name(), l, n, x), + lambda: Expression(self.get_name(), leaf, n, x), ) - def apply_margin(self, l, n, x, m, evaluation): - "%(name)s[l_, n_, x_, m_]" + def apply_margin(self, leaf, n, x, m, evaluation): + "%(name)s[leaf_, n_, x_, m_]" return self._pad( - l, n, x, m, evaluation, lambda: Expression(self.get_name(), l, n, x, m) + leaf, + n, + x, + m, + evaluation, + lambda: Expression(self.get_name(), leaf, n, x, m), ) @@ -3066,13 +2485,13 @@ def delete_one(expr, pos): leaves = expr.leaves if pos == 0: return Expression(Symbol("System`Sequence"), *leaves) - l = len(leaves) + s = len(leaves) truepos = pos if truepos < 0: - truepos = l + truepos + truepos = s + truepos else: truepos = truepos - 1 - if truepos < 0 or truepos >= l: + if truepos < 0 or truepos >= s: raise PartRangeError leaves = leaves[:truepos] + (Expression("System`Sequence"),) + leaves[truepos + 1 :] return Expression(expr.get_head(), *leaves) @@ -3085,15 +2504,15 @@ def delete_rec(expr, pos): if truepos == 0 or expr.is_atom(): raise PartDepthError(pos[0]) leaves = expr.leaves - l = len(leaves) + s = len(leaves) if truepos < 0: - truepos = truepos + l + truepos = truepos + s if truepos < 0: raise PartRangeError newleaf = delete_rec(leaves[truepos], pos[1:]) leaves = leaves[:truepos] + (newleaf,) + leaves[truepos + 1 :] else: - if truepos > l: + if truepos > s: raise PartRangeError newleaf = delete_rec(leaves[truepos - 1], pos[1:]) leaves = leaves[: truepos - 1] + (newleaf,) + leaves[truepos:] diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index 202301448..606ab27f0 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -1710,7 +1710,7 @@ class CoefficientArrays(_CoefficientHandler): def apply_list(self, polys, varlist, evaluation, options): "%(name)s[polys_, varlist_, OptionsPattern[]]" - from mathics.builtin.lists import walk_parts + from mathics.algorithm.parts import walk_parts if polys.has_form("List", None): list_polys = polys.leaves diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 657bb1e1c..5ca11cead 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -40,7 +40,8 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin, BinaryOperator, PostfixOperator, AtomBuiltin from mathics.builtin.base import PatternObject, PatternError -from mathics.builtin.lists import python_levelspec, InvalidLevelspecError +from mathics.algorithm.parts import python_levelspec +from mathics.builtin.lists import InvalidLevelspecError from mathics.builtin.numeric import apply_N from mathics.core.symbols import ( diff --git a/mathics/builtin/sparse.py b/mathics/builtin/sparse.py index 92d6bf27d..c3101c0e2 100644 --- a/mathics/builtin/sparse.py +++ b/mathics/builtin/sparse.py @@ -6,7 +6,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.lists import walk_parts +from mathics.algorithm.parts import walk_parts from mathics.builtin.base import Builtin diff --git a/mathics/builtin/string/operations.py b/mathics/builtin/string/operations.py index d4867e977..8115d6e49 100644 --- a/mathics/builtin/string/operations.py +++ b/mathics/builtin/string/operations.py @@ -25,7 +25,7 @@ String, from_python, ) -from mathics.builtin.lists import python_seq, convert_seq +from mathics.algorithm.parts import python_seq, convert_seq from mathics.builtin.strings import ( _StringFind, _evaluate_match, diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index c3c2f58fe..79d1e3c9c 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -25,7 +25,7 @@ ) from mathics.core.rules import Pattern -from mathics.builtin.lists import get_part +from mathics.algorithm.parts import get_part def get_default_distance(p): diff --git a/setup.py b/setup.py index bc4ce71c1..67181780e 100644 --- a/setup.py +++ b/setup.py @@ -81,6 +81,7 @@ def read(*rnames): EXTENSIONS_DICT = { "core": ("expression", "numbers", "rules", "pattern"), "builtin": ["arithmetic", "numeric", "patterns", "graphics"], + "algorithm": ["parts"], } EXTENSIONS = [ Extension( From 086c37fe38a56bdcd785be2e800a2b25c540d6ed Mon Sep 17 00:00:00 2001 From: mmatera Date: Sun, 3 Oct 2021 11:22:51 -0300 Subject: [PATCH 157/193] removing SymbolN, SymbolNull from systemsymbols --- mathics/builtin/assignment.py | 3 +-- mathics/core/systemsymbols.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/assignment.py b/mathics/builtin/assignment.py index aa32810df..2a91c88ea 100644 --- a/mathics/builtin/assignment.py +++ b/mathics/builtin/assignment.py @@ -13,6 +13,7 @@ from mathics.core.symbols import ( Symbol, SymbolNull, + SymbolN, valid_context_name, system_symbols, ) @@ -20,8 +21,6 @@ from mathics.core.systemsymbols import ( SymbolFailed, SymbolMachinePrecision, - SymbolN, - SymbolNull, ) from mathics.core.atoms import String diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 20d12d827..19410b22c 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from mathics.core.symbols import Symbol, SymbolN, SymbolNull +from mathics.core.symbols import Symbol # Some other common Symbols. This list is sorted in alphabetic order. SymbolAborted = Symbol("$Aborted") From d2e7eaed7fa6e6d01b5340f11446da2419dbdc83 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 3 Oct 2021 11:33:14 -0400 Subject: [PATCH 158/193] Add a traced version of rules.py ... which tracks Builtins called and elapsed time in those routines. --- mathics/core/rules.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/mathics/core/rules.py b/mathics/core/rules.py index beea8cfd9..bbdd246de 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -2,6 +2,7 @@ # cython: language_level=3 # -*- coding: utf-8 -*- +import time from mathics.core.expression import Expression from mathics.core.symbols import strip_context, KeyComparable @@ -10,6 +11,8 @@ from itertools import chain +from collections import defaultdict +function_stats = defaultdict(lambda: {"count": 0, "elapsed_microseconds": 0.0}) class StopGenerator_BaseRule(StopGenerator): pass @@ -126,10 +129,19 @@ def do_replace(self, expression, vars, options, evaluation): vars_noctx = dict(((strip_context(s), vars[s]) for s in vars)) if self.pass_expression: vars_noctx["expression"] = expression + builtin_name = self.function.__qualname__.split(".")[0] + stat = function_stats[builtin_name] + ts = time.time() + + stat["count"] += 1 if options: - return self.function(evaluation=evaluation, options=options, **vars_noctx) + result = self.function(evaluation=evaluation, options=options, **vars_noctx) else: - return self.function(evaluation=evaluation, **vars_noctx) + result = self.function(evaluation=evaluation, **vars_noctx) + te = time.time() + elapsed = int((te - ts) * 1000) + stat["elapsed_microseconds"] += elapsed + return result def __repr__(self) -> str: return " %s>" % (self.pattern, self.function) @@ -147,3 +159,13 @@ def __setstate__(self, dict): cls, name = dict["function_"] self.function = getattr(builtins[cls], name) + +def dump_tracing_stats(): + for key_field in ("count", "elapsed_microseconds"): + print("count msecs Builtin Name") + for name, statistic in sorted(function_stats.items(), key=lambda tup: tup[1][key_field], reverse=True): + print("%5d %6g %s" % (statistic["count"], statistic["elapsed_microseconds"], name)) + print("") + +import atexit +atexit.register(dump_tracing_stats) From 722672015dff4662e921ea7415d98c562726b139 Mon Sep 17 00:00:00 2001 From: autoblack Date: Sun, 3 Oct 2021 15:38:53 +0000 Subject: [PATCH 159/193] fixup: Format Python code with Black --- mathics/core/rules.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mathics/core/rules.py b/mathics/core/rules.py index bbdd246de..749a5a8e3 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -12,8 +12,10 @@ from itertools import chain from collections import defaultdict + function_stats = defaultdict(lambda: {"count": 0, "elapsed_microseconds": 0.0}) + class StopGenerator_BaseRule(StopGenerator): pass @@ -160,12 +162,20 @@ def __setstate__(self, dict): self.function = getattr(builtins[cls], name) + def dump_tracing_stats(): for key_field in ("count", "elapsed_microseconds"): print("count msecs Builtin Name") - for name, statistic in sorted(function_stats.items(), key=lambda tup: tup[1][key_field], reverse=True): - print("%5d %6g %s" % (statistic["count"], statistic["elapsed_microseconds"], name)) + for name, statistic in sorted( + function_stats.items(), key=lambda tup: tup[1][key_field], reverse=True + ): + print( + "%5d %6g %s" + % (statistic["count"], statistic["elapsed_microseconds"], name) + ) print("") + import atexit + atexit.register(dump_tracing_stats) From 7554be3bb6f2d2df9a19f039443dada4ffb97091 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sun, 3 Oct 2021 13:14:44 -0300 Subject: [PATCH 160/193] Update setup.py --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 67181780e..bc4ce71c1 100644 --- a/setup.py +++ b/setup.py @@ -81,7 +81,6 @@ def read(*rnames): EXTENSIONS_DICT = { "core": ("expression", "numbers", "rules", "pattern"), "builtin": ["arithmetic", "numeric", "patterns", "graphics"], - "algorithm": ["parts"], } EXTENSIONS = [ Extension( From 2f2adf257c3e06970b4529d5f3c1382b5d247b05 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 3 Oct 2021 19:10:42 -0400 Subject: [PATCH 161/193] Allow a developer to overide to running Cython Set environment variable NO_CYTHON before running setup.py to avoid running Cython even if it is installed. --- setup.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/setup.py b/setup.py index 0c0552951..4c6fefd07 100644 --- a/setup.py +++ b/setup.py @@ -22,10 +22,12 @@ """ -import re -import sys +import os import os.path as osp import platform +import re +import sys + from setuptools import setup, Extension # Ensure user has the correct Python version @@ -70,25 +72,30 @@ def read(*rnames): # "http://github.com/Mathics3/mathics-scanner/tarball/master#egg=Mathics_Scanner-1.0.0.dev" # ] +# What should be run through Cython? +EXTENSIONS = [] +CMDCLASS = {} + try: if is_PyPy: raise ImportError from Cython.Distutils import build_ext except ImportError: - EXTENSIONS = [] - CMDCLASS = {} + pass else: - EXTENSIONS_DICT = { - "core": ("expression", "number", "rules", "pattern"), - "builtin": ["arithmetic", "numeric", "patterns", "graphics"], - } - EXTENSIONS = [ - Extension( - "mathics.%s.%s" % (parent, module), ["mathics/%s/%s.py" % (parent, module)] - ) - for parent, modules in EXTENSIONS_DICT.items() - for module in modules - ] + if not os.environ.get("NO_CYTHON", False): + print("Running Cython over code base") + EXTENSIONS_DICT = { + "core": ("expression", "number", "rules", "pattern"), + "builtin": ["arithmetic", "numeric", "patterns", "graphics"], + } + EXTENSIONS = [ + Extension( + "mathics.%s.%s" % (parent, module), ["mathics/%s/%s.py" % (parent, module)] + ) + for parent, modules in EXTENSIONS_DICT.items() + for module in modules + ] # EXTENSIONS_SUBDIR_DICT = { # "builtin": [("numbers", "arithmetic"), ("numbers", "numeric"), ("drawing", "graphics")], # } From 5113a881f8416ff8b772a443405219f745b8a3c1 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 3 Oct 2021 19:41:02 -0400 Subject: [PATCH 162/193] Go over CHANGE.rst (note NO_CYTHON) Blacken setup.py --- CHANGES.rst | 10 +++++----- setup.py | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d8e306c53..d08b1ffcd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,11 +4,11 @@ CHANGES Internals ========= -* now `Expression.is_numeric()` accepts an `Evaluation` object as a parameter, - to use the definitions. -* To numerify expressions, the function `apply_N` was introduced in the module `mathics.builtin.numeric` to avoid the idiom - `Expression("N", expr, prec).evaluate(evaluation)`. The idea is to avoid when it is possible to call the Pattern matching routines to obtain the numeric value of an expression. -* A bug comming from a failure in the order in which `mathics.core.definitions` stores the rules was fixed. +* To speed up development, you can set ``NO_CYTHON`` to skip Cythonizing Python modules +* ``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`` was used to speed up critically used built-in function ``N``. Its use reduces the use of + ``Expression("N", expr, prec).evaluate(evaluation)`` which is slower. +* A bug was fixed relating to the order in which ``mathics.core.definitions`` stores the rules 4.0.1 diff --git a/setup.py b/setup.py index 4c6fefd07..359253e35 100644 --- a/setup.py +++ b/setup.py @@ -91,7 +91,8 @@ def read(*rnames): } EXTENSIONS = [ Extension( - "mathics.%s.%s" % (parent, module), ["mathics/%s/%s.py" % (parent, module)] + "mathics.%s.%s" % (parent, module), + ["mathics/%s/%s.py" % (parent, module)], ) for parent, modules in EXTENSIONS_DICT.items() for module in modules From 9c15fba2f3c4fcb25577941b116ff4158e371bc0 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 3 Oct 2021 19:46:36 -0400 Subject: [PATCH 163/193] Remove Cython except once on Ubuntu 3.9 --- .github/workflows/osx.yml | 2 ++ .github/workflows/ubuntu-cython.yml | 31 +++++++++++++++++++++++++++++ .github/workflows/ubuntu.yml | 4 +++- .github/workflows/windows.yml | 2 ++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ubuntu-cython.yml diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index 2be1735a8..dc01c45e8 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -8,6 +8,8 @@ on: jobs: build: + env: + NO_CYTHON: 1 runs-on: macos-latest strategy: matrix: diff --git a/.github/workflows/ubuntu-cython.yml b/.github/workflows/ubuntu-cython.yml new file mode 100644 index 000000000..80226fc07 --- /dev/null +++ b/.github/workflows/ubuntu-cython.yml @@ -0,0 +1,31 @@ +name: Mathics (ubuntu full with Cython) + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: [3.9] + steps: + - uses: actions/checkout@v2 + - 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 + - name: Install Mathics with full dependencies + run: | + make develop-full + - name: Test Mathics + run: | + pip install -r requirements-dev.txt + make -j3 check diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index db89856e7..b5c9f70d6 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -8,6 +8,8 @@ on: jobs: build: + env: + NO_CYTHON: 1 runs-on: ubuntu-20.04 strategy: matrix: @@ -24,7 +26,7 @@ jobs: python -m pip install --upgrade pip - name: Install Mathics with full dependencies run: | - make develop-full + make develop - name: Test Mathics run: | pip install -r requirements-dev.txt diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 10baa3afe..521579886 100755 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -8,6 +8,8 @@ on: jobs: build: + env: + NO_CYTHON: 1 runs-on: windows-latest strategy: matrix: From df84df5f9feff0b144fc4a2fde2becfd00ab2679 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 3 Oct 2021 20:01:50 -0400 Subject: [PATCH 164/193] Go over workflows, and extensions Makefile: - when running develop include development Python modules - add a develop target that includes Cython .github: - Simplify by using improved develop-full and develop-full- cython target --- .editorconfig | 16 ++++++++++++++++ .github/workflows/osx.yml | 1 - .github/workflows/ubuntu-cython.yml | 3 +-- .github/workflows/ubuntu.yml | 3 +-- .github/workflows/windows.yml | 3 +-- Makefile | 8 ++++++-- requirements-cython.txt | 2 ++ setup.py | 7 ++++++- 8 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 .editorconfig create mode 100644 requirements-cython.txt diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..1d2656bab --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = tab +indent_size = 4 + +[*.yml] +indent_style = space +indent_size = 2 + +[*.py] +indent_style = space +indent_size = 4 diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index dc01c45e8..263931def 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -31,5 +31,4 @@ jobs: make develop-full - name: Test Mathics run: | - pip install -r requirements-dev.txt make -j3 check diff --git a/.github/workflows/ubuntu-cython.yml b/.github/workflows/ubuntu-cython.yml index 80226fc07..f328586dc 100644 --- a/.github/workflows/ubuntu-cython.yml +++ b/.github/workflows/ubuntu-cython.yml @@ -24,8 +24,7 @@ jobs: python -m pip install --upgrade pip - name: Install Mathics with full dependencies run: | - make develop-full + make develop-full-cython - name: Test Mathics run: | - pip install -r requirements-dev.txt make -j3 check diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index b5c9f70d6..80a1039aa 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -26,8 +26,7 @@ jobs: python -m pip install --upgrade pip - name: Install Mathics with full dependencies run: | - make develop + make develop-full - name: Test Mathics run: | - pip install -r requirements-dev.txt make -j3 check diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 521579886..ed7c58291 100755 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -33,7 +33,6 @@ jobs: python setup.py install - name: Test Mathics run: | - pip install -e .[full] - pip install -r requirements-dev.txt + pip install -e .[dev,full] set PYTEST_WORKERS="-n3" make check diff --git a/Makefile b/Makefile index fcbb5f08f..d7e7d7107 100644 --- a/Makefile +++ b/Makefile @@ -35,11 +35,15 @@ build: #: Set up to run from the source tree develop: - $(PIP) install -e . + $(PIP) install -e .[dev] #: Set up to run from the source tree with full dependencies develop-full: - $(PIP) install -e .[full] + $(PIP) install -e .[dev,full] + +#: Set up to run from the source tree with full dependencies and Cython +develop-full-cython: + $(PIP) install -e .[dev,full,cython] #: Make distirbution: wheels, eggs, tarball diff --git a/requirements-cython.txt b/requirements-cython.txt new file mode 100644 index 000000000..770255574 --- /dev/null +++ b/requirements-cython.txt @@ -0,0 +1,2 @@ +# Optional packages which add functionality or speed things up +cython # To speed up building diff --git a/setup.py b/setup.py index 359253e35..e854b8adb 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,11 @@ pip install -e . +For full installation: + + pip install -e .[full] + + This will install the library in the default location. For instructions on how to customize the install procedure read the output of: @@ -59,7 +64,7 @@ def read(*rnames): exec(compile(open("mathics/version.py").read(), "mathics/version.py", "exec")) EXTRAS_REQUIRE = {} -for kind in ("dev", "full"): +for kind in ("dev", "full", "cython"): extras_require = [] requirements_file = f"requirements-{kind}.txt" for line in open(requirements_file).read().split("\n"): From f8c51b770d569a0d61cfb535792766f8d8597aa7 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 4 Oct 2021 15:04:02 -0400 Subject: [PATCH 165/193] Only sets build_ext to Cython when NO_CYTHON isn't set --- setup.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index e854b8adb..03b20bca1 100644 --- a/setup.py +++ b/setup.py @@ -102,18 +102,18 @@ def read(*rnames): for parent, modules in EXTENSIONS_DICT.items() for module in modules ] - # EXTENSIONS_SUBDIR_DICT = { - # "builtin": [("numbers", "arithmetic"), ("numbers", "numeric"), ("drawing", "graphics")], - # } - # EXTENSIONS.append( - # Extension( - # "mathics.%s.%s.%s" % (parent, module[0], module[1]), ["mathics/%s/%s/%s.py" % (parent, module[0], module[1])] - # ) - # for parent, modules in EXTENSIONS_SUBDIR_DICT.items() - # for module in modules - # ) - CMDCLASS = {"build_ext": build_ext} - INSTALL_REQUIRES += ["cython>=0.15.1"] + # EXTENSIONS_SUBDIR_DICT = { + # "builtin": [("numbers", "arithmetic"), ("numbers", "numeric"), ("drawing", "graphics")], + # } + # EXTENSIONS.append( + # Extension( + # "mathics.%s.%s.%s" % (parent, module[0], module[1]), ["mathics/%s/%s/%s.py" % (parent, module[0], module[1])] + # ) + # for parent, modules in EXTENSIONS_SUBDIR_DICT.items() + # for module in modules + # ) + CMDCLASS = {"build_ext": build_ext} + INSTALL_REQUIRES += ["cython>=0.15.1"] # General Requirements INSTALL_REQUIRES += [ From 3568fc0179b6c6da2e47823ac44cfbe364b3f4b7 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Tue, 5 Oct 2021 17:31:36 -0400 Subject: [PATCH 166/193] Add TraceBuiltins. I moved the code @rocky made to inside the builtin code. That works creating another evaluation object with the do_replace function from BuiltinRule overrided by a custom do_replace, and after the work is done, it rolls back to what it was before overriding. --- mathics/builtin/trace.py | 93 ++++++++++++++++++++++++++++++++++++++++ mathics/core/rules.py | 36 +--------------- 2 files changed, 95 insertions(+), 34 deletions(-) create mode 100644 mathics/builtin/trace.py diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py new file mode 100644 index 000000000..b1b47fb62 --- /dev/null +++ b/mathics/builtin/trace.py @@ -0,0 +1,93 @@ +from mathics.version import __version__ # noqa used in loading to check consistency. + +from mathics.builtin.base import Builtin +from mathics.core.rules import BuiltinRule +from mathics.core.symbols import strip_context +from mathics.core.definitions import Definitions +from mathics.core.evaluation import Evaluation + +from time import time +from collections import defaultdict + + +function_stats: "defauldict" = defaultdict( + lambda: {"count": 0, "elapsed_milliseconds": 0.0} +) + + +def traced_do_replace(self, expression, vars, options, evaluation): + if options and self.check_options: + if not self.check_options(options, evaluation): + return None + vars_noctx = dict(((strip_context(s), vars[s]) for s in vars)) + if self.pass_expression: + vars_noctx["expression"] = expression + builtin_name = self.function.__qualname__.split(".")[0] + stat = function_stats[builtin_name] + ts = time() + + stat["count"] += 1 + if options: + result = self.function(evaluation=evaluation, options=options, **vars_noctx) + else: + result = self.function(evaluation=evaluation, **vars_noctx) + te = time() + elapsed = (te - ts) * 1000 + stat["elapsed_milliseconds"] += elapsed + return result + + +class TraceBuiltins(Builtin): + """ +
+
'TraceBuiltins[$expr$]' +
Print the list of the called builtin names, count and time spend in their apply method. Returns the result of $expr$. +
+ + >> TraceBuiltins[Graphics3D[Tetrahedron[]]] + : count msecs Builtin name + : ... + = -Graphics3D- + """ + + custom_evaluation: Evaluation = None + + def dump_tracing_stats(self): + print("count msecs Builtin name") + + for name, statistic in sorted( + function_stats.items(), + key=lambda tup: tup[1]["count"], + reverse=True, + ): + print( + "%5d %6g %s" + % (statistic["count"], int(statistic["elapsed_milliseconds"]), name) + ) + + def apply(self, expr, evaluation): + "TraceBuiltins[expr_]" + + # Reset function_stats + function_stats = defaultdict(lambda: {"count": 0, "elapsed_milliseconds": 0.0}) + + if TraceBuiltins.custom_evaluation is None: + do_replace_copy = BuiltinRule.do_replace + + # Replaces do_replace by the custom one + BuiltinRule.do_replace = traced_do_replace + + # Create new definitions uses the new do_replace + definitions = Definitions(add_builtin=True) + TraceBuiltins.custom_evaluation = Evaluation(definitions=definitions) + + result = expr.evaluate(TraceBuiltins.custom_evaluation) + + # Reverts do_replace to what it was + BuiltinRule.do_replace = do_replace_copy + else: + result = expr.evaluate(TraceBuiltins.custom_evaluation) + + self.dump_tracing_stats() + + return result diff --git a/mathics/core/rules.py b/mathics/core/rules.py index 749a5a8e3..beea8cfd9 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -2,7 +2,6 @@ # cython: language_level=3 # -*- coding: utf-8 -*- -import time from mathics.core.expression import Expression from mathics.core.symbols import strip_context, KeyComparable @@ -11,10 +10,6 @@ from itertools import chain -from collections import defaultdict - -function_stats = defaultdict(lambda: {"count": 0, "elapsed_microseconds": 0.0}) - class StopGenerator_BaseRule(StopGenerator): pass @@ -131,19 +126,10 @@ def do_replace(self, expression, vars, options, evaluation): vars_noctx = dict(((strip_context(s), vars[s]) for s in vars)) if self.pass_expression: vars_noctx["expression"] = expression - builtin_name = self.function.__qualname__.split(".")[0] - stat = function_stats[builtin_name] - ts = time.time() - - stat["count"] += 1 if options: - result = self.function(evaluation=evaluation, options=options, **vars_noctx) + return self.function(evaluation=evaluation, options=options, **vars_noctx) else: - result = self.function(evaluation=evaluation, **vars_noctx) - te = time.time() - elapsed = int((te - ts) * 1000) - stat["elapsed_microseconds"] += elapsed - return result + return self.function(evaluation=evaluation, **vars_noctx) def __repr__(self) -> str: return " %s>" % (self.pattern, self.function) @@ -161,21 +147,3 @@ def __setstate__(self, dict): cls, name = dict["function_"] self.function = getattr(builtins[cls], name) - - -def dump_tracing_stats(): - for key_field in ("count", "elapsed_microseconds"): - print("count msecs Builtin Name") - for name, statistic in sorted( - function_stats.items(), key=lambda tup: tup[1][key_field], reverse=True - ): - print( - "%5d %6g %s" - % (statistic["count"], statistic["elapsed_microseconds"], name) - ) - print("") - - -import atexit - -atexit.register(dump_tracing_stats) From 7ba4f895af768eb6362c3a21089cbdedf5c0acad Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Wed, 6 Oct 2021 11:46:09 -0400 Subject: [PATCH 167/193] Add the option SortBy to TracedBuiltins --- mathics/builtin/trace.py | 62 +++++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index b1b47fb62..3789ecf57 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -48,30 +48,67 @@ class TraceBuiltins(Builtin): : count msecs Builtin name : ... = -Graphics3D- + + The default is sorting the builtin names by calls count. + >> TraceBuiltins[Times[x, x], SortBy->"count"] + : count msecs Builtin name + : ... + = x^2 + + But you can also sort by name, or time. + + The default is sorting the builtin names by type. + >> TraceBuiltins[Plus @@ {1, x, x x}, SortBy->"name"] + : count msecs Builtin name + : ... + = 1 + x + x^2 """ - custom_evaluation: Evaluation = None + options = { + "SortBy": "count", + } + + messages = { + "wsort": '`1` must be one of the following: "count", "name", "time"', + } + + traced_evaluation: Evaluation = None + + def dump_tracing_stats(self, sort_by: str, evaluation): + if sort_by not in ("count", "name", "time"): + sort_by = "count" + evaluation.message("TraceBuiltins", "wsort", sort_by) + print() - def dump_tracing_stats(self): print("count msecs Builtin name") + if sort_by == "count": + inverse = True + sort_fn = lambda tup: tup[1]["count"] + elif sort_by == "time": + inverse = True + sort_fn = lambda tup: tup[1]["elapsed_milliseconds"] + else: + inverse = False + sort_fn = lambda tup: tup[0] + for name, statistic in sorted( function_stats.items(), - key=lambda tup: tup[1]["count"], - reverse=True, + key=sort_fn, + reverse=inverse, ): print( "%5d %6g %s" % (statistic["count"], int(statistic["elapsed_milliseconds"]), name) ) - def apply(self, expr, evaluation): - "TraceBuiltins[expr_]" + def apply(self, expr, evaluation, options={}): + "%(name)s[expr_, OptionsPattern[%(name)s]]" # Reset function_stats function_stats = defaultdict(lambda: {"count": 0, "elapsed_milliseconds": 0.0}) - if TraceBuiltins.custom_evaluation is None: + if TraceBuiltins.traced_evaluation is None: do_replace_copy = BuiltinRule.do_replace # Replaces do_replace by the custom one @@ -79,15 +116,18 @@ def apply(self, expr, evaluation): # Create new definitions uses the new do_replace definitions = Definitions(add_builtin=True) - TraceBuiltins.custom_evaluation = Evaluation(definitions=definitions) + TraceBuiltins.traced_evaluation = Evaluation(definitions=definitions) - result = expr.evaluate(TraceBuiltins.custom_evaluation) + result = expr.evaluate(TraceBuiltins.traced_evaluation) # Reverts do_replace to what it was BuiltinRule.do_replace = do_replace_copy else: - result = expr.evaluate(TraceBuiltins.custom_evaluation) + result = expr.evaluate(TraceBuiltins.traced_evaluation) - self.dump_tracing_stats() + self.dump_tracing_stats( + sort_by=self.get_option(options, "SortBy", evaluation).get_string_value(), + evaluation=evaluation, + ) return result From 7bb0ef8820701ff6c011ed8e33f69a1c01ef04a4 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Wed, 6 Oct 2021 21:15:58 -0400 Subject: [PATCH 168/193] Add $TraceBuiltins --- mathics/builtin/trace.py | 126 +++++++++++++++++++++++++++++---------- mathics/core/rules.py | 2 + 2 files changed, 98 insertions(+), 30 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 3789ecf57..cf75f4281 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -2,17 +2,13 @@ from mathics.builtin.base import Builtin from mathics.core.rules import BuiltinRule -from mathics.core.symbols import strip_context +from mathics.core.symbols import strip_context, SymbolTrue, SymbolFalse, SymbolNull from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation from time import time from collections import defaultdict - - -function_stats: "defauldict" = defaultdict( - lambda: {"count": 0, "elapsed_milliseconds": 0.0} -) +from typing import Callable def traced_do_replace(self, expression, vars, options, evaluation): @@ -23,7 +19,7 @@ def traced_do_replace(self, expression, vars, options, evaluation): if self.pass_expression: vars_noctx["expression"] = expression builtin_name = self.function.__qualname__.split(".")[0] - stat = function_stats[builtin_name] + stat = TraceBuiltins.function_stats[builtin_name] ts = time() stat["count"] += 1 @@ -45,13 +41,13 @@ class TraceBuiltins(Builtin):
>> TraceBuiltins[Graphics3D[Tetrahedron[]]] - : count msecs Builtin name + : count ms Builtin name : ... = -Graphics3D- The default is sorting the builtin names by calls count. >> TraceBuiltins[Times[x, x], SortBy->"count"] - : count msecs Builtin name + : count ms Builtin name : ... = x^2 @@ -59,7 +55,7 @@ class TraceBuiltins(Builtin): The default is sorting the builtin names by type. >> TraceBuiltins[Plus @@ {1, x, x x}, SortBy->"name"] - : count msecs Builtin name + : count ms Builtin name : ... = 1 + x + x^2 """ @@ -72,15 +68,22 @@ class TraceBuiltins(Builtin): "wsort": '`1` must be one of the following: "count", "name", "time"', } - traced_evaluation: Evaluation = None + traced_definitions: Evaluation = None + definitions_copy: Definitions + do_replace_copy: Callable + + function_stats: "defauldict" = defaultdict( + lambda: {"count": 0, "elapsed_milliseconds": 0.0} + ) - def dump_tracing_stats(self, sort_by: str, evaluation): + @staticmethod + def dump_tracing_stats(sort_by: str, evaluation) -> None: if sort_by not in ("count", "name", "time"): sort_by = "count" evaluation.message("TraceBuiltins", "wsort", sort_by) print() - print("count msecs Builtin name") + print("count ms Builtin name") if sort_by == "count": inverse = True @@ -93,7 +96,7 @@ def dump_tracing_stats(self, sort_by: str, evaluation): sort_fn = lambda tup: tup[0] for name, statistic in sorted( - function_stats.items(), + TraceBuiltins.function_stats.items(), key=sort_fn, reverse=inverse, ): @@ -102,28 +105,35 @@ def dump_tracing_stats(self, sort_by: str, evaluation): % (statistic["count"], int(statistic["elapsed_milliseconds"]), name) ) - def apply(self, expr, evaluation, options={}): - "%(name)s[expr_, OptionsPattern[%(name)s]]" - - # Reset function_stats - function_stats = defaultdict(lambda: {"count": 0, "elapsed_milliseconds": 0.0}) - - if TraceBuiltins.traced_evaluation is None: - do_replace_copy = BuiltinRule.do_replace + @staticmethod + def enable_trace(evaluation) -> None: + if TraceBuiltins.traced_definitions is None: + TraceBuiltins.do_replace_copy = BuiltinRule.do_replace + TraceBuiltins.definitions_copy = evaluation.definitions # Replaces do_replace by the custom one BuiltinRule.do_replace = traced_do_replace - # Create new definitions uses the new do_replace - definitions = Definitions(add_builtin=True) - TraceBuiltins.traced_evaluation = Evaluation(definitions=definitions) + evaluation.definitions = Definitions(add_builtin=True) + else: + evaluation.definitions = TraceBuiltins.definitions_copy - result = expr.evaluate(TraceBuiltins.traced_evaluation) + @staticmethod + def disable_trace(evaluation) -> None: + BuiltinRule.do_replace = TraceBuiltins.do_replace_copy + evaluation.definitions = TraceBuiltins.definitions_copy - # Reverts do_replace to what it was - BuiltinRule.do_replace = do_replace_copy - else: - result = expr.evaluate(TraceBuiltins.traced_evaluation) + def apply(self, expr, evaluation, options={}): + "%(name)s[expr_, OptionsPattern[%(name)s]]" + + # Reset function_stats + TraceBuiltins.function_stats = defaultdict( + lambda: {"count": 0, "elapsed_milliseconds": 0.0} + ) + + self.enable_trace(evaluation) + result = expr.evaluate(evaluation) + self.disable_trace(evaluation) self.dump_tracing_stats( sort_by=self.get_option(options, "SortBy", evaluation).get_string_value(), @@ -131,3 +141,59 @@ def apply(self, expr, evaluation, options={}): ) return result + + +# The convention is to use the name of the variable without the "$" as +# the class name, but it is already taken by the builtin `TraceBuiltins` +class TraceBuiltinsVariable(Builtin): + """ +
+
'$TraceBuiltins' +
Setting this enable/disable tracing. It defaults to False. +
+ + >> $TraceBuiltins = True + = True + Now tracing is enabled. + >> x + = x + You can print it with 'PrintTrace[]' and clear it with 'ClearTrace[]'. + >> PrintTrace[] + : count ms Builtin name + : ... + = Null + + >> $TraceBuiltins = False + = False + + It can't be set to a non-boolean. + >> $TraceBuiltins = x + : Set::wrsym: Symbol $TraceBuiltins is Protected. + = x + """ + + name = "$TraceBuiltins" + value = SymbolFalse + + def apply_get(self, evaluation): + "%(name)s" + + return self.value + + def apply_set_true(self, evaluation): + "%(name)s = True" + + self.value = SymbolTrue + TraceBuiltins.enable_trace(evaluation) + + return SymbolTrue + + def apply_set_false(self, evaluation): + "%(name)s = False" + + self.value = SymbolFalse + TraceBuiltins.disable_trace(evaluation) + + return SymbolFalse + + diff --git a/mathics/core/rules.py b/mathics/core/rules.py index beea8cfd9..5be5bc00c 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -116,6 +116,8 @@ def __init__(self, name, pattern, function, check_options, system=False) -> None self.check_options = check_options self.pass_expression = "expression" in function_arguments(function) + # If you update this, you must also update traced_do_replace + # (that's in the same file TraceBuiltins is) def do_replace(self, expression, vars, options, evaluation): if options and self.check_options: if not self.check_options(options, evaluation): From 2c4cd9818efa23837f0ab375b905a2d175c562f8 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Wed, 6 Oct 2021 21:17:18 -0400 Subject: [PATCH 169/193] Add PrintTrace --- mathics/builtin/trace.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index cf75f4281..9d4d75e7f 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -197,3 +197,34 @@ def apply_set_false(self, evaluation): return SymbolFalse +class PrintTrace(Builtin): + """ +
+
'PrintTrace[]' +
Print the builtin trace. +
+ + If '$TraceBuiltins' was never set to 'True', this will print an empty list. + >> PrintTrace[] + : count ms Builtin name + = Null + + >> $TraceBuiltins = True + + >> PrintTrace[] + : count ms Builtin name + : ... + = Null + + #> $TraceBuiltins = False + """ + + def apply(self, evaluation): + "%(name)s[]" + + TraceBuiltins.dump_tracing_stats( + sort_by="count", + evaluation=evaluation, + ) + + return SymbolNull From 3b1d1379e7933e78a3efd2eb18a4d23d410800a8 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Wed, 6 Oct 2021 21:17:33 -0400 Subject: [PATCH 170/193] Add ClearTrace --- mathics/builtin/trace.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 9d4d75e7f..30895727c 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -197,6 +197,42 @@ def apply_set_false(self, evaluation): return SymbolFalse +class ClearTrace(Builtin): + """ +
+
'ClearTrace[]' +
Clear the builtin trace. +
+ + >> $TraceBuiltins = True + = True + + >> PrintTrace[] + : count ms Builtin name + : ... + = Null + + >> ClearTrace[] + = Null + + >> PrintTrace[] + : count ms Builtin name + : 1 ... PrintTrace + = Null + + #> $TraceBuiltins = False + """ + + def apply(self, evaluation): + "%(name)s[]" + + TraceBuiltins.function_stats: "defauldict" = defaultdict( + lambda: {"count": 0, "elapsed_milliseconds": 0.0} + ) + + return SymbolNull + + class PrintTrace(Builtin): """
From 07c50a4c0a80c642f48edf1651ecd14016182c4c Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 9 Oct 2021 13:24:15 -0400 Subject: [PATCH 171/193] Comment out PrintTrace and ClearTrace for now... They don't work. Add -T/--trace-builtins to do tracing at CLI level For example `mathics -e 1+1 -T` is a oneshot command to get traces `mathics -T` also works. Some minor corrections and tweaks --- mathics/builtin/trace.py | 43 +++++++++++++++++++++++++--------------- mathics/main.py | 26 ++++++++++++++++++++---- test/test_cli.py | 16 +++++++++++---- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 30895727c..24fd83699 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -33,46 +33,57 @@ def traced_do_replace(self, expression, vars, options, evaluation): return result -class TraceBuiltins(Builtin): +class _TraceBase(Builtin): + options = { + "SortBy": '"count"', + } + + messages = { + "wsort": '`1` must be one of the following: "count", "name", "time"', + } + + +class TraceBuiltins(_TraceBase): """
-
'TraceBuiltins[$expr$]' -
Print the list of the called builtin names, count and time spend in their apply method. Returns the result of $expr$. +
'TraceBuiltins[$expr$]' +
Print a list of the Built-in Functions called in evaluating $expr$ along with the number of times is each called, and combined elapsed time in milliseconds spent in each.
+ Sort Options: + +
    +
  • count +
  • name +
  • time +
+ + >> TraceBuiltins[Graphics3D[Tetrahedron[]]] : count ms Builtin name : ... = -Graphics3D- - The default is sorting the builtin names by calls count. + By default, the output is sorted by the number of calls of the builtin from highest to lowest: >> TraceBuiltins[Times[x, x], SortBy->"count"] : count ms Builtin name : ... = x^2 - But you can also sort by name, or time. + You can have results ordered by name, or time. - The default is sorting the builtin names by type. - >> TraceBuiltins[Plus @@ {1, x, x x}, SortBy->"name"] + Trace an expression and list the result by time from highest to lowest. + >> TraceBuiltins[Plus @@ {1, x, x x}, SortBy->"time"] : count ms Builtin name : ... = 1 + x + x^2 """ - options = { - "SortBy": "count", - } - - messages = { - "wsort": '`1` must be one of the following: "count", "name", "time"', - } - traced_definitions: Evaluation = None definitions_copy: Definitions do_replace_copy: Callable - function_stats: "defauldict" = defaultdict( + function_stats: "defaultdict" = defaultdict( lambda: {"count": 0, "elapsed_milliseconds": 0.0} ) diff --git a/mathics/main.py b/mathics/main.py index 95937c1b7..72c1fd380 100755 --- a/mathics/main.py +++ b/mathics/main.py @@ -9,13 +9,15 @@ import sys import os.path as osp -from mathics.core.parser import MathicsFileLineFeeder, MathicsLineFeeder +from mathics import settings +from mathics import version_string, license_string, __version__ +from mathics.builtin.trace import TraceBuiltins, traced_do_replace from mathics.core.definitions import autoload_files, Definitions, Symbol -from mathics.core.expression import strip_context from mathics.core.evaluation import Evaluation, Output -from mathics import version_string, license_string, __version__ -from mathics import settings +from mathics.core.expression import strip_context +from mathics.core.parser import MathicsFileLineFeeder, MathicsLineFeeder +from mathics.core.rules import BuiltinRule def get_srcdir(): @@ -300,6 +302,13 @@ def main() -> int: action="store_true", ) + argparser.add_argument( + "--trace-builtins", + "-T", + help="Trace Built-in call counts and elapsed time", + action="store_true", + ) + args, script_args = argparser.parse_known_args() quit_command = "CTRL-BREAK" if sys.platform == "win32" else "CONTROL-D" @@ -313,6 +322,15 @@ def main() -> int: extension_modules = default_pymathics_modules + if args.trace_builtins: + BuiltinRule.do_replace = traced_do_replace + import atexit + + def dump_tracing_stats(): + TraceBuiltins.dump_tracing_stats(sort_by="count", evaluation=None) + + atexit.register(dump_tracing_stats) + definitions = Definitions(add_builtin=True, extension_modules=extension_modules) definitions.set_line_no(0) diff --git a/test/test_cli.py b/test/test_cli.py index 9254a7d0c..bdf1f30c3 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -17,13 +17,21 @@ def test_cli(): script_file = osp.join(get_testdir(), "data", "script.m") # asserts output contains 'Hello' and '2' - assert re.match( - r"Hello\s+2", - subprocess.run( + result = subprocess.run( ["mathics", "-e", "Print[1+1];", "-script", script_file], capture_output=True, - ).stdout.decode("utf-8"), + ) + + assert re.match( + r"Hello\s+2", result.stdout.decode("utf-8") ) + assert result.returncode == 0 + + result = subprocess.run( + ["mathics", "--execute", "2+3", "---trace-builtins"], + capture_output=False, + ) + assert result.returncode == 0 if __name__ == "__main__": From 5e4198999a3176b1b52b33906501b84cea217f80 Mon Sep 17 00:00:00 2001 From: autoblack Date: Sat, 9 Oct 2021 17:26:56 +0000 Subject: [PATCH 172/193] fixup: Format Python code with Black --- test/test_cli.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/test_cli.py b/test/test_cli.py index bdf1f30c3..4cfc8c6ca 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -18,19 +18,17 @@ def test_cli(): # asserts output contains 'Hello' and '2' result = subprocess.run( - ["mathics", "-e", "Print[1+1];", "-script", script_file], - capture_output=True, - ) - - assert re.match( - r"Hello\s+2", result.stdout.decode("utf-8") + ["mathics", "-e", "Print[1+1];", "-script", script_file], + capture_output=True, ) + + assert re.match(r"Hello\s+2", result.stdout.decode("utf-8")) assert result.returncode == 0 result = subprocess.run( ["mathics", "--execute", "2+3", "---trace-builtins"], capture_output=False, - ) + ) assert result.returncode == 0 From 3e6d138a2fbbb309b138cff3f7b18aba69ec2513 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 9 Oct 2021 14:50:04 -0400 Subject: [PATCH 173/193] Add to CHANGES.rst --- CHANGES.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d8e306c53..38f8f1076 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -48,6 +48,18 @@ Uniform Polyhedron * ``TetraHedron`` * ``UniformPolyedron`` +Mathics-specific + +* ``TraceBuiltin[]``, ``$TraceBuiltins``, ``ClearTrace[]``, ``PrintTrace[]`` + +These collect builtin-function call counts and elapsed time in the routines. +``TraceBuiltin[expr]`` collects information for just *expr*. Whereas +setting ``$TraceBuiltins`` to True will accumulate results of evaluations +``PrintTrace[]`` dumps the statistics and ``ClearTrace[]`` clears the statistics data. + +``mathics -T/--trace-builtin`` is about the same as setting +``$TraceBuiltins = True`` on entry and runs ``PrintTrace[]`` on exit. + Bugs ++++ From 2bbc1fddf7cd6c560447ec4dc9411ac327c9ea95 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 10 Oct 2021 11:12:52 -0400 Subject: [PATCH 174/193] Go over docs for Buitin-Function tracing... Also sort class variables and classes within the module --- mathics/builtin/trace.py | 223 +++++++++++++++++++++++---------------- 1 file changed, 132 insertions(+), 91 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 24fd83699..8b75d64fa 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -1,3 +1,13 @@ +# -*- coding: utf-8 -*- + +""" +Built-in Function Tracing + +Built-in Function Tracing provides one high-level way understand where the time is spent is evaluating expressions. + +With this it may be possible for both users and implementers to figure out how to speed up running expressions. +""" + from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import Builtin @@ -43,11 +53,97 @@ class _TraceBase(Builtin): } +class ClearTrace(Builtin): + """ +
+
'ClearTrace[]' +
Clear the statistics collected for Built-in Functions +
+ + First, set up Builtin-function tracing: + >> $TraceBuiltins = True + = True + + Dump Builtin-Function statistics gathered in running that assignment: + >> PrintTrace[] + : count ms Builtin name + : ... + = Null + + >> ClearTrace[] + = Null + + >> PrintTrace[] + : count ms Builtin name + : 1 ... PrintTrace + = Null + + #> $TraceBuiltins = False + """ + + summary_text = "Clear any statistics collected for Built-in Functions" + + def apply(self, evaluation): + "%(name)s[]" + + TraceBuiltins.function_stats: "defaultdict" = defaultdict( + lambda: {"count": 0, "elapsed_milliseconds": 0.0} + ) + + return SymbolNull + + +class PrintTrace(Builtin): + """ +
+
'PrintTrace[]' +
Print statistics collected for Built-in Functions +
+ + Sort Options: + +
    +
  • count +
  • name +
  • time +
+ + Note that in a browser the information only appears in a console. + + + If '$TraceBuiltins' was never set to 'True', this will print an empty list. + >> PrintTrace[] + : count ms Builtin name + = Null + + >> $TraceBuiltins = True + + >> PrintTrace[SortBy -> "time"] + : count ms Builtin name + : ... + = Null + + #> $TraceBuiltins = False + """ + + summary_text = "Print statistics collected for Built-in Functions" + + def apply(self, evaluation): + "%(name)s[]" + + TraceBuiltins.dump_tracing_stats( + sort_by="count", + evaluation=evaluation, + ) + + return SymbolNull + + class TraceBuiltins(_TraceBase): """
'TraceBuiltins[$expr$]' -
Print a list of the Built-in Functions called in evaluating $expr$ along with the number of times is each called, and combined elapsed time in milliseconds spent in each. +
Evaluate $expr$ and then print a list of the Built-in Functions called in evaluating $expr$ along with the number of times is each called, and combined elapsed time in milliseconds spent in each.
Sort Options: @@ -79,7 +175,6 @@ class TraceBuiltins(_TraceBase): = 1 + x + x^2 """ - traced_definitions: Evaluation = None definitions_copy: Definitions do_replace_copy: Callable @@ -87,6 +182,12 @@ class TraceBuiltins(_TraceBase): lambda: {"count": 0, "elapsed_milliseconds": 0.0} ) + summary_text = ( + "Evaluate an expression and print statistics on Built-in Functions called" + ) + + traced_definitions: Evaluation = None + @staticmethod def dump_tracing_stats(sort_by: str, evaluation) -> None: if sort_by not in ("count", "name", "time"): @@ -158,32 +259,41 @@ def apply(self, expr, evaluation, options={}): # the class name, but it is already taken by the builtin `TraceBuiltins` class TraceBuiltinsVariable(Builtin): """ -
-
'$TraceBuiltins' -
Setting this enable/disable tracing. It defaults to False. -
+
+
'$TraceBuiltins' +
Enable or disable Built-in Function evaluation statistics. +
- >> $TraceBuiltins = True - = True - Now tracing is enabled. - >> x - = x - You can print it with 'PrintTrace[]' and clear it with 'ClearTrace[]'. - >> PrintTrace[] - : count ms Builtin name - : ... - = Null + Setting this variable True will enable statistics collection for Built-in functions that are evaluated. + In contrast to 'TraceBuiltins[]' statistics are accumulated and over several inputs, and are not shown + after each input is evaluated. By default this setting is off of False. + + >> $TraceBuiltins = True + = True + + Tracing is enabled, so we expressions entered and evaluated will have statistics collected for the evaluations. + >> x + = x + + To print the statistics collected, use 'PrintTrace[]': + >> PrintTrace[] + : count ms Builtin name + : ... + = Null - >> $TraceBuiltins = False - = False + To clear statistics collected use 'ClearTrace[]': - It can't be set to a non-boolean. - >> $TraceBuiltins = x - : Set::wrsym: Symbol $TraceBuiltins is Protected. - = x + >> ClearTrace[] + = None + + '$TraceBuiltins' cannot be set to a non-boolean value. + >> $TraceBuiltins = x + : Set::wrsym: Symbol $TraceBuiltins is Protected. + = x """ name = "$TraceBuiltins" + summary_text = "Enable or disable Built-in Function evaluation statistics" value = SymbolFalse def apply_get(self, evaluation): @@ -206,72 +316,3 @@ def apply_set_false(self, evaluation): TraceBuiltins.disable_trace(evaluation) return SymbolFalse - - -class ClearTrace(Builtin): - """ -
-
'ClearTrace[]' -
Clear the builtin trace. -
- - >> $TraceBuiltins = True - = True - - >> PrintTrace[] - : count ms Builtin name - : ... - = Null - - >> ClearTrace[] - = Null - - >> PrintTrace[] - : count ms Builtin name - : 1 ... PrintTrace - = Null - - #> $TraceBuiltins = False - """ - - def apply(self, evaluation): - "%(name)s[]" - - TraceBuiltins.function_stats: "defauldict" = defaultdict( - lambda: {"count": 0, "elapsed_milliseconds": 0.0} - ) - - return SymbolNull - - -class PrintTrace(Builtin): - """ -
-
'PrintTrace[]' -
Print the builtin trace. -
- - If '$TraceBuiltins' was never set to 'True', this will print an empty list. - >> PrintTrace[] - : count ms Builtin name - = Null - - >> $TraceBuiltins = True - - >> PrintTrace[] - : count ms Builtin name - : ... - = Null - - #> $TraceBuiltins = False - """ - - def apply(self, evaluation): - "%(name)s[]" - - TraceBuiltins.dump_tracing_stats( - sort_by="count", - evaluation=evaluation, - ) - - return SymbolNull From 32997bd6bf01cddec223a255688656e4350eaebb Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 10 Oct 2021 12:06:42 -0400 Subject: [PATCH 175/193] Adjust tests so they pass Would love to ditch this testing mechanism and switch to pytest and sphinx autodoc --- mathics/builtin/trace.py | 39 +++++++++------------------------------ 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 8b75d64fa..c5b5dd8fe 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -66,19 +66,11 @@ class ClearTrace(Builtin): Dump Builtin-Function statistics gathered in running that assignment: >> PrintTrace[] - : count ms Builtin name - : ... - = Null >> ClearTrace[] - = Null - - >> PrintTrace[] - : count ms Builtin name - : 1 ... PrintTrace - = Null #> $TraceBuiltins = False + = False """ summary_text = "Clear any statistics collected for Built-in Functions" @@ -113,17 +105,15 @@ class PrintTrace(Builtin): If '$TraceBuiltins' was never set to 'True', this will print an empty list. >> PrintTrace[] - : count ms Builtin name - = Null >> $TraceBuiltins = True + = True >> PrintTrace[SortBy -> "time"] - : count ms Builtin name - : ... - = Null + = PrintTrace[SortBy -> time] #> $TraceBuiltins = False + = False """ summary_text = "Print statistics collected for Built-in Functions" @@ -156,23 +146,17 @@ class TraceBuiltins(_TraceBase): >> TraceBuiltins[Graphics3D[Tetrahedron[]]] - : count ms Builtin name - : ... = -Graphics3D- By default, the output is sorted by the number of calls of the builtin from highest to lowest: >> TraceBuiltins[Times[x, x], SortBy->"count"] - : count ms Builtin name - : ... - = x^2 + = x ^ 2 You can have results ordered by name, or time. Trace an expression and list the result by time from highest to lowest. >> TraceBuiltins[Plus @@ {1, x, x x}, SortBy->"time"] - : count ms Builtin name - : ... - = 1 + x + x^2 + = 1 + x + x ^ 2 """ definitions_copy: Definitions @@ -277,19 +261,14 @@ class TraceBuiltinsVariable(Builtin): To print the statistics collected, use 'PrintTrace[]': >> PrintTrace[] - : count ms Builtin name - : ... - = Null To clear statistics collected use 'ClearTrace[]': >> ClearTrace[] - = None - '$TraceBuiltins' cannot be set to a non-boolean value. - >> $TraceBuiltins = x - : Set::wrsym: Symbol $TraceBuiltins is Protected. - = x + # '$TraceBuiltins' cannot be set to a non-boolean value. + # >> $TraceBuiltins = x + # = x """ name = "$TraceBuiltins" From 176361ab35173846370a82e5c6c2848812d3ef2b Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sun, 10 Oct 2021 18:51:54 -0400 Subject: [PATCH 176/193] Improve the documentaion of $TraceBuiltins --- mathics/builtin/trace.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index c5b5dd8fe..86ed5ea8c 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -243,28 +243,32 @@ def apply(self, expr, evaluation, options={}): # the class name, but it is already taken by the builtin `TraceBuiltins` class TraceBuiltinsVariable(Builtin): """ -
-
'$TraceBuiltins' -
Enable or disable Built-in Function evaluation statistics. -
+
+
'$TraceBuiltins' +
Enable or disable Built-in Function evaluation statistics. +
- Setting this variable True will enable statistics collection for Built-in functions that are evaluated. - In contrast to 'TraceBuiltins[]' statistics are accumulated and over several inputs, and are not shown - after each input is evaluated. By default this setting is off of False. + Setting this variable True will enable statistics collection for Built-in functions that are evaluated. + In contrast to 'TraceBuiltins[]' statistics are accumulated and over several inputs, and are not shown + after each input is evaluated. + By default this setting is False. + + >> $TraceBuiltins = True + = True - >> $TraceBuiltins = True - = True + ## We shouldn't let this enabled. + #> $TraceBuiltins = False + = False - Tracing is enabled, so we expressions entered and evaluated will have statistics collected for the evaluations. - >> x - = x + Tracing is enabled, so the expressions entered and evaluated will have statistics collected for the evaluations. + >> x + = x To print the statistics collected, use 'PrintTrace[]': - >> PrintTrace[] - - To clear statistics collected use 'ClearTrace[]': + X> PrintTrace[] - >> ClearTrace[] + To clear statistics collected use 'ClearTrace[]': + X> ClearTrace[] # '$TraceBuiltins' cannot be set to a non-boolean value. # >> $TraceBuiltins = x From 7f372bafc21fcbca15ea77b2ac6d737d0c01b3cb Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sun, 10 Oct 2021 18:52:29 -0400 Subject: [PATCH 177/193] Change the tests of PrintTrace so them check for the message --- mathics/builtin/trace.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 86ed5ea8c..9b8e6210f 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -105,6 +105,9 @@ class PrintTrace(Builtin): If '$TraceBuiltins' was never set to 'True', this will print an empty list. >> PrintTrace[] + : count ms Builtin name + : ... + = Null >> $TraceBuiltins = True = True @@ -146,16 +149,22 @@ class TraceBuiltins(_TraceBase): >> TraceBuiltins[Graphics3D[Tetrahedron[]]] + : count ms Builtin name + : ... = -Graphics3D- By default, the output is sorted by the number of calls of the builtin from highest to lowest: >> TraceBuiltins[Times[x, x], SortBy->"count"] + : count ms Builtin name + : ... = x ^ 2 You can have results ordered by name, or time. Trace an expression and list the result by time from highest to lowest. >> TraceBuiltins[Plus @@ {1, x, x x}, SortBy->"time"] + : count ms Builtin name + : ... = 1 + x + x ^ 2 """ From 88b0c5647fc995a5e33678fc0f1580ef9af70b34 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sun, 10 Oct 2021 18:52:58 -0400 Subject: [PATCH 178/193] Improve the warning when setting $TraceBuiltins to a non-bool value --- mathics/builtin/trace.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 9b8e6210f..304c51f78 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -279,32 +279,35 @@ class TraceBuiltinsVariable(Builtin): To clear statistics collected use 'ClearTrace[]': X> ClearTrace[] - # '$TraceBuiltins' cannot be set to a non-boolean value. - # >> $TraceBuiltins = x - # = x + '$TraceBuiltins' cannot be set to a non-boolean value. + >> $TraceBuiltins = x + : $TraceBuiltins::bool: x should be True or False. + = x """ name = "$TraceBuiltins" - summary_text = "Enable or disable Built-in Function evaluation statistics" + + messages = {"bool": "`1` should be True or False."} + value = SymbolFalse + summary_text = "enable or disable built-in function evaluation statistics" + def apply_get(self, evaluation): "%(name)s" return self.value - def apply_set_true(self, evaluation): - "%(name)s = True" + def apply_set(self, value, evaluation): + "%(name)s = value_" - self.value = SymbolTrue - TraceBuiltins.enable_trace(evaluation) - - return SymbolTrue - - def apply_set_false(self, evaluation): - "%(name)s = False" - - self.value = SymbolFalse - TraceBuiltins.disable_trace(evaluation) + if value == SymbolTrue: + self.value = SymbolTrue + TraceBuiltins.enable_trace(evaluation) + elif value == SymbolFalse: + self.value = SymbolFalse + TraceBuiltins.disable_trace(evaluation) + else: + evaluation.message("$TraceBuiltins", "bool", value) - return SymbolFalse + return value From 770ed758a15da7e01321da0bedfa70f5bfe92bb4 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Sun, 10 Oct 2021 19:03:31 -0400 Subject: [PATCH 179/193] Fix warning when the sort option doesn't exist in PrintTrace --- mathics/builtin/trace.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 304c51f78..264f3aaaf 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -73,7 +73,7 @@ class ClearTrace(Builtin): = False """ - summary_text = "Clear any statistics collected for Built-in Functions" + summary_text = "clear any statistics collected for Built-in functions" def apply(self, evaluation): "%(name)s[]" @@ -119,7 +119,7 @@ class PrintTrace(Builtin): = False """ - summary_text = "Print statistics collected for Built-in Functions" + summary_text = "print statistics collected for Built-in functions" def apply(self, evaluation): "%(name)s[]" @@ -176,7 +176,7 @@ class TraceBuiltins(_TraceBase): ) summary_text = ( - "Evaluate an expression and print statistics on Built-in Functions called" + "evaluate an expression and print statistics on Built-in functions called" ) traced_definitions: Evaluation = None @@ -184,8 +184,8 @@ class TraceBuiltins(_TraceBase): @staticmethod def dump_tracing_stats(sort_by: str, evaluation) -> None: if sort_by not in ("count", "name", "time"): - sort_by = "count" evaluation.message("TraceBuiltins", "wsort", sort_by) + sort_by = "count" print() print("count ms Builtin name") @@ -291,7 +291,7 @@ class TraceBuiltinsVariable(Builtin): value = SymbolFalse - summary_text = "enable or disable built-in function evaluation statistics" + summary_text = "enable or disable Built-in function evaluation statistics" def apply_get(self, evaluation): "%(name)s" From 46975183cfce9cfc70b7182f8ae2537c21016dba Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 11 Oct 2021 07:48:10 -0400 Subject: [PATCH 180/193] Fix tests --- mathics/builtin/trace.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 264f3aaaf..b66e45b69 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -105,15 +105,15 @@ class PrintTrace(Builtin): If '$TraceBuiltins' was never set to 'True', this will print an empty list. >> PrintTrace[] - : count ms Builtin name - : ... - = Null + \| count ms Builtin name + = None >> $TraceBuiltins = True = True >> PrintTrace[SortBy -> "time"] - = PrintTrace[SortBy -> time] + \| count ms Builtin name + = None #> $TraceBuiltins = False = False @@ -149,22 +149,16 @@ class TraceBuiltins(_TraceBase): >> TraceBuiltins[Graphics3D[Tetrahedron[]]] - : count ms Builtin name - : ... = -Graphics3D- By default, the output is sorted by the number of calls of the builtin from highest to lowest: >> TraceBuiltins[Times[x, x], SortBy->"count"] - : count ms Builtin name - : ... = x ^ 2 You can have results ordered by name, or time. Trace an expression and list the result by time from highest to lowest. >> TraceBuiltins[Plus @@ {1, x, x x}, SortBy->"time"] - : count ms Builtin name - : ... = 1 + x + x ^ 2 """ @@ -281,7 +275,7 @@ class TraceBuiltinsVariable(Builtin): '$TraceBuiltins' cannot be set to a non-boolean value. >> $TraceBuiltins = x - : $TraceBuiltins::bool: x should be True or False. + : x should be True or False. = x """ From a5b6416d427df2ab8dce5533f72d9f4e2c0cb740 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 11 Oct 2021 07:48:30 -0400 Subject: [PATCH 181/193] Allow SortBy in PrintTrace --- mathics/builtin/trace.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index b66e45b69..f08dd4efe 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -85,7 +85,7 @@ def apply(self, evaluation): return SymbolNull -class PrintTrace(Builtin): +class PrintTrace(_TraceBase): """
'PrintTrace[]' @@ -121,11 +121,11 @@ class PrintTrace(Builtin): summary_text = "print statistics collected for Built-in functions" - def apply(self, evaluation): - "%(name)s[]" + def apply(self, evaluation, options={}): + "%(name)s[OptionsPattern[%(name)s]]" TraceBuiltins.dump_tracing_stats( - sort_by="count", + sort_by=self.get_option(options, "SortBy", evaluation).get_string_value(), evaluation=evaluation, ) From cc93e4a59258e50a62dd61b026fdb1aef83dadea Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 11 Oct 2021 11:23:10 -0400 Subject: [PATCH 182/193] Improve the looking of PrintTrace's docs The "\|" was shown in the Django documentation, so these tests were removed. --- mathics/builtin/trace.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index f08dd4efe..a09f84ef8 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -252,8 +252,7 @@ class TraceBuiltinsVariable(Builtin):
Setting this variable True will enable statistics collection for Built-in functions that are evaluated. - In contrast to 'TraceBuiltins[]' statistics are accumulated and over several inputs, and are not shown - after each input is evaluated. + In contrast to 'TraceBuiltins[]' statistics are accumulated and over several inputs, and are not shown after each input is evaluated. By default this setting is False. >> $TraceBuiltins = True From ac085e681bf957359b83abdc076bd33dcc275222 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 11 Oct 2021 11:23:10 -0400 Subject: [PATCH 183/193] Improve the looking of PrintTrace's docs The "\|" was shown in the Django documentation, so these tests were removed. --- mathics/builtin/trace.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index f08dd4efe..5ac06c1f4 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -105,15 +105,11 @@ class PrintTrace(_TraceBase): If '$TraceBuiltins' was never set to 'True', this will print an empty list. >> PrintTrace[] - \| count ms Builtin name - = None >> $TraceBuiltins = True = True >> PrintTrace[SortBy -> "time"] - \| count ms Builtin name - = None #> $TraceBuiltins = False = False @@ -252,8 +248,7 @@ class TraceBuiltinsVariable(Builtin):
Setting this variable True will enable statistics collection for Built-in functions that are evaluated. - In contrast to 'TraceBuiltins[]' statistics are accumulated and over several inputs, and are not shown - after each input is evaluated. + In contrast to 'TraceBuiltins[]' statistics are accumulated and over several inputs, and are not shown after each input is evaluated. By default this setting is False. >> $TraceBuiltins = True From 251526ffc18ce0e49c509da92c8c4cd5d4b54849 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 16 Oct 2021 20:10:49 -0400 Subject: [PATCH 184/193] Start to split out assignments into its own module --- mathics/builtin/__init__.py | 1 + mathics/builtin/assignments/__init__.py | 7 + .../builtin/{ => assignments}/assignment.py | 616 +----------------- mathics/builtin/assignments/internals.py | 605 +++++++++++++++++ mathics/builtin/attributes.py | 2 +- mathics/builtin/base.py | 7 + mathics/builtin/scoping.py | 3 +- setup.py | 2 + 8 files changed, 640 insertions(+), 603 deletions(-) create mode 100644 mathics/builtin/assignments/__init__.py rename mathics/builtin/{ => assignments}/assignment.py (66%) create mode 100644 mathics/builtin/assignments/internals.py diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index 0ee7e10f5..14c07d858 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -155,6 +155,7 @@ def is_builtin(var): for subdir in ( "arithfns", + "assignments", "colors", "distance", "drawing", diff --git a/mathics/builtin/assignments/__init__.py b/mathics/builtin/assignments/__init__.py new file mode 100644 index 000000000..be81671be --- /dev/null +++ b/mathics/builtin/assignments/__init__.py @@ -0,0 +1,7 @@ +""" +Assignments + +Assigments allow you to set variables, indexed variables, structure elements, functions, and general transformations. +""" + +from mathics.version import __version__ # noqa used in loading to check consistency. diff --git a/mathics/builtin/assignment.py b/mathics/builtin/assignments/assignment.py similarity index 66% rename from mathics/builtin/assignment.py rename to mathics/builtin/assignments/assignment.py index 0e0452bcb..d704fdc05 100644 --- a/mathics/builtin/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -3,6 +3,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import ( + AssignmentException, Builtin, BinaryOperator, PostfixOperator, @@ -13,597 +14,33 @@ from mathics.core.symbols import ( Symbol, SymbolNull, - SymbolN, - valid_context_name, system_symbols, ) from mathics.core.systemsymbols import ( SymbolFailed, - SymbolMachinePrecision, ) from mathics.core.atoms import String from mathics.core.definitions import PyMathicsLoadException from mathics.algorithm.parts import walk_parts -from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit - - -class AssignmentException(Exception): - def __init__(self, lhs, rhs) -> None: - super().__init__(" %s cannot be assigned to %s" % (rhs, lhs)) - self.lhs = lhs - self.rhs = rhs - - -def repl_pattern_by_symbol(expr): - leaves = expr.get_leaves() - if len(leaves) == 0: - return expr - - headname = expr.get_head_name() - if headname == "System`Pattern": - return leaves[0] - - changed = False - newleaves = [] - for leave in leaves: - leaf = repl_pattern_by_symbol(leave) - if not (leaf is leave): - changed = True - newleaves.append(leaf) - if changed: - return Expression(headname, *newleaves) - else: - return expr - - -def get_symbol_list(list, error_callback): - if list.has_form("List", None): - list = list.leaves - else: - list = [list] - values = [] - for item in list: - name = item.get_name() - if name: - values.append(name) - else: - error_callback(item) - return None - return values - - -# Here are the functions related to assign_elementary -# Auxiliary routines - - -def rejected_because_protected(self, lhs, tag, evaluation, ignore=False): - defs = evaluation.definitions - if not ignore and is_protected(tag, defs): - if lhs.get_name() == tag: - evaluation.message(self.get_name(), "wrsym", Symbol(tag)) - else: - evaluation.message(self.get_name(), "write", Symbol(tag), lhs) - return True - return False - - -def find_tag_and_check(lhs, tags, evaluation): - name = lhs.get_head_name() - if len(lhs.leaves) != 1: - evaluation.message_args(name, len(lhs.leaves), 1) - raise AssignmentException(lhs, None) - tag = lhs.leaves[0].get_name() - if not tag: - evaluation.message(name, "sym", lhs.leaves[0], 1) - raise AssignmentException(lhs, None) - if tags is not None and tags != [tag]: - evaluation.message(name, "tag", Symbol(name), Symbol(tag)) - raise AssignmentException(lhs, None) - if is_protected(tag, evaluation.definitions): - evaluation.message(name, "wrsym", Symbol(tag)) - raise AssignmentException(lhs, None) - return tag - - -def is_protected(tag, defin): - return "System`Protected" in defin.get_attributes(tag) - - -def build_rulopc(optval): - return Rule( - Expression( - "OptionValue", - Expression("Pattern", Symbol("$cond$"), Expression("Blank")), - ), - Expression("OptionValue", optval, Symbol("$cond$")), - ) - - -def unroll_patterns(lhs, rhs, evaluation): - if type(lhs) is Symbol: - return lhs, rhs - name = lhs.get_head_name() - lhsleaves = lhs._leaves - if name == "System`Pattern": - lhs = lhsleaves[1] - rulerepl = (lhsleaves[0], repl_pattern_by_symbol(lhs)) - rhs, status = rhs.apply_rules([Rule(*rulerepl)], evaluation) - name = lhs.get_head_name() - - if name == "System`HoldPattern": - lhs = lhsleaves[0] - name = lhs.get_head_name() - return lhs, rhs - - -def unroll_conditions(lhs): - condition = None - if type(lhs) is Symbol: - return lhs, None - else: - name, lhs_leaves = lhs.get_head_name(), lhs._leaves - condition = [] - # This handle the case of many sucesive conditions: - # f[x_]/; cond1 /; cond2 ... -> f[x_]/; And[cond1, cond2, ...] - while name == "System`Condition" and len(lhs.leaves) == 2: - condition.append(lhs_leaves[1]) - lhs = lhs_leaves[0] - name, lhs_leaves = lhs.get_head_name(), lhs._leaves - if len(condition) == 0: - return lhs, None - if len(condition) > 1: - condition = Expression("System`And", *condition) - else: - condition = condition[0] - condition = Expression("System`Condition", lhs, condition) - lhs._format_cache = None - return lhs, condition - - -def process_rhs_conditions(lhs, rhs, condition, evaluation): - # To Handle `OptionValue` in `Condition` - rulopc = build_rulopc(lhs.get_head()) - rhs_name = rhs.get_head_name() - while rhs_name == "System`Condition": - if len(rhs.leaves) != 2: - evaluation.message_args("Condition", len(rhs.leaves), 2) - raise AssignmentException(lhs, None) - lhs = Expression( - "Condition", lhs, rhs.leaves[1].apply_rules([rulopc], evaluation)[0] - ) - rhs = rhs.leaves[0] - rhs_name = rhs.get_head_name() - - # Now, let's add the conditions on the LHS - if condition: - lhs = Expression( - "Condition", - lhs, - condition.leaves[1].apply_rules([rulopc], evaluation)[0], - ) - return lhs, rhs - - -def process_tags_and_upset_dont_allow_custom(tags, upset, self, lhs, focus, evaluation): - # TODO: the following provides a hacky fix for 1259. I know @rocky loves - # this kind of things, but otherwise we need to work on rebuild the pattern - # matching mechanism... - flag_ioi, evaluation.ignore_oneidentity = evaluation.ignore_oneidentity, True - focus = focus.evaluate_leaves(evaluation) - evaluation.ignore_oneidentity = flag_ioi - name = lhs.get_head_name() - if tags is None and not upset: - name = focus.get_lookup_name() - if not name: - evaluation.message(self.get_name(), "setraw", focus) - raise AssignmentException(lhs, None) - tags = [name] - elif upset: - tags = [focus.get_lookup_name()] - else: - allowed_names = [focus.get_lookup_name()] - for name in tags: - if name not in allowed_names: - evaluation.message(self.get_name(), "tagnfd", Symbol(name)) - raise AssignmentException(lhs, None) - return tags - - -def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation): - # TODO: the following provides a hacky fix for 1259. I know @rocky loves - # this kind of things, but otherwise we need to work on rebuild the pattern - # matching mechanism... - name = lhs.get_head_name() - focus = lhs - flag_ioi, evaluation.ignore_oneidentity = evaluation.ignore_oneidentity, True - focus = focus.evaluate_leaves(evaluation) - evaluation.ignore_oneidentity = flag_ioi - if tags is None and not upset: - name = focus.get_lookup_name() - if not name: - evaluation.message(self.get_name(), "setraw", focus) - raise AssignmentException(lhs, None) - tags = [name] - elif upset: - tags = [] - if focus.is_atom(): - evaluation.message(self.get_name(), "normal") - raise AssignmentException(lhs, None) - for leaf in focus.leaves: - name = leaf.get_lookup_name() - tags.append(name) - else: - allowed_names = [focus.get_lookup_name()] - for leaf in focus.get_leaves(): - if not leaf.is_symbol() and leaf.get_head_name() in ("System`HoldPattern",): - leaf = leaf.leaves[0] - if not leaf.is_symbol() and leaf.get_head_name() in ("System`Pattern",): - leaf = leaf.leaves[1] - if not leaf.is_symbol() and leaf.get_head_name() in ( - "System`Blank", - "System`BlankSequence", - "System`BlankNullSequence", - ): - if len(leaf.leaves) == 1: - leaf = leaf.leaves[0] - - allowed_names.append(leaf.get_lookup_name()) - for name in tags: - if name not in allowed_names: - evaluation.message(self.get_name(), "tagnfd", Symbol(name)) - raise AssignmentException(lhs, None) - - return tags, focus - - -# Here starts the functions that implement `assign_elementary` for different -# kind of expressions. Maybe they should be put in a separated module or -# maybe they should be member functions of _SetOperator. - - -def process_assign_messagename(self, lhs, rhs, evaluation, tags, upset): - lhs, condition = unroll_conditions(lhs) - lhs, rhs = unroll_patterns(lhs, rhs, evaluation) - count = 0 - defs = evaluation.definitions - if len(lhs.leaves) != 2: - evaluation.message_args("MessageName", len(lhs.leaves), 2) - raise AssignmentException(lhs, None) - focus = lhs.leaves[0] - tags = process_tags_and_upset_dont_allow_custom( - tags, upset, self, lhs, focus, evaluation - ) - lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) - rule = Rule(lhs, rhs) - for tag in tags: - if rejected_because_protected(self, lhs, tag, evaluation): - continue - count += 1 - defs.add_message(tag, rule) - return count > 0 - - -def process_assign_default(self, lhs, rhs, evaluation, tags, upset): - lhs, condition = unroll_conditions(lhs) - lhs, rhs = unroll_patterns(lhs, rhs, evaluation) - count = 0 - defs = evaluation.definitions - - if len(lhs.leaves) not in (1, 2, 3): - evaluation.message_args("Default", len(lhs.leaves), 1, 2, 3) - raise AssignmentException(lhs, None) - focus = lhs.leaves[0] - tags = process_tags_and_upset_dont_allow_custom( - tags, upset, self, lhs, focus, evaluation - ) - lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) - rule = Rule(lhs, rhs) - for tag in tags: - if rejected_because_protected(self, lhs, tag, evaluation): - continue - count += 1 - defs.add_default(tag, rule) - return count > 0 - - -def process_assign_format(self, lhs, rhs, evaluation, tags, upset): - lhs, condition = unroll_conditions(lhs) - lhs, rhs = unroll_patterns(lhs, rhs, evaluation) - count = 0 - defs = evaluation.definitions - - if len(lhs.leaves) not in (1, 2): - evaluation.message_args("Format", len(lhs.leaves), 1, 2) - raise AssignmentException(lhs, None) - if len(lhs.leaves) == 2: - form = lhs.leaves[1].get_name() - if not form: - evaluation.message("Format", "fttp", lhs.leaves[1]) - raise AssignmentException(lhs, None) - else: - form = system_symbols( - "StandardForm", - "TraditionalForm", - "OutputForm", - "TeXForm", - "MathMLForm", - ) - lhs = focus = lhs.leaves[0] - tags = process_tags_and_upset_dont_allow_custom( - tags, upset, self, lhs, focus, evaluation - ) - lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) - rule = Rule(lhs, rhs) - for tag in tags: - if rejected_because_protected(self, lhs, tag, evaluation): - continue - count += 1 - defs.add_format(tag, rule, form) - return count > 0 - - -def process_assign_recursion_limit(lhs, rhs, evaluation): - rhs_int_value = rhs.get_int_value() - # if (not rhs_int_value or rhs_int_value < 20) and not - # rhs.get_name() == 'System`Infinity': - if ( - not rhs_int_value or rhs_int_value < 20 or rhs_int_value > MAX_RECURSION_DEPTH - ): # nopep8 - - evaluation.message("$RecursionLimit", "limset", rhs) - raise AssignmentException(lhs, None) - try: - set_python_recursion_limit(rhs_int_value) - except OverflowError: - # TODO: Message - raise AssignmentException(lhs, None) - return False - - -def process_assign_iteration_limit(lhs, rhs, evaluation): - rhs_int_value = rhs.get_int_value() - if ( - not rhs_int_value or rhs_int_value < 20 - ) and not rhs.get_name() == "System`Infinity": - evaluation.message("$IterationLimit", "limset", rhs) - raise AssignmentException(lhs, None) - return False - - -def process_assign_module_number(lhs, rhs, evaluation): - rhs_int_value = rhs.get_int_value() - if not rhs_int_value or rhs_int_value <= 0: - evaluation.message("$ModuleNumber", "set", rhs) - raise AssignmentException(lhs, None) - return False - - -def process_assign_line_number_and_history_length( - self, lhs, rhs, evaluation, tags, upset -): - lhs_name = lhs.get_name() - rhs_int_value = rhs.get_int_value() - if rhs_int_value is None or rhs_int_value < 0: - evaluation.message(lhs_name, "intnn", rhs) - raise AssignmentException(lhs, None) - return False - - -def process_assign_random_state(self, lhs, rhs, evaluation, tags, upset): - # TODO: allow setting of legal random states! - # (but consider pickle's insecurity!) - evaluation.message("$RandomState", "rndst", rhs) - raise AssignmentException(lhs, None) - - -def process_assign_context(self, lhs, rhs, evaluation, tags, upset): - lhs_name = lhs.get_head_name() - new_context = rhs.get_string_value() - if new_context is None or not valid_context_name( - new_context, allow_initial_backquote=True - ): - evaluation.message(lhs_name, "cxset", rhs) - exit() - raise AssignmentException(lhs, None) - - # With $Context in Mathematica you can do some strange - # things: e.g. with $Context set to Global`, something - # like: - # $Context = "`test`"; newsym - # is accepted and creates Global`test`newsym. - # Implement this behaviour by interpreting - # $Context = "`test`" - # as - # $Context = $Context <> "test`" - # - if new_context.startswith("`"): - new_context = evaluation.definitions.get_current_context() + new_context.lstrip( - "`" - ) - - evaluation.definitions.set_current_context(new_context) - return True - - -def process_assign_context_path(self, lhs, rhs, evaluation, tags, upset): - lhs_name = lhs.get_name() - currContext = evaluation.definitions.get_current_context() - context_path = [s.get_string_value() for s in rhs.get_leaves()] - context_path = [ - s if (s is None or s[0] != "`") else currContext[:-1] + s for s in context_path - ] - if rhs.has_form("List", None) and all(valid_context_name(s) for s in context_path): - evaluation.definitions.set_context_path(context_path) - return True - else: - evaluation.message(lhs_name, "cxlist", rhs) - raise AssignmentException(lhs, None) - - -def process_assign_minprecision(self, lhs, rhs, evaluation, tags, upset): - lhs_name = lhs.get_name() - rhs_int_value = rhs.get_int_value() - # $MinPrecision = Infinity is not allowed - if rhs_int_value is not None and rhs_int_value >= 0: - max_prec = evaluation.definitions.get_config_value("$MaxPrecision") - if max_prec is not None and max_prec < rhs_int_value: - evaluation.message("$MinPrecision", "preccon", Symbol("$MinPrecision")) - raise AssignmentException(lhs, None) - return False - else: - evaluation.message(lhs_name, "precset", lhs, rhs) - raise AssignmentException(lhs, None) - - -def process_assign_maxprecision(self, lhs, rhs, evaluation, tags, upset): - lhs_name = lhs.get_name() - rhs_int_value = rhs.get_int_value() - if rhs.has_form("DirectedInfinity", 1) and rhs.leaves[0].get_int_value() == 1: - return False - elif rhs_int_value is not None and rhs_int_value > 0: - min_prec = evaluation.definitions.get_config_value("$MinPrecision") - if min_prec is not None and rhs_int_value < min_prec: - evaluation.message("$MaxPrecision", "preccon", Symbol("$MaxPrecision")) - raise AssignmentException(lhs, None) - return False - else: - evaluation.message(lhs_name, "precset", lhs, rhs) - raise AssignmentException(lhs, None) - - -def process_assign_definition_values(self, lhs, rhs, evaluation, tags, upset): - name = lhs.get_head_name() - tag = find_tag_and_check(lhs, tags, evaluation) - rules = rhs.get_rules_list() - if rules is None: - evaluation.message(name, "vrule", lhs, rhs) - raise AssignmentException(lhs, None) - evaluation.definitions.set_values(tag, name, rules) - return True - - -def process_assign_options(self, lhs, rhs, evaluation, tags, upset): - lhs_leaves = lhs.leaves - name = lhs.get_head_name() - if len(lhs_leaves) != 1: - evaluation.message_args(name, len(lhs_leaves), 1) - raise AssignmentException(lhs, rhs) - tag = lhs_leaves[0].get_name() - if not tag: - evaluation.message(name, "sym", lhs_leaves[0], 1) - raise AssignmentException(lhs, rhs) - if tags is not None and tags != [tag]: - evaluation.message(name, "tag", Symbol(name), Symbol(tag)) - raise AssignmentException(lhs, rhs) - if is_protected(tag, evaluation.definitions): - evaluation.message(name, "wrsym", Symbol(tag)) - raise AssignmentException(lhs, None) - option_values = rhs.get_option_values(evaluation) - if option_values is None: - evaluation.message(name, "options", rhs) - raise AssignmentException(lhs, None) - evaluation.definitions.set_options(tag, option_values) - return True - - -def process_assign_attributes(self, lhs, rhs, evaluation, tags, upset): - name = lhs.get_head_name() - if len(lhs.leaves) != 1: - evaluation.message_args(name, len(lhs.leaves), 1) - raise AssignmentException(lhs, rhs) - tag = lhs.leaves[0].get_name() - if not tag: - evaluation.message(name, "sym", lhs.leaves[0], 1) - raise AssignmentException(lhs, rhs) - if tags is not None and tags != [tag]: - evaluation.message(name, "tag", Symbol(name), Symbol(tag)) - raise AssignmentException(lhs, rhs) - attributes = get_symbol_list( - rhs, lambda item: evaluation.message(name, "sym", item, 1) - ) - if attributes is None: - raise AssignmentException(lhs, rhs) - if "System`Locked" in evaluation.definitions.get_attributes(tag): - evaluation.message(name, "locked", Symbol(tag)) - raise AssignmentException(lhs, rhs) - evaluation.definitions.set_attributes(tag, attributes) - return True - - -def process_assign_n(self, lhs, rhs, evaluation, tags, upset): - lhs, condition = unroll_conditions(lhs) - lhs, rhs = unroll_patterns(lhs, rhs, evaluation) - defs = evaluation.definitions - - if len(lhs.leaves) not in (1, 2): - evaluation.message_args("N", len(lhs.leaves), 1, 2) - raise AssignmentException(lhs, None) - if len(lhs.leaves) == 1: - nprec = SymbolMachinePrecision - else: - nprec = lhs.leaves[1] - focus = lhs.leaves[0] - lhs = Expression(SymbolN, focus, nprec) - tags = process_tags_and_upset_dont_allow_custom( - tags, upset, self, lhs, focus, evaluation - ) - count = 0 - lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) - rule = Rule(lhs, rhs) - for tag in tags: - if rejected_because_protected(self, lhs, tag, evaluation): - continue - count += 1 - defs.add_nvalue(tag, rule) - return count > 0 - - -def process_assign_other(self, lhs, rhs, evaluation, tags=None, upset=False): - tags, focus = process_tags_and_upset_allow_custom( - tags, upset, self, lhs, evaluation - ) - lhs_name = lhs.get_name() - if lhs_name == "System`$RecursionLimit": - process_assign_recursion_limit(self, lhs, rhs, evaluation, tags, upset) - elif lhs_name in ("System`$Line", "System`$HistoryLength"): - process_assign_line_number_and_history_length( - self, lhs, rhs, evaluation, tags, upset - ) - elif lhs_name == "System`$IterationLimit": - process_assign_iteration_limit(self, lhs, rhs, evaluation, tags, upset) - elif lhs_name == "System`$ModuleNumber": - process_assign_module_number(self, lhs, rhs, evaluation, tags, upset) - elif lhs_name == "System`$MinPrecision": - process_assign_minprecision(self, lhs, rhs, evaluation, tags, upset) - elif lhs_name == "System`$MaxPrecision": - process_assign_maxprecision(self, lhs, rhs, evaluation, tags, upset) - else: - return False, tags - return True, tags - - -def assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset=None): - lhs, condition = unroll_conditions(lhs) - lhs, rhs = unroll_patterns(lhs, rhs, evaluation) - count = 0 - defs = evaluation.definitions - ignore_protection, tags = process_assign_other( - self, lhs, rhs, evaluation, tags, upset - ) - lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) - count = 0 - rule = Rule(lhs, rhs) - position = "up" if upset else None - for tag in tags: - if rejected_because_protected(self, lhs, tag, evaluation, ignore_protection): - continue - count += 1 - defs.add_rule(tag, rule, position=position) - return count > 0 +from mathics.builtin.assignments.internals import ( + assign_store_rules_by_tag, + get_symbol_values, + is_protected, + process_assign_attributes, + process_assign_context, + process_assign_context_path, + process_assign_default, + process_assign_definition_values, + process_assign_format, + process_assign_messagename, + process_assign_n, + process_assign_options, + process_assign_random_state, +) class _SetOperator(object): @@ -1657,27 +1094,6 @@ def apply(self, expr, evaluation): return Symbol("Null") -def get_symbol_values(symbol, func_name, position, evaluation): - name = symbol.get_name() - if not name: - evaluation.message(func_name, "sym", symbol, 1) - return - if position in ("default",): - definition = evaluation.definitions.get_definition(name) - else: - definition = evaluation.definitions.get_user_definition(name) - leaves = [] - for rule in definition.get_values_list(position): - if isinstance(rule, Rule): - pattern = rule.pattern - if pattern.has_form("HoldPattern", 1): - pattern = pattern.expr - else: - pattern = Expression("HoldPattern", pattern.expr) - leaves.append(Expression("RuleDelayed", pattern, rule.replace)) - return Expression("List", *leaves) - - class DownValues(Builtin): """
diff --git a/mathics/builtin/assignments/internals.py b/mathics/builtin/assignments/internals.py new file mode 100644 index 000000000..6c814a51b --- /dev/null +++ b/mathics/builtin/assignments/internals.py @@ -0,0 +1,605 @@ +# -*- coding: utf-8 -*- + +from mathics.version import __version__ # noqa used in loading to check consistency. + +from mathics.builtin.base import AssignmentException +from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit +from mathics.core.expression import Expression +from mathics.core.rules import Rule +from mathics.core.symbols import ( + Symbol, + SymbolN, + system_symbols, + valid_context_name, +) +from mathics.core.systemsymbols import SymbolMachinePrecision + + +def assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset=None): + lhs, condition = unroll_conditions(lhs) + lhs, rhs = unroll_patterns(lhs, rhs, evaluation) + count = 0 + defs = evaluation.definitions + ignore_protection, tags = process_assign_other( + self, lhs, rhs, evaluation, tags, upset + ) + lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) + count = 0 + rule = Rule(lhs, rhs) + position = "up" if upset else None + for tag in tags: + if rejected_because_protected(self, lhs, tag, evaluation, ignore_protection): + continue + count += 1 + defs.add_rule(tag, rule, position=position) + return count > 0 + + +def build_rulopc(optval): + return Rule( + Expression( + "OptionValue", + Expression("Pattern", Symbol("$cond$"), Expression("Blank")), + ), + Expression("OptionValue", optval, Symbol("$cond$")), + ) + + +def get_symbol_list(list, error_callback): + if list.has_form("List", None): + list = list.leaves + else: + list = [list] + values = [] + for item in list: + name = item.get_name() + if name: + values.append(name) + else: + error_callback(item) + return None + return values + + +def get_symbol_values(symbol, func_name, position, evaluation): + name = symbol.get_name() + if not name: + evaluation.message(func_name, "sym", symbol, 1) + return + if position in ("default",): + definition = evaluation.definitions.get_definition(name) + else: + definition = evaluation.definitions.get_user_definition(name) + leaves = [] + for rule in definition.get_values_list(position): + if isinstance(rule, Rule): + pattern = rule.pattern + if pattern.has_form("HoldPattern", 1): + pattern = pattern.expr + else: + pattern = Expression("HoldPattern", pattern.expr) + leaves.append(Expression("RuleDelayed", pattern, rule.replace)) + return Expression("List", *leaves) + + +def is_protected(tag, defin): + return "System`Protected" in defin.get_attributes(tag) + + +def repl_pattern_by_symbol(expr): + leaves = expr.get_leaves() + if len(leaves) == 0: + return expr + + headname = expr.get_head_name() + if headname == "System`Pattern": + return leaves[0] + + changed = False + newleaves = [] + for leave in leaves: + leaf = repl_pattern_by_symbol(leave) + if not (leaf is leave): + changed = True + newleaves.append(leaf) + if changed: + return Expression(headname, *newleaves) + else: + return expr + + +# Here are the functions related to assign_elementary + +# Auxiliary routines + + +def rejected_because_protected(self, lhs, tag, evaluation, ignore=False): + defs = evaluation.definitions + if not ignore and is_protected(tag, defs): + if lhs.get_name() == tag: + evaluation.message(self.get_name(), "wrsym", Symbol(tag)) + else: + evaluation.message(self.get_name(), "write", Symbol(tag), lhs) + return True + return False + + +def find_tag_and_check(lhs, tags, evaluation): + name = lhs.get_head_name() + if len(lhs.leaves) != 1: + evaluation.message_args(name, len(lhs.leaves), 1) + raise AssignmentException(lhs, None) + tag = lhs.leaves[0].get_name() + if not tag: + evaluation.message(name, "sym", lhs.leaves[0], 1) + raise AssignmentException(lhs, None) + if tags is not None and tags != [tag]: + evaluation.message(name, "tag", Symbol(name), Symbol(tag)) + raise AssignmentException(lhs, None) + if is_protected(tag, evaluation.definitions): + evaluation.message(name, "wrsym", Symbol(tag)) + raise AssignmentException(lhs, None) + return tag + + +def unroll_patterns(lhs, rhs, evaluation): + if type(lhs) is Symbol: + return lhs, rhs + name = lhs.get_head_name() + lhsleaves = lhs._leaves + if name == "System`Pattern": + lhs = lhsleaves[1] + rulerepl = (lhsleaves[0], repl_pattern_by_symbol(lhs)) + rhs, status = rhs.apply_rules([Rule(*rulerepl)], evaluation) + name = lhs.get_head_name() + + if name == "System`HoldPattern": + lhs = lhsleaves[0] + name = lhs.get_head_name() + return lhs, rhs + + +def unroll_conditions(lhs): + condition = None + if type(lhs) is Symbol: + return lhs, None + else: + name, lhs_leaves = lhs.get_head_name(), lhs._leaves + condition = [] + # This handle the case of many sucesive conditions: + # f[x_]/; cond1 /; cond2 ... -> f[x_]/; And[cond1, cond2, ...] + while name == "System`Condition" and len(lhs.leaves) == 2: + condition.append(lhs_leaves[1]) + lhs = lhs_leaves[0] + name, lhs_leaves = lhs.get_head_name(), lhs._leaves + if len(condition) == 0: + return lhs, None + if len(condition) > 1: + condition = Expression("System`And", *condition) + else: + condition = condition[0] + condition = Expression("System`Condition", lhs, condition) + lhs._format_cache = None + return lhs, condition + + +# Here starts the functions that implement `assign_elementary` for different +# kind of expressions. Maybe they should be put in a separated module or +# maybe they should be member functions of _SetOperator. + + +def process_assign_recursion_limit(lhs, rhs, evaluation): + rhs_int_value = rhs.get_int_value() + # if (not rhs_int_value or rhs_int_value < 20) and not + # rhs.get_name() == 'System`Infinity': + if ( + not rhs_int_value or rhs_int_value < 20 or rhs_int_value > MAX_RECURSION_DEPTH + ): # nopep8 + + evaluation.message("$RecursionLimit", "limset", rhs) + raise AssignmentException(lhs, None) + try: + set_python_recursion_limit(rhs_int_value) + except OverflowError: + # TODO: Message + raise AssignmentException(lhs, None) + return False + + +def process_assign_iteration_limit(lhs, rhs, evaluation): + rhs_int_value = rhs.get_int_value() + if ( + not rhs_int_value or rhs_int_value < 20 + ) and not rhs.get_name() == "System`Infinity": + evaluation.message("$IterationLimit", "limset", rhs) + raise AssignmentException(lhs, None) + return False + + +def process_assign_module_number(lhs, rhs, evaluation): + rhs_int_value = rhs.get_int_value() + if not rhs_int_value or rhs_int_value <= 0: + evaluation.message("$ModuleNumber", "set", rhs) + raise AssignmentException(lhs, None) + return False + + +def process_assign_line_number_and_history_length( + self, lhs, rhs, evaluation, tags, upset +): + lhs_name = lhs.get_name() + rhs_int_value = rhs.get_int_value() + if rhs_int_value is None or rhs_int_value < 0: + evaluation.message(lhs_name, "intnn", rhs) + raise AssignmentException(lhs, None) + return False + + +def process_assign_random_state(self, lhs, rhs, evaluation, tags, upset): + # TODO: allow setting of legal random states! + # (but consider pickle's insecurity!) + evaluation.message("$RandomState", "rndst", rhs) + raise AssignmentException(lhs, None) + + +def process_assign_context(self, lhs, rhs, evaluation, tags, upset): + lhs_name = lhs.get_head_name() + new_context = rhs.get_string_value() + if new_context is None or not valid_context_name( + new_context, allow_initial_backquote=True + ): + evaluation.message(lhs_name, "cxset", rhs) + exit() + raise AssignmentException(lhs, None) + + # With $Context in Mathematica you can do some strange + # things: e.g. with $Context set to Global`, something + # like: + # $Context = "`test`"; newsym + # is accepted and creates Global`test`newsym. + # Implement this behaviour by interpreting + # $Context = "`test`" + # as + # $Context = $Context <> "test`" + # + if new_context.startswith("`"): + new_context = evaluation.definitions.get_current_context() + new_context.lstrip( + "`" + ) + + evaluation.definitions.set_current_context(new_context) + return True + + +def process_assign_context_path(self, lhs, rhs, evaluation, tags, upset): + lhs_name = lhs.get_name() + currContext = evaluation.definitions.get_current_context() + context_path = [s.get_string_value() for s in rhs.get_leaves()] + context_path = [ + s if (s is None or s[0] != "`") else currContext[:-1] + s for s in context_path + ] + if rhs.has_form("List", None) and all(valid_context_name(s) for s in context_path): + evaluation.definitions.set_context_path(context_path) + return True + else: + evaluation.message(lhs_name, "cxlist", rhs) + raise AssignmentException(lhs, None) + + +def process_assign_minprecision(self, lhs, rhs, evaluation, tags, upset): + lhs_name = lhs.get_name() + rhs_int_value = rhs.get_int_value() + # $MinPrecision = Infinity is not allowed + if rhs_int_value is not None and rhs_int_value >= 0: + max_prec = evaluation.definitions.get_config_value("$MaxPrecision") + if max_prec is not None and max_prec < rhs_int_value: + evaluation.message("$MinPrecision", "preccon", Symbol("$MinPrecision")) + raise AssignmentException(lhs, None) + return False + else: + evaluation.message(lhs_name, "precset", lhs, rhs) + raise AssignmentException(lhs, None) + + +def process_assign_maxprecision(self, lhs, rhs, evaluation, tags, upset): + lhs_name = lhs.get_name() + rhs_int_value = rhs.get_int_value() + if rhs.has_form("DirectedInfinity", 1) and rhs.leaves[0].get_int_value() == 1: + return False + elif rhs_int_value is not None and rhs_int_value > 0: + min_prec = evaluation.definitions.get_config_value("$MinPrecision") + if min_prec is not None and rhs_int_value < min_prec: + evaluation.message("$MaxPrecision", "preccon", Symbol("$MaxPrecision")) + raise AssignmentException(lhs, None) + return False + else: + evaluation.message(lhs_name, "precset", lhs, rhs) + raise AssignmentException(lhs, None) + + +def process_assign_definition_values(self, lhs, rhs, evaluation, tags, upset): + name = lhs.get_head_name() + tag = find_tag_and_check(lhs, tags, evaluation) + rules = rhs.get_rules_list() + if rules is None: + evaluation.message(name, "vrule", lhs, rhs) + raise AssignmentException(lhs, None) + evaluation.definitions.set_values(tag, name, rules) + return True + + +def process_assign_options(self, lhs, rhs, evaluation, tags, upset): + lhs_leaves = lhs.leaves + name = lhs.get_head_name() + if len(lhs_leaves) != 1: + evaluation.message_args(name, len(lhs_leaves), 1) + raise AssignmentException(lhs, rhs) + tag = lhs_leaves[0].get_name() + if not tag: + evaluation.message(name, "sym", lhs_leaves[0], 1) + raise AssignmentException(lhs, rhs) + if tags is not None and tags != [tag]: + evaluation.message(name, "tag", Symbol(name), Symbol(tag)) + raise AssignmentException(lhs, rhs) + if is_protected(tag, evaluation.definitions): + evaluation.message(name, "wrsym", Symbol(tag)) + raise AssignmentException(lhs, None) + option_values = rhs.get_option_values(evaluation) + if option_values is None: + evaluation.message(name, "options", rhs) + raise AssignmentException(lhs, None) + evaluation.definitions.set_options(tag, option_values) + return True + + +def process_assign_n(self, lhs, rhs, evaluation, tags, upset): + lhs, condition = unroll_conditions(lhs) + lhs, rhs = unroll_patterns(lhs, rhs, evaluation) + defs = evaluation.definitions + + if len(lhs.leaves) not in (1, 2): + evaluation.message_args("N", len(lhs.leaves), 1, 2) + raise AssignmentException(lhs, None) + if len(lhs.leaves) == 1: + nprec = SymbolMachinePrecision + else: + nprec = lhs.leaves[1] + focus = lhs.leaves[0] + lhs = Expression(SymbolN, focus, nprec) + tags = process_tags_and_upset_dont_allow_custom( + tags, upset, self, lhs, focus, evaluation + ) + count = 0 + lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) + rule = Rule(lhs, rhs) + for tag in tags: + if rejected_because_protected(self, lhs, tag, evaluation): + continue + count += 1 + defs.add_nvalue(tag, rule) + return count > 0 + + +def process_assign_other(self, lhs, rhs, evaluation, tags=None, upset=False): + tags, focus = process_tags_and_upset_allow_custom( + tags, upset, self, lhs, evaluation + ) + lhs_name = lhs.get_name() + if lhs_name == "System`$RecursionLimit": + process_assign_recursion_limit(self, lhs, rhs, evaluation, tags, upset) + elif lhs_name in ("System`$Line", "System`$HistoryLength"): + process_assign_line_number_and_history_length( + self, lhs, rhs, evaluation, tags, upset + ) + elif lhs_name == "System`$IterationLimit": + process_assign_iteration_limit(self, lhs, rhs, evaluation, tags, upset) + elif lhs_name == "System`$ModuleNumber": + process_assign_module_number(self, lhs, rhs, evaluation, tags, upset) + elif lhs_name == "System`$MinPrecision": + process_assign_minprecision(self, lhs, rhs, evaluation, tags, upset) + elif lhs_name == "System`$MaxPrecision": + process_assign_maxprecision(self, lhs, rhs, evaluation, tags, upset) + else: + return False, tags + return True, tags + + +def process_assign_attributes(self, lhs, rhs, evaluation, tags, upset): + name = lhs.get_head_name() + if len(lhs.leaves) != 1: + evaluation.message_args(name, len(lhs.leaves), 1) + raise AssignmentException(lhs, rhs) + tag = lhs.leaves[0].get_name() + if not tag: + evaluation.message(name, "sym", lhs.leaves[0], 1) + raise AssignmentException(lhs, rhs) + if tags is not None and tags != [tag]: + evaluation.message(name, "tag", Symbol(name), Symbol(tag)) + raise AssignmentException(lhs, rhs) + attributes = get_symbol_list( + rhs, lambda item: evaluation.message(name, "sym", item, 1) + ) + if attributes is None: + raise AssignmentException(lhs, rhs) + if "System`Locked" in evaluation.definitions.get_attributes(tag): + evaluation.message(name, "locked", Symbol(tag)) + raise AssignmentException(lhs, rhs) + evaluation.definitions.set_attributes(tag, attributes) + return True + + +def process_assign_default(self, lhs, rhs, evaluation, tags, upset): + lhs, condition = unroll_conditions(lhs) + lhs, rhs = unroll_patterns(lhs, rhs, evaluation) + count = 0 + defs = evaluation.definitions + + if len(lhs.leaves) not in (1, 2, 3): + evaluation.message_args("Default", len(lhs.leaves), 1, 2, 3) + raise AssignmentException(lhs, None) + focus = lhs.leaves[0] + tags = process_tags_and_upset_dont_allow_custom( + tags, upset, self, lhs, focus, evaluation + ) + lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) + rule = Rule(lhs, rhs) + for tag in tags: + if rejected_because_protected(self, lhs, tag, evaluation): + continue + count += 1 + defs.add_default(tag, rule) + return count > 0 + + +def process_assign_format(self, lhs, rhs, evaluation, tags, upset): + lhs, condition = unroll_conditions(lhs) + lhs, rhs = unroll_patterns(lhs, rhs, evaluation) + count = 0 + defs = evaluation.definitions + + if len(lhs.leaves) not in (1, 2): + evaluation.message_args("Format", len(lhs.leaves), 1, 2) + raise AssignmentException(lhs, None) + if len(lhs.leaves) == 2: + form = lhs.leaves[1].get_name() + if not form: + evaluation.message("Format", "fttp", lhs.leaves[1]) + raise AssignmentException(lhs, None) + else: + form = system_symbols( + "StandardForm", + "TraditionalForm", + "OutputForm", + "TeXForm", + "MathMLForm", + ) + lhs = focus = lhs.leaves[0] + tags = process_tags_and_upset_dont_allow_custom( + tags, upset, self, lhs, focus, evaluation + ) + lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) + rule = Rule(lhs, rhs) + for tag in tags: + if rejected_because_protected(self, lhs, tag, evaluation): + continue + count += 1 + defs.add_format(tag, rule, form) + return count > 0 + + +def process_assign_messagename(self, lhs, rhs, evaluation, tags, upset): + lhs, condition = unroll_conditions(lhs) + lhs, rhs = unroll_patterns(lhs, rhs, evaluation) + count = 0 + defs = evaluation.definitions + if len(lhs.leaves) != 2: + evaluation.message_args("MessageName", len(lhs.leaves), 2) + raise AssignmentException(lhs, None) + focus = lhs.leaves[0] + tags = process_tags_and_upset_dont_allow_custom( + tags, upset, self, lhs, focus, evaluation + ) + lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) + rule = Rule(lhs, rhs) + for tag in tags: + if rejected_because_protected(self, lhs, tag, evaluation): + continue + count += 1 + defs.add_message(tag, rule) + return count > 0 + + +def process_rhs_conditions(lhs, rhs, condition, evaluation): + # To Handle `OptionValue` in `Condition` + rulopc = build_rulopc(lhs.get_head()) + rhs_name = rhs.get_head_name() + while rhs_name == "System`Condition": + if len(rhs.leaves) != 2: + evaluation.message_args("Condition", len(rhs.leaves), 2) + raise AssignmentException(lhs, None) + lhs = Expression( + "Condition", lhs, rhs.leaves[1].apply_rules([rulopc], evaluation)[0] + ) + rhs = rhs.leaves[0] + rhs_name = rhs.get_head_name() + + # Now, let's add the conditions on the LHS + if condition: + lhs = Expression( + "Condition", + lhs, + condition.leaves[1].apply_rules([rulopc], evaluation)[0], + ) + return lhs, rhs + + +def process_tags_and_upset_dont_allow_custom(tags, upset, self, lhs, focus, evaluation): + # TODO: the following provides a hacky fix for 1259. I know @rocky loves + # this kind of things, but otherwise we need to work on rebuild the pattern + # matching mechanism... + flag_ioi, evaluation.ignore_oneidentity = evaluation.ignore_oneidentity, True + focus = focus.evaluate_leaves(evaluation) + evaluation.ignore_oneidentity = flag_ioi + name = lhs.get_head_name() + if tags is None and not upset: + name = focus.get_lookup_name() + if not name: + evaluation.message(self.get_name(), "setraw", focus) + raise AssignmentException(lhs, None) + tags = [name] + elif upset: + tags = [focus.get_lookup_name()] + else: + allowed_names = [focus.get_lookup_name()] + for name in tags: + if name not in allowed_names: + evaluation.message(self.get_name(), "tagnfd", Symbol(name)) + raise AssignmentException(lhs, None) + return tags + + +def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation): + # TODO: the following provides a hacky fix for 1259. I know @rocky loves + # this kind of things, but otherwise we need to work on rebuild the pattern + # matching mechanism... + name = lhs.get_head_name() + focus = lhs + flag_ioi, evaluation.ignore_oneidentity = evaluation.ignore_oneidentity, True + focus = focus.evaluate_leaves(evaluation) + evaluation.ignore_oneidentity = flag_ioi + if tags is None and not upset: + name = focus.get_lookup_name() + if not name: + evaluation.message(self.get_name(), "setraw", focus) + raise AssignmentException(lhs, None) + tags = [name] + elif upset: + tags = [] + if focus.is_atom(): + evaluation.message(self.get_name(), "normal") + raise AssignmentException(lhs, None) + for leaf in focus.leaves: + name = leaf.get_lookup_name() + tags.append(name) + else: + allowed_names = [focus.get_lookup_name()] + for leaf in focus.get_leaves(): + if not leaf.is_symbol() and leaf.get_head_name() in ("System`HoldPattern",): + leaf = leaf.leaves[0] + if not leaf.is_symbol() and leaf.get_head_name() in ("System`Pattern",): + leaf = leaf.leaves[1] + if not leaf.is_symbol() and leaf.get_head_name() in ( + "System`Blank", + "System`BlankSequence", + "System`BlankNullSequence", + ): + if len(leaf.leaves) == 1: + leaf = leaf.leaves[0] + + allowed_names.append(leaf.get_lookup_name()) + for name in tags: + if name not in allowed_names: + evaluation.message(self.get_name(), "tagnfd", Symbol(name)) + raise AssignmentException(lhs, None) + + return tags, focus diff --git a/mathics/builtin/attributes.py b/mathics/builtin/attributes.py index 7628929d3..2528982c7 100644 --- a/mathics/builtin/attributes.py +++ b/mathics/builtin/attributes.py @@ -16,7 +16,7 @@ from mathics.core.symbols import Symbol, SymbolNull from mathics.core.atoms import String -from mathics.builtin.assignment import get_symbol_list +from mathics.builtin.assignments.internals import get_symbol_list class Attributes(Builtin): diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 63a09a77b..f169278bc 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -70,6 +70,13 @@ def has_option(options, name, evaluation): mathics_to_python = {} +class AssignmentException(Exception): + def __init__(self, lhs, rhs) -> None: + super().__init__(" %s cannot be assigned to %s" % (rhs, lhs)) + self.lhs = lhs + self.rhs = rhs + + class Builtin(object): name: typing.Optional[str] = None context = "" diff --git a/mathics/builtin/scoping.py b/mathics/builtin/scoping.py index 8e48c3630..f2a6bede8 100644 --- a/mathics/builtin/scoping.py +++ b/mathics/builtin/scoping.py @@ -12,7 +12,7 @@ Integer, ) -from mathics.core.rules import Rule +from mathics.builtin.assignments.internals import get_symbol_list from mathics.core.evaluation import Evaluation @@ -383,7 +383,6 @@ def apply_symbol(self, vars, attributes, evaluation): "Unique[vars_, attributes___]" from mathics.core.parser import is_symbol_name - from mathics.builtin.attributes import get_symbol_list attributes = attributes.get_sequence() if len(attributes) > 1: diff --git a/setup.py b/setup.py index 03b20bca1..3f3685d56 100644 --- a/setup.py +++ b/setup.py @@ -146,6 +146,7 @@ def subdirs(root, file="*.*", depth=10): "mathics.core.parser", "mathics.builtin", "mathics.builtin.arithfns", + "mathics.builtin.assignments", "mathics.builtin.box", "mathics.builtin.colors", "mathics.builtin.compile", @@ -215,6 +216,7 @@ def subdirs(root, file="*.*", depth=10): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Scientific/Engineering", From 26a663bcb5a63f30028112f761d54d6962ca0518 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 16 Oct 2021 20:50:50 -0400 Subject: [PATCH 185/193] Split out assignment upvalies and clear operations --- mathics/builtin/assignments/assignment.py | 472 ++-------------------- mathics/builtin/assignments/clear.py | 278 +++++++++++++ mathics/builtin/assignments/internals.py | 88 +++- mathics/builtin/assignments/upvalues.py | 95 +++++ mathics/builtin/base.py | 7 - 5 files changed, 484 insertions(+), 456 deletions(-) create mode 100644 mathics/builtin/assignments/clear.py create mode 100644 mathics/builtin/assignments/upvalues.py diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py index d704fdc05..d360c2960 100644 --- a/mathics/builtin/assignments/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- +""" +Forms of Assignment +""" from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.builtin.base import ( - AssignmentException, Builtin, BinaryOperator, PostfixOperator, @@ -11,11 +13,7 @@ ) from mathics.core.rules import Rule from mathics.core.expression import Expression -from mathics.core.symbols import ( - Symbol, - SymbolNull, - system_symbols, -) +from mathics.core.symbols import Symbol, SymbolNull from mathics.core.systemsymbols import ( SymbolFailed, @@ -24,113 +22,19 @@ from mathics.core.atoms import String from mathics.core.definitions import PyMathicsLoadException -from mathics.algorithm.parts import walk_parts - -from mathics.builtin.assignments.internals import ( - assign_store_rules_by_tag, - get_symbol_values, - is_protected, - process_assign_attributes, - process_assign_context, - process_assign_context_path, - process_assign_default, - process_assign_definition_values, - process_assign_format, - process_assign_messagename, - process_assign_n, - process_assign_options, - process_assign_random_state, -) - - -class _SetOperator(object): - special_cases = { - "System`OwnValues": process_assign_definition_values, - "System`DownValues": process_assign_definition_values, - "System`SubValues": process_assign_definition_values, - "System`UpValues": process_assign_definition_values, - "System`NValues": process_assign_definition_values, - "System`DefaultValues": process_assign_definition_values, - "System`Messages": process_assign_definition_values, - "System`Attributes": process_assign_attributes, - "System`Options": process_assign_options, - "System`$RandomState": process_assign_random_state, - "System`$Context": process_assign_context, - "System`$ContextPath": process_assign_context_path, - "System`N": process_assign_n, - "System`MessageName": process_assign_messagename, - "System`Default": process_assign_default, - "System`Format": process_assign_format, - } - def assign_elementary(self, lhs, rhs, evaluation, tags=None, upset=False): - if type(lhs) is Symbol: - name = lhs.name - else: - name = lhs.get_head_name() - lhs._format_cache = None - try: - # Deal with direct assignation to properties of - # the definition object - func = self.special_cases.get(name, None) - if func: - return func(self, lhs, rhs, evaluation, tags, upset) - - return assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset) - except AssignmentException: - return False - - def assign(self, lhs, rhs, evaluation): - lhs._format_cache = None - defs = evaluation.definitions - if lhs.get_head_name() == "System`List": - if not (rhs.get_head_name() == "System`List") or len(lhs.leaves) != len( - rhs.leaves - ): # nopep8 - - evaluation.message(self.get_name(), "shape", lhs, rhs) - return False - else: - result = True - for left, right in zip(lhs.leaves, rhs.leaves): - if not self.assign(left, right, evaluation): - result = False - return result - elif lhs.get_head_name() == "System`Part": - if len(lhs.leaves) < 1: - evaluation.message(self.get_name(), "setp", lhs) - return False - symbol = lhs.leaves[0] - name = symbol.get_name() - if not name: - evaluation.message(self.get_name(), "setps", symbol) - return False - if is_protected(name, defs): - evaluation.message(self.get_name(), "wrsym", symbol) - return False - rule = defs.get_ownvalue(name) - if rule is None: - evaluation.message(self.get_name(), "noval", symbol) - return False - indices = lhs.leaves[1:] - result = walk_parts([rule.replace], indices, evaluation, rhs) - if result: - evaluation.definitions.set_ownvalue(name, result) - else: - return False - else: - return self.assign_elementary(lhs, rhs, evaluation) +from mathics.builtin.assignments.internals import _SetOperator, get_symbol_values class Set(BinaryOperator, _SetOperator): """
-
'Set[$expr$, $value$]' -
$expr$ = $value$ -
evaluates $value$ and assigns it to $expr$. -
{$s1$, $s2$, $s3$} = {$v1$, $v2$, $v3$} -
sets multiple symbols ($s1$, $s2$, ...) to the - corresponding values ($v1$, $v2$, ...). +
'Set[$expr$, $value$]' +
$expr$ = $value$ +
evaluates $value$ and assigns it to $expr$. + +
{$s1$, $s2$, $s3$} = {$v1$, $v2$, $v3$} +
sets multiple symbols ($s1$, $s2$, ...) to the corresponding values ($v1$, $v2$, ...).
'Set' can be used to give a symbol a value: @@ -187,16 +91,24 @@ class Set(BinaryOperator, _SetOperator): #> x = Infinity; """ + attributes = ("HoldFirst", "SequenceHold") + grouping = "Right" + + messages = { + "setraw": "Cannot assign to raw object `1`.", + "shape": "Lists `1` and `2` are not the same shape.", + } + operator = "=" precedence = 40 - grouping = "Right" - attributes = ("HoldFirst", "SequenceHold") messages = { "setraw": "Cannot assign to raw object `1`.", "shape": "Lists `1` and `2` are not the same shape.", } + summary_text = "assign a value" + def apply(self, lhs, rhs, evaluation): "lhs_ = rhs_" @@ -212,9 +124,7 @@ class SetDelayed(Set):
assigns $value$ to $expr$, without evaluating $value$.
- 'SetDelayed' is like 'Set', except it has attribute 'HoldAll', - thus it does not evaluate the right-hand side immediately, but - evaluates it when needed. + 'SetDelayed' is like 'Set', except it has attribute 'HoldAll', thus it does not evaluate the right-hand side immediately, but evaluates it when needed. >> Attributes[SetDelayed] = {HoldAll, Protected, SequenceHold} @@ -250,6 +160,8 @@ class SetDelayed(Set): operator = ":=" attributes = ("HoldAll", "SequenceHold") + summary_text = "test a delayed value; used in defining functions" + def apply(self, lhs, rhs, evaluation): "lhs_ := rhs_" @@ -259,87 +171,6 @@ def apply(self, lhs, rhs, evaluation): return SymbolFailed -class UpSet(BinaryOperator, _SetOperator): - """ -
-
$f$[$x$] ^= $expression$ -
evaluates $expression$ and assigns it to the value of - $f$[$x$], associating the value with $x$. -
- - 'UpSet' creates an upvalue: - >> a[b] ^= 3; - >> DownValues[a] - = {} - >> UpValues[b] - = {HoldPattern[a[b]] :> 3} - - >> a ^= 3 - : Nonatomic expression expected. - = 3 - - You can use 'UpSet' to specify special values like format values. - However, these values will not be saved in 'UpValues': - >> Format[r] ^= "custom"; - >> r - = custom - >> UpValues[r] - = {} - - #> f[g, a + b, h] ^= 2 - : Tag Plus in f[g, a + b, h] is Protected. - = 2 - #> UpValues[h] - = {HoldPattern[f[g, a + b, h]] :> 2} - """ - - operator = "^=" - precedence = 40 - attributes = ("HoldFirst", "SequenceHold") - grouping = "Right" - - def apply(self, lhs, rhs, evaluation): - "lhs_ ^= rhs_" - - self.assign_elementary(lhs, rhs, evaluation, upset=True) - return rhs - - -class UpSetDelayed(UpSet): - """ -
-
'UpSetDelayed[$expression$, $value$]' -
'$expression$ ^:= $value$' -
assigns $expression$ to the value of $f$[$x$] (without - evaluating $expression$), associating the value with $x$. -
- - >> a[b] ^:= x - >> x = 2; - >> a[b] - = 2 - >> UpValues[b] - = {HoldPattern[a[b]] :> x} - - #> f[g, a + b, h] ^:= 2 - : Tag Plus in f[g, a + b, h] is Protected. - #> f[a+b] ^:= 2 - : Tag Plus in f[a + b] is Protected. - = $Failed - """ - - operator = "^:=" - attributes = ("HoldAll", "SequenceHold") - - def apply(self, lhs, rhs, evaluation): - "lhs_ ^:= rhs_" - - if self.assign_elementary(lhs, rhs, evaluation, upset=True): - return Symbol("Null") - else: - return SymbolFailed - - class TagSet(Builtin, _SetOperator): """
@@ -839,261 +670,6 @@ def format_definition_input(self, symbol, evaluation, options): return ret -class Clear(Builtin): - """ -
-
'Clear[$symb1$, $symb2$, ...]' -
clears all values of the given symbols. - The arguments can also be given as strings containing symbol names. -
- - >> x = 2; - >> Clear[x] - >> x - = x - - >> x = 2; - >> y = 3; - >> Clear["Global`*"] - >> x - = x - >> y - = y - - 'ClearAll' may not be called for 'Protected' symbols. - >> Clear[Sin] - : Symbol Sin is Protected. - The values and rules associated with built-in symbols will not get lost when applying 'Clear' - (after unprotecting them): - >> Unprotect[Sin] - >> Clear[Sin] - >> Sin[Pi] - = 0 - - 'Clear' does not remove attributes, messages, options, and default values associated - with the symbols. Use 'ClearAll' to do so. - >> Attributes[r] = {Flat, Orderless}; - >> Clear["r"] - >> Attributes[r] - = {Flat, Orderless} - """ - - attributes = ("HoldAll",) - - messages = { - "ssym": "`1` is not a symbol or a string.", - } - - allow_locked = True - - def do_clear(self, definition): - definition.ownvalues = [] - definition.downvalues = [] - definition.subvalues = [] - definition.upvalues = [] - definition.formatvalues = {} - definition.nvalues = [] - - def apply(self, symbols, evaluation): - "%(name)s[symbols___]" - if isinstance(symbols, Symbol): - symbols = [symbols] - elif isinstance(symbols, Expression): - symbols = symbols.get_leaves() - elif isinstance(symbols, String): - symbols = [symbols] - else: - symbols = symbols.get_sequence() - - for symbol in symbols: - if isinstance(symbol, Symbol): - names = [symbol.get_name()] - else: - pattern = symbol.get_string_value() - if not pattern: - evaluation.message("Clear", "ssym", symbol) - continue - if pattern[0] == "`": - pattern = evaluation.definitions.get_current_context() + pattern[1:] - - names = evaluation.definitions.get_matching_names(pattern) - for name in names: - attributes = evaluation.definitions.get_attributes(name) - if is_protected(name, evaluation.definitions): - evaluation.message("Clear", "wrsym", Symbol(name)) - continue - if not self.allow_locked and "System`Locked" in attributes: - evaluation.message("Clear", "locked", Symbol(name)) - continue - definition = evaluation.definitions.get_user_definition(name) - self.do_clear(definition) - - return Symbol("Null") - - def apply_all(self, evaluation): - "Clear[System`All]" - evaluation.definitions.set_user_definitions({}) - evaluation.definitions.clear_pymathics_modules() - return - - -class ClearAll(Clear): - """ -
-
'ClearAll[$symb1$, $symb2$, ...]' -
clears all values, attributes, messages and options associated with the given symbols. - The arguments can also be given as strings containing symbol names. -
- - >> x = 2; - >> ClearAll[x] - >> x - = x - >> Attributes[r] = {Flat, Orderless}; - >> ClearAll[r] - >> Attributes[r] - = {} - - 'ClearAll' may not be called for 'Protected' or 'Locked' symbols. - >> Attributes[lock] = {Locked}; - >> ClearAll[lock] - : Symbol lock is locked. - """ - - allow_locked = False - - def do_clear(self, definition): - super(ClearAll, self).do_clear(definition) - definition.attributes = set() - definition.messages = [] - definition.options = [] - definition.defaultvalues = [] - - def apply_all(self, evaluation): - "ClearAll[System`All]" - evaluation.definitions.set_user_definitions({}) - evaluation.definitions.clear_pymathics_modules() - return - - -class Unset(PostfixOperator): - """ -
-
'Unset[$x$]' -
'$x$=.' -
removes any value belonging to $x$. -
- >> a = 2 - = 2 - >> a =. - >> a - = a - - Unsetting an already unset or never defined variable will not - change anything: - >> a =. - >> b =. - - 'Unset' can unset particular function values. It will print a message - if no corresponding rule is found. - >> f[x_] =. - : Assignment on f for f[x_] not found. - = $Failed - >> f[x_] := x ^ 2 - >> f[3] - = 9 - >> f[x_] =. - >> f[3] - = f[3] - - You can also unset 'OwnValues', 'DownValues', 'SubValues', and 'UpValues' directly. - This is equivalent to setting them to '{}'. - >> f[x_] = x; f[0] = 1; - >> DownValues[f] =. - >> f[2] - = f[2] - - 'Unset' threads over lists: - >> a = b = 3; - >> {a, {b}} =. - = {Null, {Null}} - - #> x = 2; - #> OwnValues[x] =. - #> x - = x - #> f[a][b] = 3; - #> SubValues[f] =. - #> f[a][b] - = f[a][b] - #> PrimeQ[p] ^= True - = True - #> PrimeQ[p] - = True - #> UpValues[p] =. - #> PrimeQ[p] - = False - - #> a + b ^= 5; - #> a =. - #> a + b - = 5 - #> {UpValues[a], UpValues[b]} =. - = {Null, Null} - #> a + b - = a + b - - #> Unset[Messages[1]] - : First argument in Messages[1] is not a symbol or a string naming a symbol. - = $Failed - """ - - operator = "=." - precedence = 670 - attributes = ("HoldFirst", "Listable", "ReadProtected") - - messages = { - "norep": "Assignment on `2` for `1` not found.", - "usraw": "Cannot unset raw object `1`.", - } - - def apply(self, expr, evaluation): - "Unset[expr_]" - - name = expr.get_head_name() - if name in system_symbols( - "OwnValues", - "DownValues", - "SubValues", - "UpValues", - "NValues", - "Options", - "Messages", - ): - if len(expr.leaves) != 1: - evaluation.message_args(name, len(expr.leaves), 1) - return SymbolFailed - symbol = expr.leaves[0].get_name() - if not symbol: - evaluation.message(name, "fnsym", expr) - return SymbolFailed - if name == "System`Options": - empty = {} - else: - empty = [] - evaluation.definitions.set_values(symbol, name, empty) - return Symbol("Null") - name = expr.get_lookup_name() - if not name: - evaluation.message("Unset", "usraw", expr) - return SymbolFailed - if not evaluation.definitions.unset(name, expr): - if not expr.is_atom(): - evaluation.message("Unset", "norep", expr, Symbol(name)) - return SymbolFailed - return Symbol("Null") - - class DownValues(Builtin): """
diff --git a/mathics/builtin/assignments/clear.py b/mathics/builtin/assignments/clear.py new file mode 100644 index 000000000..142f88195 --- /dev/null +++ b/mathics/builtin/assignments/clear.py @@ -0,0 +1,278 @@ +# -*- coding: utf-8 -*- +""" +Clearing Assignments +""" + +from mathics.version import __version__ # noqa used in loading to check consistency. + +from mathics.builtin.base import ( + Builtin, + PostfixOperator, +) +from mathics.core.expression import Expression +from mathics.core.symbols import ( + Symbol, + system_symbols, +) + +from mathics.core.systemsymbols import ( + SymbolFailed, +) + +from mathics.core.atoms import String + +from mathics.builtin.assignments.internals import is_protected + + +class Clear(Builtin): + """ +
+
'Clear[$symb1$, $symb2$, ...]' +
clears all values of the given symbols. The arguments can also be given as strings containing symbol names. +
+ + >> x = 2; + >> Clear[x] + >> x + = x + + >> x = 2; + >> y = 3; + >> Clear["Global`*"] + >> x + = x + >> y + = y + + 'ClearAll' may not be called for 'Protected' symbols. + >> Clear[Sin] + : Symbol Sin is Protected. + The values and rules associated with built-in symbols will not get lost when applying 'Clear' + (after unprotecting them): + >> Unprotect[Sin] + >> Clear[Sin] + >> Sin[Pi] + = 0 + + 'Clear' does not remove attributes, messages, options, and default values associated + with the symbols. Use 'ClearAll' to do so. + >> Attributes[r] = {Flat, Orderless}; + >> Clear["r"] + >> Attributes[r] + = {Flat, Orderless} + """ + + attributes = ("HoldAll",) + + messages = { + "ssym": "`1` is not a symbol or a string.", + } + + allow_locked = True + + def do_clear(self, definition): + definition.ownvalues = [] + definition.downvalues = [] + definition.subvalues = [] + definition.upvalues = [] + definition.formatvalues = {} + definition.nvalues = [] + + def apply(self, symbols, evaluation): + "%(name)s[symbols___]" + if isinstance(symbols, Symbol): + symbols = [symbols] + elif isinstance(symbols, Expression): + symbols = symbols.get_leaves() + elif isinstance(symbols, String): + symbols = [symbols] + else: + symbols = symbols.get_sequence() + + for symbol in symbols: + if isinstance(symbol, Symbol): + names = [symbol.get_name()] + else: + pattern = symbol.get_string_value() + if not pattern: + evaluation.message("Clear", "ssym", symbol) + continue + if pattern[0] == "`": + pattern = evaluation.definitions.get_current_context() + pattern[1:] + + names = evaluation.definitions.get_matching_names(pattern) + for name in names: + attributes = evaluation.definitions.get_attributes(name) + if is_protected(name, evaluation.definitions): + evaluation.message("Clear", "wrsym", Symbol(name)) + continue + if not self.allow_locked and "System`Locked" in attributes: + evaluation.message("Clear", "locked", Symbol(name)) + continue + definition = evaluation.definitions.get_user_definition(name) + self.do_clear(definition) + + return Symbol("Null") + + def apply_all(self, evaluation): + "Clear[System`All]" + evaluation.definitions.set_user_definitions({}) + evaluation.definitions.clear_pymathics_modules() + return + + +class ClearAll(Clear): + """ +
+
'ClearAll[$symb1$, $symb2$, ...]' +
clears all values, attributes, messages and options associated with the given symbols. + The arguments can also be given as strings containing symbol names. +
+ + >> x = 2; + >> ClearAll[x] + >> x + = x + >> Attributes[r] = {Flat, Orderless}; + >> ClearAll[r] + >> Attributes[r] + = {} + + 'ClearAll' may not be called for 'Protected' or 'Locked' symbols. + >> Attributes[lock] = {Locked}; + >> ClearAll[lock] + : Symbol lock is locked. + """ + + allow_locked = False + + def do_clear(self, definition): + super(ClearAll, self).do_clear(definition) + definition.attributes = set() + definition.messages = [] + definition.options = [] + definition.defaultvalues = [] + + def apply_all(self, evaluation): + "ClearAll[System`All]" + evaluation.definitions.set_user_definitions({}) + evaluation.definitions.clear_pymathics_modules() + return + + +class Unset(PostfixOperator): + """ +
+
'Unset[$x$]' +
'$x$=.' +
removes any value belonging to $x$. +
+ >> a = 2 + = 2 + >> a =. + >> a + = a + + Unsetting an already unset or never defined variable will not + change anything: + >> a =. + >> b =. + + 'Unset' can unset particular function values. It will print a message + if no corresponding rule is found. + >> f[x_] =. + : Assignment on f for f[x_] not found. + = $Failed + >> f[x_] := x ^ 2 + >> f[3] + = 9 + >> f[x_] =. + >> f[3] + = f[3] + + You can also unset 'OwnValues', 'DownValues', 'SubValues', and 'UpValues' directly. + This is equivalent to setting them to '{}'. + >> f[x_] = x; f[0] = 1; + >> DownValues[f] =. + >> f[2] + = f[2] + + 'Unset' threads over lists: + >> a = b = 3; + >> {a, {b}} =. + = {Null, {Null}} + + #> x = 2; + #> OwnValues[x] =. + #> x + = x + #> f[a][b] = 3; + #> SubValues[f] =. + #> f[a][b] + = f[a][b] + #> PrimeQ[p] ^= True + = True + #> PrimeQ[p] + = True + #> UpValues[p] =. + #> PrimeQ[p] + = False + + #> a + b ^= 5; + #> a =. + #> a + b + = 5 + #> {UpValues[a], UpValues[b]} =. + = {Null, Null} + #> a + b + = a + b + + #> Unset[Messages[1]] + : First argument in Messages[1] is not a symbol or a string naming a symbol. + = $Failed + """ + + operator = "=." + precedence = 670 + attributes = ("HoldFirst", "Listable", "ReadProtected") + + messages = { + "norep": "Assignment on `2` for `1` not found.", + "usraw": "Cannot unset raw object `1`.", + } + + def apply(self, expr, evaluation): + "Unset[expr_]" + + name = expr.get_head_name() + if name in system_symbols( + "OwnValues", + "DownValues", + "SubValues", + "UpValues", + "NValues", + "Options", + "Messages", + ): + if len(expr.leaves) != 1: + evaluation.message_args(name, len(expr.leaves), 1) + return SymbolFailed + symbol = expr.leaves[0].get_name() + if not symbol: + evaluation.message(name, "fnsym", expr) + return SymbolFailed + if name == "System`Options": + empty = {} + else: + empty = [] + evaluation.definitions.set_values(symbol, name, empty) + return Symbol("Null") + name = expr.get_lookup_name() + if not name: + evaluation.message("Unset", "usraw", expr) + return SymbolFailed + if not evaluation.definitions.unset(name, expr): + if not expr.is_atom(): + evaluation.message("Unset", "norep", expr, Symbol(name)) + return SymbolFailed + return Symbol("Null") diff --git a/mathics/builtin/assignments/internals.py b/mathics/builtin/assignments/internals.py index 6c814a51b..953791850 100644 --- a/mathics/builtin/assignments/internals.py +++ b/mathics/builtin/assignments/internals.py @@ -2,7 +2,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.base import AssignmentException +from mathics.algorithm.parts import walk_parts from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit from mathics.core.expression import Expression from mathics.core.rules import Rule @@ -15,6 +15,13 @@ from mathics.core.systemsymbols import SymbolMachinePrecision +class AssignmentException(Exception): + def __init__(self, lhs, rhs) -> None: + super().__init__(" %s cannot be assigned to %s" % (rhs, lhs)) + self.lhs = lhs + self.rhs = rhs + + def assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset=None): lhs, condition = unroll_conditions(lhs) lhs, rhs = unroll_patterns(lhs, rhs, evaluation) @@ -603,3 +610,82 @@ def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation): raise AssignmentException(lhs, None) return tags, focus + + +class _SetOperator(object): + special_cases = { + "System`OwnValues": process_assign_definition_values, + "System`DownValues": process_assign_definition_values, + "System`SubValues": process_assign_definition_values, + "System`UpValues": process_assign_definition_values, + "System`NValues": process_assign_definition_values, + "System`DefaultValues": process_assign_definition_values, + "System`Messages": process_assign_definition_values, + "System`Attributes": process_assign_attributes, + "System`Options": process_assign_options, + "System`$RandomState": process_assign_random_state, + "System`$Context": process_assign_context, + "System`$ContextPath": process_assign_context_path, + "System`N": process_assign_n, + "System`MessageName": process_assign_messagename, + "System`Default": process_assign_default, + "System`Format": process_assign_format, + } + + def assign_elementary(self, lhs, rhs, evaluation, tags=None, upset=False): + if type(lhs) is Symbol: + name = lhs.name + else: + name = lhs.get_head_name() + lhs._format_cache = None + try: + # Deal with direct assignation to properties of + # the definition object + func = self.special_cases.get(name, None) + if func: + return func(self, lhs, rhs, evaluation, tags, upset) + + return assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset) + except AssignmentException: + return False + + def assign(self, lhs, rhs, evaluation): + lhs._format_cache = None + defs = evaluation.definitions + if lhs.get_head_name() == "System`List": + if not (rhs.get_head_name() == "System`List") or len(lhs.leaves) != len( + rhs.leaves + ): # nopep8 + + evaluation.message(self.get_name(), "shape", lhs, rhs) + return False + else: + result = True + for left, right in zip(lhs.leaves, rhs.leaves): + if not self.assign(left, right, evaluation): + result = False + return result + elif lhs.get_head_name() == "System`Part": + if len(lhs.leaves) < 1: + evaluation.message(self.get_name(), "setp", lhs) + return False + symbol = lhs.leaves[0] + name = symbol.get_name() + if not name: + evaluation.message(self.get_name(), "setps", symbol) + return False + if is_protected(name, defs): + evaluation.message(self.get_name(), "wrsym", symbol) + return False + rule = defs.get_ownvalue(name) + if rule is None: + evaluation.message(self.get_name(), "noval", symbol) + return False + indices = lhs.leaves[1:] + result = walk_parts([rule.replace], indices, evaluation, rhs) + if result: + evaluation.definitions.set_ownvalue(name, result) + else: + return False + else: + return self.assign_elementary(lhs, rhs, evaluation) diff --git a/mathics/builtin/assignments/upvalues.py b/mathics/builtin/assignments/upvalues.py new file mode 100644 index 000000000..a6e9ad9fc --- /dev/null +++ b/mathics/builtin/assignments/upvalues.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +from mathics.version import __version__ # noqa used in loading to check consistency. + +from mathics.builtin.base import BinaryOperator +from mathics.core.symbols import Symbol + +from mathics.core.systemsymbols import SymbolFailed + +from mathics.builtin.assignments.internals import _SetOperator + + +class UpSet(BinaryOperator, _SetOperator): + """ +
+
$f$[$x$] ^= $expression$ +
evaluates $expression$ and assigns it to the value of $f$[$x$], associating the value with $x$. +
+ + 'UpSet' creates an upvalue: + >> a[b] ^= 3; + >> DownValues[a] + = {} + >> UpValues[b] + = {HoldPattern[a[b]] :> 3} + + >> a ^= 3 + : Nonatomic expression expected. + = 3 + + You can use 'UpSet' to specify special values like format values. + However, these values will not be saved in 'UpValues': + >> Format[r] ^= "custom"; + >> r + = custom + >> UpValues[r] + = {} + + #> f[g, a + b, h] ^= 2 + : Tag Plus in f[g, a + b, h] is Protected. + = 2 + #> UpValues[h] + = {HoldPattern[f[g, a + b, h]] :> 2} + """ + + attributes = ("HoldFirst", "SequenceHold") + grouping = "Right" + operator = "^=" + precedence = 40 + + summary_text = ( + "set value and associate the assignment with symbols that occur at level one" + ) + + def apply(self, lhs, rhs, evaluation): + "lhs_ ^= rhs_" + + self.assign_elementary(lhs, rhs, evaluation, upset=True) + return rhs + + +class UpSetDelayed(UpSet): + """ +
+
'UpSetDelayed[$expression$, $value$]' + +
'$expression$ ^:= $value$' +
assigns $expression$ to the value of $f$[$x$] (without evaluating $expression$), associating the value with $x$. +
+ + >> a[b] ^:= x + >> x = 2; + >> a[b] + = 2 + >> UpValues[b] + = {HoldPattern[a[b]] :> x} + + #> f[g, a + b, h] ^:= 2 + : Tag Plus in f[g, a + b, h] is Protected. + #> f[a+b] ^:= 2 + : Tag Plus in f[a + b] is Protected. + = $Failed + """ + + attributes = ("HoldAll", "SequenceHold") + operator = "^:=" + summary_text = "set a delayed value and associate the assignment with symbols that occur at level one" + + def apply(self, lhs, rhs, evaluation): + "lhs_ ^:= rhs_" + + if self.assign_elementary(lhs, rhs, evaluation, upset=True): + return Symbol("Null") + else: + return SymbolFailed diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index f169278bc..63a09a77b 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -70,13 +70,6 @@ def has_option(options, name, evaluation): mathics_to_python = {} -class AssignmentException(Exception): - def __init__(self, lhs, rhs) -> None: - super().__init__(" %s cannot be assigned to %s" % (rhs, lhs)) - self.lhs = lhs - self.rhs = rhs - - class Builtin(object): name: typing.Optional[str] = None context = "" From 04ab8f4f2c64b601e212b89ad6a7d6809a8d10e8 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 16 Oct 2021 21:05:55 -0400 Subject: [PATCH 186/193] Go over summary text for clear ... and top-level assignments --- mathics/builtin/assignments/__init__.py | 4 +++- mathics/builtin/assignments/clear.py | 29 +++++++++++-------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/mathics/builtin/assignments/__init__.py b/mathics/builtin/assignments/__init__.py index be81671be..b590c37d9 100644 --- a/mathics/builtin/assignments/__init__.py +++ b/mathics/builtin/assignments/__init__.py @@ -1,7 +1,9 @@ """ Assignments -Assigments allow you to set variables, indexed variables, structure elements, functions, and general transformations. +Assigments allow you to set or clear variables, indexed variables, structure elements, functions, and general transformations. + +You can also get assignment and documentation information about symbols. """ from mathics.version import __version__ # noqa used in loading to check consistency. diff --git a/mathics/builtin/assignments/clear.py b/mathics/builtin/assignments/clear.py index 142f88195..19119d284 100644 --- a/mathics/builtin/assignments/clear.py +++ b/mathics/builtin/assignments/clear.py @@ -54,21 +54,19 @@ class Clear(Builtin): >> Sin[Pi] = 0 - 'Clear' does not remove attributes, messages, options, and default values associated - with the symbols. Use 'ClearAll' to do so. + 'Clear' does not remove attributes, messages, options, and default values associated with the symbols. Use 'ClearAll' to do so. >> Attributes[r] = {Flat, Orderless}; >> Clear["r"] >> Attributes[r] = {Flat, Orderless} """ + allow_locked = True attributes = ("HoldAll",) - messages = { "ssym": "`1` is not a symbol or a string.", } - - allow_locked = True + summary_text = "clear all values associated with the LHS or symbol" def do_clear(self, definition): definition.ownvalues = [] @@ -124,9 +122,9 @@ def apply_all(self, evaluation): class ClearAll(Clear): """
-
'ClearAll[$symb1$, $symb2$, ...]' -
clears all values, attributes, messages and options associated with the given symbols. - The arguments can also be given as strings containing symbol names. +
'ClearAll[$symb1$, $symb2$, ...]' +
clears all values, attributes, messages and options associated with the given symbols. + The arguments can also be given as strings containing symbol names.
>> x = 2; @@ -145,6 +143,7 @@ class ClearAll(Clear): """ allow_locked = False + summary_text = "clear all values, definitions, messages and defaults for symbols" def do_clear(self, definition): super(ClearAll, self).do_clear(definition) @@ -173,13 +172,11 @@ class Unset(PostfixOperator): >> a = a - Unsetting an already unset or never defined variable will not - change anything: + Unsetting an already unset or never defined variable will not change anything: >> a =. >> b =. - 'Unset' can unset particular function values. It will print a message - if no corresponding rule is found. + 'Unset' can unset particular function values. It will print a message if no corresponding rule is found. >> f[x_] =. : Assignment on f for f[x_] not found. = $Failed @@ -190,8 +187,7 @@ class Unset(PostfixOperator): >> f[3] = f[3] - You can also unset 'OwnValues', 'DownValues', 'SubValues', and 'UpValues' directly. - This is equivalent to setting them to '{}'. + You can also unset 'OwnValues', 'DownValues', 'SubValues', and 'UpValues' directly. This is equivalent to setting them to '{}'. >> f[x_] = x; f[0] = 1; >> DownValues[f] =. >> f[2] @@ -232,14 +228,15 @@ class Unset(PostfixOperator): = $Failed """ - operator = "=." - precedence = 670 attributes = ("HoldFirst", "Listable", "ReadProtected") + operator = "=." messages = { "norep": "Assignment on `2` for `1` not found.", "usraw": "Cannot unset raw object `1`.", } + precedence = 670 + summary_text = "unset a value of the LHS" def apply(self, expr, evaluation): "Unset[expr_]" From ab683959b5851d830adf181d25b7ffc7d3406569 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 16 Oct 2021 21:31:03 -0400 Subject: [PATCH 187/193] Split off Assignment information functions --- mathics/builtin/assignments/assignment.py | 369 +------------------- mathics/builtin/assignments/information.py | 388 +++++++++++++++++++++ 2 files changed, 389 insertions(+), 368 deletions(-) create mode 100644 mathics/builtin/assignments/information.py diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py index d360c2960..14952f313 100644 --- a/mathics/builtin/assignments/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -13,14 +13,12 @@ ) from mathics.core.rules import Rule from mathics.core.expression import Expression -from mathics.core.symbols import Symbol, SymbolNull +from mathics.core.symbols import Symbol from mathics.core.systemsymbols import ( SymbolFailed, ) -from mathics.core.atoms import String - from mathics.core.definitions import PyMathicsLoadException from mathics.builtin.assignments.internals import _SetOperator, get_symbol_values @@ -458,303 +456,6 @@ def format_definition_input(self, symbol, evaluation): return self.format_definition(symbol, evaluation, grid=False) -def _get_usage_string(symbol, evaluation, is_long_form: bool, htmlout=False): - """ - Returns a python string with the documentation associated to a given symbol. - """ - definition = evaluation.definitions.get_definition(symbol.name) - ruleusage = definition.get_values_list("messages") - usagetext = None - import re - - # First look at user definitions: - for rulemsg in ruleusage: - if rulemsg.pattern.expr.leaves[1].__str__() == '"usage"': - usagetext = rulemsg.replace.value - if usagetext is not None: - # Maybe, if htmltout is True, we should convert - # the value to a HTML form... - return usagetext - # Otherwise, look at the pymathics, and builtin docstrings: - builtins = evaluation.definitions.builtin - pymathics = evaluation.definitions.pymathics - bio = pymathics.get(definition.name) - if bio is None: - bio = builtins.get(definition.name) - - if bio is not None: - if not is_long_form and hasattr(bio.builtin.__class__, "summary_text"): - return bio.builtin.__class__.summary_text - from mathics.doc.common_doc import XMLDoc - - docstr = bio.builtin.__class__.__doc__ - title = bio.builtin.__class__.__name__ - if docstr is None: - return None - if htmlout: - usagetext = XMLDoc(docstr, title).html() - else: - usagetext = XMLDoc(docstr, title).text(0) - usagetext = re.sub(r"\$([0-9a-zA-Z]*)\$", r"\1", usagetext) - return usagetext - return None - - -class Information(PrefixOperator): - """ -
-
'Information[$symbol$]' -
Prints information about a $symbol$ -
- 'Information' does not print information for 'ReadProtected' symbols. - 'Information' uses 'InputForm' to format values. - - #> a = 2; - #> Information[a] - | a = 2 - . - = Null - - #> f[x_] := x ^ 2; - #> g[f] ^:= 2; - #> f::usage = "f[x] returns the square of x"; - #> Information[f] - | f[x] returns the square of x - . - . f[x_] = x ^ 2 - . - . g[f] ^= 2 - . - = Null - - """ - - operator = "??" - precedence = 0 - attributes = ("HoldAll", "SequenceHold", "Protect", "ReadProtect") - messages = {"notfound": "Expression `1` is not a symbol"} - options = { - "LongForm": "True", - } - - def format_definition(self, symbol, evaluation, options, grid=True): - "StandardForm,TraditionalForm,OutputForm: Information[symbol_, OptionsPattern[Information]]" - ret = SymbolNull - lines = [] - if isinstance(symbol, String): - evaluation.print_out(symbol) - return ret - if not isinstance(symbol, Symbol): - evaluation.message("Information", "notfound", symbol) - return ret - # Print the "usage" message if available. - is_long_form = self.get_option(options, "LongForm", evaluation).to_python() - usagetext = _get_usage_string(symbol, evaluation, is_long_form) - if usagetext is not None: - lines.append(usagetext) - - if is_long_form: - self.show_definitions(symbol, evaluation, lines) - - if grid: - if lines: - infoshow = Expression( - "Grid", - Expression("List", *(Expression("List", line) for line in lines)), - Expression("Rule", Symbol("ColumnAlignments"), Symbol("Left")), - ) - evaluation.print_out(infoshow) - else: - for line in lines: - evaluation.print_out(Expression("InputForm", line)) - return ret - - # It would be deserable to call here the routine inside Definition, but for some reason it fails... - # Instead, I just copy the code from Definition - - def show_definitions(self, symbol, evaluation, lines): - def print_rule(rule, up=False, lhs=lambda k: k, rhs=lambda r: r): - evaluation.check_stopped() - if isinstance(rule, Rule): - r = rhs( - rule.replace.replace_vars( - { - "System`Definition": Expression( - "HoldForm", Symbol("Definition") - ) - } - ) - ) - lines.append( - Expression( - "HoldForm", - Expression(up and "UpSet" or "Set", lhs(rule.pattern.expr), r), - ) - ) - - name = symbol.get_name() - if not name: - evaluation.message("Definition", "sym", symbol, 1) - return - attributes = evaluation.definitions.get_attributes(name) - definition = evaluation.definitions.get_user_definition(name, create=False) - all = evaluation.definitions.get_definition(name) - if attributes: - attributes = list(attributes) - attributes.sort() - lines.append( - Expression( - "HoldForm", - Expression( - "Set", - Expression("Attributes", symbol), - Expression( - "List", *(Symbol(attribute) for attribute in attributes) - ), - ), - ) - ) - - if definition is not None and "System`ReadProtected" not in attributes: - for rule in definition.ownvalues: - print_rule(rule) - for rule in definition.downvalues: - print_rule(rule) - for rule in definition.subvalues: - print_rule(rule) - for rule in definition.upvalues: - print_rule(rule, up=True) - for rule in definition.nvalues: - print_rule(rule) - formats = sorted(definition.formatvalues.items()) - for format, rules in formats: - for rule in rules: - - def lhs(expr): - return Expression("Format", expr, Symbol(format)) - - def rhs(expr): - if expr.has_form("Infix", None): - expr = Expression( - Expression("HoldForm", expr.head), *expr.leaves - ) - return Expression("InputForm", expr) - - print_rule(rule, lhs=lhs, rhs=rhs) - for rule in all.defaultvalues: - print_rule(rule) - if all.options: - options = sorted(all.options.items()) - lines.append( - Expression( - "HoldForm", - Expression( - "Set", - Expression("Options", symbol), - Expression( - "List", - *( - Expression("Rule", Symbol(name), value) - for name, value in options - ) - ), - ), - ) - ) - return - - def format_definition_input(self, symbol, evaluation, options): - "InputForm: Information[symbol_, OptionsPattern[Information]]" - self.format_definition(symbol, evaluation, options, grid=False) - ret = SymbolNull - return ret - - -class DownValues(Builtin): - """ -
-
'DownValues[$symbol$]' -
gives the list of downvalues associated with $symbol$. -
- - 'DownValues' uses 'HoldPattern' and 'RuleDelayed' to protect the - downvalues from being evaluated. Moreover, it has attribute - 'HoldAll' to get the specified symbol instead of its value. - - >> f[x_] := x ^ 2 - >> DownValues[f] - = {HoldPattern[f[x_]] :> x ^ 2} - - Mathics will sort the rules you assign to a symbol according to - their specificity. If it cannot decide which rule is more special, - the newer one will get higher precedence. - >> f[x_Integer] := 2 - >> f[x_Real] := 3 - >> DownValues[f] - = {HoldPattern[f[x_Real]] :> 3, HoldPattern[f[x_Integer]] :> 2, HoldPattern[f[x_]] :> x ^ 2} - >> f[3] - = 2 - >> f[3.] - = 3 - >> f[a] - = a ^ 2 - - The default order of patterns can be computed using 'Sort' with - 'PatternsOrderedQ': - >> Sort[{x_, x_Integer}, PatternsOrderedQ] - = {x_Integer, x_} - - By assigning values to 'DownValues', you can override the default - ordering: - >> DownValues[g] := {g[x_] :> x ^ 2, g[x_Integer] :> x} - >> g[2] - = 4 - - Fibonacci numbers: - >> DownValues[fib] := {fib[0] -> 0, fib[1] -> 1, fib[n_] :> fib[n - 1] + fib[n - 2]} - >> fib[5] - = 5 - """ - - attributes = ("HoldAll",) - - def apply(self, symbol, evaluation): - "DownValues[symbol_]" - - return get_symbol_values(symbol, "DownValues", "down", evaluation) - - -class OwnValues(Builtin): - """ -
-
'OwnValues[$symbol$]' -
gives the list of ownvalues associated with $symbol$. -
- - >> x = 3; - >> x = 2; - >> OwnValues[x] - = {HoldPattern[x] :> 2} - >> x := y - >> OwnValues[x] - = {HoldPattern[x] :> y} - >> y = 5; - >> OwnValues[x] - = {HoldPattern[x] :> y} - >> Hold[x] /. OwnValues[x] - = Hold[y] - >> Hold[x] /. OwnValues[x] // ReleaseHold - = 5 - """ - - attributes = ("HoldAll",) - - def apply(self, symbol, evaluation): - "OwnValues[symbol_]" - - return get_symbol_values(symbol, "OwnValues", "own", evaluation) - - class SubValues(Builtin): """
@@ -780,74 +481,6 @@ def apply(self, symbol, evaluation): return get_symbol_values(symbol, "SubValues", "sub", evaluation) -class UpValues(Builtin): - """ -
-
'UpValues[$symbol$]' -
gives the list of upvalues associated with $symbol$. -
- - >> a + b ^= 2 - = 2 - >> UpValues[a] - = {HoldPattern[a + b] :> 2} - >> UpValues[b] - = {HoldPattern[a + b] :> 2} - - You can assign values to 'UpValues': - >> UpValues[pi] := {Sin[pi] :> 0} - >> Sin[pi] - = 0 - """ - - attributes = ("HoldAll",) - - def apply(self, symbol, evaluation): - "UpValues[symbol_]" - - return get_symbol_values(symbol, "UpValues", "up", evaluation) - - -class NValues(Builtin): - """ -
-
'NValues[$symbol$]' -
gives the list of numerical values associated with $symbol$. -
- - >> NValues[a] - = {} - >> N[a] = 3; - >> NValues[a] - = {HoldPattern[N[a, MachinePrecision]] :> 3} - - You can assign values to 'NValues': - >> NValues[b] := {N[b, MachinePrecision] :> 2} - >> N[b] - = 2. - Be sure to use 'SetDelayed', otherwise the left-hand side of the transformation rule will be evaluated immediately, - causing the head of 'N' to get lost. Furthermore, you have to include the precision in the rules; 'MachinePrecision' - will not be inserted automatically: - >> NValues[c] := {N[c] :> 3} - >> N[c] - = c - - Mathics will gracefully assign any list of rules to 'NValues'; however, inappropriate rules will never be used: - >> NValues[d] = {foo -> bar}; - >> NValues[d] - = {HoldPattern[foo] :> bar} - >> N[d] - = d - """ - - attributes = ("HoldAll",) - - def apply(self, symbol, evaluation): - "NValues[symbol_]" - - return get_symbol_values(symbol, "NValues", "n", evaluation) - - class Messages(Builtin): """
diff --git a/mathics/builtin/assignments/information.py b/mathics/builtin/assignments/information.py new file mode 100644 index 000000000..ebc71e9b1 --- /dev/null +++ b/mathics/builtin/assignments/information.py @@ -0,0 +1,388 @@ +# -*- coding: utf-8 -*- +""" +Information about Assignments +""" + +from mathics.version import __version__ # noqa used in loading to check consistency. + +from mathics.builtin.base import Builtin, PrefixOperator + +from mathics.core.rules import Rule +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol, SymbolNull + +from mathics.core.atoms import String + +from mathics.builtin.assignments.internals import get_symbol_values + + +def _get_usage_string(symbol, evaluation, is_long_form: bool, htmlout=False): + """ + Returns a python string with the documentation associated to a given symbol. + """ + definition = evaluation.definitions.get_definition(symbol.name) + ruleusage = definition.get_values_list("messages") + usagetext = None + import re + + # First look at user definitions: + for rulemsg in ruleusage: + if rulemsg.pattern.expr.leaves[1].__str__() == '"usage"': + usagetext = rulemsg.replace.value + if usagetext is not None: + # Maybe, if htmltout is True, we should convert + # the value to a HTML form... + return usagetext + # Otherwise, look at the pymathics, and builtin docstrings: + builtins = evaluation.definitions.builtin + pymathics = evaluation.definitions.pymathics + bio = pymathics.get(definition.name) + if bio is None: + bio = builtins.get(definition.name) + + if bio is not None: + if not is_long_form and hasattr(bio.builtin.__class__, "summary_text"): + return bio.builtin.__class__.summary_text + from mathics.doc.common_doc import XMLDoc + + docstr = bio.builtin.__class__.__doc__ + title = bio.builtin.__class__.__name__ + if docstr is None: + return None + if htmlout: + usagetext = XMLDoc(docstr, title).html() + else: + usagetext = XMLDoc(docstr, title).text(0) + usagetext = re.sub(r"\$([0-9a-zA-Z]*)\$", r"\1", usagetext) + return usagetext + return None + + +class DownValues(Builtin): + """ +
+
'DownValues[$symbol$]' +
gives the list of downvalues associated with $symbol$. +
+ + 'DownValues' uses 'HoldPattern' and 'RuleDelayed' to protect the + downvalues from being evaluated. Moreover, it has attribute + 'HoldAll' to get the specified symbol instead of its value. + + >> f[x_] := x ^ 2 + >> DownValues[f] + = {HoldPattern[f[x_]] :> x ^ 2} + + Mathics will sort the rules you assign to a symbol according to + their specificity. If it cannot decide which rule is more special, + the newer one will get higher precedence. + >> f[x_Integer] := 2 + >> f[x_Real] := 3 + >> DownValues[f] + = {HoldPattern[f[x_Real]] :> 3, HoldPattern[f[x_Integer]] :> 2, HoldPattern[f[x_]] :> x ^ 2} + >> f[3] + = 2 + >> f[3.] + = 3 + >> f[a] + = a ^ 2 + + The default order of patterns can be computed using 'Sort' with + 'PatternsOrderedQ': + >> Sort[{x_, x_Integer}, PatternsOrderedQ] + = {x_Integer, x_} + + By assigning values to 'DownValues', you can override the default + ordering: + >> DownValues[g] := {g[x_] :> x ^ 2, g[x_Integer] :> x} + >> g[2] + = 4 + + Fibonacci numbers: + >> DownValues[fib] := {fib[0] -> 0, fib[1] -> 1, fib[n_] :> fib[n - 1] + fib[n - 2]} + >> fib[5] + = 5 + """ + + attributes = ("HoldAll",) + summary_text = "gives a list of transformation rules corresponding to all downvalues defined for a symbol" + + def apply(self, symbol, evaluation): + "DownValues[symbol_]" + + return get_symbol_values(symbol, "DownValues", "down", evaluation) + + +class Information(PrefixOperator): + """ +
+
'Information[$symbol$]' +
Prints information about a $symbol$ +
+ 'Information' does not print information for 'ReadProtected' symbols. + 'Information' uses 'InputForm' to format values. + + #> a = 2; + #> Information[a] + | a = 2 + . + = Null + + #> f[x_] := x ^ 2; + #> g[f] ^:= 2; + #> f::usage = "f[x] returns the square of x"; + #> Information[f] + | f[x] returns the square of x + . + . f[x_] = x ^ 2 + . + . g[f] ^= 2 + . + = Null + + """ + + attributes = ("HoldAll", "SequenceHold", "Protect", "ReadProtect") + messages = {"notfound": "Expression `1` is not a symbol"} + operator = "??" + options = { + "LongForm": "True", + } + precedence = 0 + summary_text = "get information about all assignments for a symbol" + + def format_definition(self, symbol, evaluation, options, grid=True): + "StandardForm,TraditionalForm,OutputForm: Information[symbol_, OptionsPattern[Information]]" + ret = SymbolNull + lines = [] + if isinstance(symbol, String): + evaluation.print_out(symbol) + return ret + if not isinstance(symbol, Symbol): + evaluation.message("Information", "notfound", symbol) + return ret + # Print the "usage" message if available. + is_long_form = self.get_option(options, "LongForm", evaluation).to_python() + usagetext = _get_usage_string(symbol, evaluation, is_long_form) + if usagetext is not None: + lines.append(usagetext) + + if is_long_form: + self.show_definitions(symbol, evaluation, lines) + + if grid: + if lines: + infoshow = Expression( + "Grid", + Expression("List", *(Expression("List", line) for line in lines)), + Expression("Rule", Symbol("ColumnAlignments"), Symbol("Left")), + ) + evaluation.print_out(infoshow) + else: + for line in lines: + evaluation.print_out(Expression("InputForm", line)) + return ret + + # It would be deserable to call here the routine inside Definition, but for some reason it fails... + # Instead, I just copy the code from Definition + + def show_definitions(self, symbol, evaluation, lines): + def print_rule(rule, up=False, lhs=lambda k: k, rhs=lambda r: r): + evaluation.check_stopped() + if isinstance(rule, Rule): + r = rhs( + rule.replace.replace_vars( + { + "System`Definition": Expression( + "HoldForm", Symbol("Definition") + ) + } + ) + ) + lines.append( + Expression( + "HoldForm", + Expression(up and "UpSet" or "Set", lhs(rule.pattern.expr), r), + ) + ) + + name = symbol.get_name() + if not name: + evaluation.message("Definition", "sym", symbol, 1) + return + attributes = evaluation.definitions.get_attributes(name) + definition = evaluation.definitions.get_user_definition(name, create=False) + all = evaluation.definitions.get_definition(name) + if attributes: + attributes = list(attributes) + attributes.sort() + lines.append( + Expression( + "HoldForm", + Expression( + "Set", + Expression("Attributes", symbol), + Expression( + "List", *(Symbol(attribute) for attribute in attributes) + ), + ), + ) + ) + + if definition is not None and "System`ReadProtected" not in attributes: + for rule in definition.ownvalues: + print_rule(rule) + for rule in definition.downvalues: + print_rule(rule) + for rule in definition.subvalues: + print_rule(rule) + for rule in definition.upvalues: + print_rule(rule, up=True) + for rule in definition.nvalues: + print_rule(rule) + formats = sorted(definition.formatvalues.items()) + for format, rules in formats: + for rule in rules: + + def lhs(expr): + return Expression("Format", expr, Symbol(format)) + + def rhs(expr): + if expr.has_form("Infix", None): + expr = Expression( + Expression("HoldForm", expr.head), *expr.leaves + ) + return Expression("InputForm", expr) + + print_rule(rule, lhs=lhs, rhs=rhs) + for rule in all.defaultvalues: + print_rule(rule) + if all.options: + options = sorted(all.options.items()) + lines.append( + Expression( + "HoldForm", + Expression( + "Set", + Expression("Options", symbol), + Expression( + "List", + *( + Expression("Rule", Symbol(name), value) + for name, value in options + ) + ), + ), + ) + ) + return + + def format_definition_input(self, symbol, evaluation, options): + "InputForm: Information[symbol_, OptionsPattern[Information]]" + self.format_definition(symbol, evaluation, options, grid=False) + ret = SymbolNull + return ret + + +class NValues(Builtin): + """ +
+
'NValues[$symbol$]' +
gives the list of numerical values associated with $symbol$. + + Note: this function is in Mathematica 5 but has been removed from current Mathematica. +
+ + >> NValues[a] + = {} + >> N[a] = 3; + >> NValues[a] + = {HoldPattern[N[a, MachinePrecision]] :> 3} + + You can assign values to 'NValues': + >> NValues[b] := {N[b, MachinePrecision] :> 2} + >> N[b] + = 2. + Be sure to use 'SetDelayed', otherwise the left-hand side of the transformation rule will be evaluated immediately, + causing the head of 'N' to get lost. Furthermore, you have to include the precision in the rules; 'MachinePrecision' + will not be inserted automatically: + >> NValues[c] := {N[c] :> 3} + >> N[c] + = c + + Mathics will gracefully assign any list of rules to 'NValues'; however, inappropriate rules will never be used: + >> NValues[d] = {foo -> bar}; + >> NValues[d] + = {HoldPattern[foo] :> bar} + >> N[d] + = d + """ + + attributes = ("HoldAll",) + summary_text = "gives the list of numerical values associated with a symbol" + + def apply(self, symbol, evaluation): + "NValues[symbol_]" + + return get_symbol_values(symbol, "NValues", "n", evaluation) + + +class OwnValues(Builtin): + """ +
+
'OwnValues[$symbol$]' +
gives the list of ownvalue associated with $symbol$. +
+ + >> x = 3; + >> x = 2; + >> OwnValues[x] + = {HoldPattern[x] :> 2} + >> x := y + >> OwnValues[x] + = {HoldPattern[x] :> y} + >> y = 5; + >> OwnValues[x] + = {HoldPattern[x] :> y} + >> Hold[x] /. OwnValues[x] + = Hold[y] + >> Hold[x] /. OwnValues[x] // ReleaseHold + = 5 + """ + + attributes = ("HoldAll",) + summary_text = "gives the rule corresponding to any ownvalue defined for a symbol" + + def apply(self, symbol, evaluation): + "OwnValues[symbol_]" + + return get_symbol_values(symbol, "OwnValues", "own", evaluation) + + +class UpValues(Builtin): + """ +
+
'UpValues[$symbol$]' +
gives the list of transformation rules corresponding to upvalues define with $symbol$. +
+ + >> a + b ^= 2 + = 2 + >> UpValues[a] + = {HoldPattern[a + b] :> 2} + >> UpValues[b] + = {HoldPattern[a + b] :> 2} + + You can assign values to 'UpValues': + >> UpValues[pi] := {Sin[pi] :> 0} + >> Sin[pi] + = 0 + """ + + attributes = ("HoldAll",) + summary_text = "gives list of transformation rules corresponding to upvalues defined for a symbol" + + def apply(self, symbol, evaluation): + "UpValues[symbol_]" + + return get_symbol_values(symbol, "UpValues", "up", evaluation) From e6741e374ac3f532b290e833c454a0b9ada8b99f Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 16 Oct 2021 22:06:46 -0400 Subject: [PATCH 188/193] Split off assignment plus binary operator Also add summary text for these --- .../builtin/assignments/assign_binaryop.py | 248 ++++++++++++++++++ mathics/builtin/assignments/assignment.py | 213 +-------------- 2 files changed, 249 insertions(+), 212 deletions(-) create mode 100644 mathics/builtin/assignments/assign_binaryop.py diff --git a/mathics/builtin/assignments/assign_binaryop.py b/mathics/builtin/assignments/assign_binaryop.py new file mode 100644 index 000000000..63499e9c5 --- /dev/null +++ b/mathics/builtin/assignments/assign_binaryop.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +""" +Assignmment plus binary operator + +There are a number operators and functions that combine assignment with some sort of binary operator. + +Sometimes a value is returned before the assignment occurs. When there is an operator for this, the operator is a prefix operator and the function name starts with 'Pre'. + +Sometimes the binary operation occurs first, and then the assignment occurs. When there is an operator for this, the operator is a postfix operator. + +Infix operators combined with assignment end in 'By', 'From', or 'To'. + +""" + +from mathics.version import __version__ # noqa used in loading to check consistency. + +from mathics.builtin.base import ( + BinaryOperator, + PostfixOperator, + PrefixOperator, +) + + +class AddTo(BinaryOperator): + """ +
+
'AddTo[$x$, $dx$]'
+ +
'$x$ += $dx$'
+
is equivalent to '$x$ = $x$ + $dx$'. +
+ + >> a = 10; + >> a += 2 + = 12 + >> a + = 12 + """ + + attributes = ("HoldFirst",) + grouping = "Right" + operator = "+=" + precedence = 100 + + rules = { + "x_ += dx_": "x = x + dx", + } + summary_text = "adds a value and assignes that returning the new value" + + +class Decrement(PostfixOperator): + """ +
+
'Decrement[$x$]'
+ +
'$x$--'
+
decrements $x$ by 1, returning the original value of $x$. +
+ + >> a = 5; + X> a-- + = 5 + X> a + = 4 + """ + + operator = "--" + precedence = 660 + attributes = ("HoldFirst", "ReadProtected") + + rules = { + "x_--": "Module[{t=x}, x = x - 1; t]", + } + + summary_text = ( + "decreases the value by one and assigns that returning the original value" + ) + + +class DivideBy(BinaryOperator): + """ +
+
'DivideBy[$x$, $dx$]'
+ +
'$x$ /= $dx$'
+
is equivalent to '$x$ = $x$ / $dx$'. +
+ + >> a = 10; + >> a /= 2 + = 5 + >> a + = 5 + """ + + attributes = ("HoldFirst",) + grouping = "Right" + operator = "/=" + precedence = 100 + + rules = { + "x_ /= dx_": "x = x / dx", + } + summary_text = "divides a value and assigns that returning the new value" + + +class Increment(PostfixOperator): + """ +
+
'Increment[$x$]'
+ +
'$x$++'
+
increments $x$ by 1, returning the original value of $x$. +
+ + >> a = 2; + >> a++ + = 2 + >> a + = 3 + Grouping of 'Increment', 'PreIncrement' and 'Plus': + >> ++++a+++++2//Hold//FullForm + = Hold[Plus[PreIncrement[PreIncrement[Increment[Increment[a]]]], 2]] + """ + + operator = "++" + precedence = 660 + attributes = ("HoldFirst", "ReadProtected") + + rules = { + "x_++": ( + "Module[{Internal`IncrementTemporary = x}," + " x = x + 1;" + " Internal`IncrementTemporary" + "]" + ), + } + + summary_text = ( + "increases the value by one and assigns that returning the original value" + ) + + +class PreIncrement(PrefixOperator): + """ +
+
'PreIncrement[$x$]'
+
'++$x$'
+
increments $x$ by 1, returning the new value of $x$. +
+ + '++$a$' is equivalent to '$a$ = $a$ + 1': + >> a = 2; + >> ++a + = 3 + >> a + = 3 + """ + + attributes = ("HoldFirst", "ReadProtected") + operator = "++" + precedence = 660 + + rules = { + "++x_": "x = x + 1", + } + + summary_text = "increases the value by one and assigns that returning the new value" + + +class PreDecrement(PrefixOperator): + """ +
+
'PreDecrement[$x$]'
+ +
'--$x$'
+
decrements $x$ by 1, returning the new value of $x$. +
+ + '--$a$' is equivalent to '$a$ = $a$ - 1': + >> a = 2; + >> --a + = 1 + >> a + = 1 + """ + + operator = "--" + precedence = 660 + attributes = ("HoldFirst", "ReadProtected") + + rules = { + "--x_": "x = x - 1", + } + summary_text = "decreases the value by one and assigns that returning the new value" + + +class SubtractFrom(BinaryOperator): + """ +
+
'SubtractFrom[$x$, $dx$]'
+
'$x$ -= $dx$'
+
is equivalent to '$x$ = $x$ - $dx$'. +
+ + >> a = 10; + >> a -= 2 + = 8 + >> a + = 8 + """ + + attributes = ("HoldFirst",) + grouping = "Right" + operator = "-=" + precedence = 100 + + rules = { + "x_ -= dx_": "x = x - dx", + } + summary_text = "subtracts a value and assins that returning the new value" + + +class TimesBy(BinaryOperator): + """ +
+
'TimesBy[$x$, $dx$]'
+ +
'$x$ *= $dx$'
+
is equivalent to '$x$ = $x$ * $dx$'. +
+ + >> a = 10; + >> a *= 2 + = 20 + >> a + = 20 + """ + + operator = "*=" + precedence = 100 + attributes = ("HoldFirst",) + grouping = "Right" + + rules = { + "x_ *= dx_": "x = x * dx", + } + summary_text = "multiplies a value and assigns that returning the new value" diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py index 14952f313..25da4b113 100644 --- a/mathics/builtin/assignments/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -5,12 +5,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. -from mathics.builtin.base import ( - Builtin, - BinaryOperator, - PostfixOperator, - PrefixOperator, -) +from mathics.builtin.base import Builtin, BinaryOperator from mathics.core.rules import Rule from mathics.core.expression import Expression from mathics.core.symbols import Symbol @@ -538,212 +533,6 @@ def apply(self, symbol, evaluation): return get_symbol_values(symbol, "System`DefaultValues", "default", evaluation) -class AddTo(BinaryOperator): - """ -
-
'AddTo[$x$, $dx$]'
-
'$x$ += $dx$'
-
is equivalent to '$x$ = $x$ + $dx$'. -
- - >> a = 10; - >> a += 2 - = 12 - >> a - = 12 - """ - - operator = "+=" - precedence = 100 - attributes = ("HoldFirst",) - grouping = "Right" - - rules = { - "x_ += dx_": "x = x + dx", - } - - -class SubtractFrom(BinaryOperator): - """ -
-
'SubtractFrom[$x$, $dx$]'
-
'$x$ -= $dx$'
-
is equivalent to '$x$ = $x$ - $dx$'. -
- - >> a = 10; - >> a -= 2 - = 8 - >> a - = 8 - """ - - operator = "-=" - precedence = 100 - attributes = ("HoldFirst",) - grouping = "Right" - - rules = { - "x_ -= dx_": "x = x - dx", - } - - -class TimesBy(BinaryOperator): - """ -
-
'TimesBy[$x$, $dx$]'
-
'$x$ *= $dx$'
-
is equivalent to '$x$ = $x$ * $dx$'. -
- - >> a = 10; - >> a *= 2 - = 20 - >> a - = 20 - """ - - operator = "*=" - precedence = 100 - attributes = ("HoldFirst",) - grouping = "Right" - - rules = { - "x_ *= dx_": "x = x * dx", - } - - -class DivideBy(BinaryOperator): - """ -
-
'DivideBy[$x$, $dx$]'
-
'$x$ /= $dx$'
-
is equivalent to '$x$ = $x$ / $dx$'. -
- - >> a = 10; - >> a /= 2 - = 5 - >> a - = 5 - """ - - operator = "/=" - precedence = 100 - attributes = ("HoldFirst",) - grouping = "Right" - - rules = { - "x_ /= dx_": "x = x / dx", - } - - -class Increment(PostfixOperator): - """ -
-
'Increment[$x$]'
-
'$x$++'
-
increments $x$ by 1, returning the original value of $x$. -
- - >> a = 2; - >> a++ - = 2 - >> a - = 3 - Grouping of 'Increment', 'PreIncrement' and 'Plus': - >> ++++a+++++2//Hold//FullForm - = Hold[Plus[PreIncrement[PreIncrement[Increment[Increment[a]]]], 2]] - """ - - operator = "++" - precedence = 660 - attributes = ("HoldFirst", "ReadProtected") - - rules = { - "x_++": ( - "Module[{Internal`IncrementTemporary = x}," - " x = x + 1;" - " Internal`IncrementTemporary" - "]" - ), - } - - -class PreIncrement(PrefixOperator): - """ -
-
'PreIncrement[$x$]'
-
'++$x$'
-
increments $x$ by 1, returning the new value of $x$. -
- - '++$a$' is equivalent to '$a$ = $a$ + 1': - >> a = 2; - >> ++a - = 3 - >> a - = 3 - """ - - operator = "++" - precedence = 660 - attributes = ("HoldFirst", "ReadProtected") - - rules = { - "++x_": "x = x + 1", - } - - -class Decrement(PostfixOperator): - """ -
-
'Decrement[$x$]'
-
'$x$--'
-
decrements $x$ by 1, returning the original value of $x$. -
- - >> a = 5; - X> a-- - = 5 - X> a - = 4 - """ - - operator = "--" - precedence = 660 - attributes = ("HoldFirst", "ReadProtected") - - rules = { - "x_--": "Module[{t=x}, x = x - 1; t]", - } - - -class PreDecrement(PrefixOperator): - """ -
-
'PreDecrement[$x$]'
-
'--$x$'
-
decrements $x$ by 1, returning the new value of $x$. -
- - '--$a$' is equivalent to '$a$ = $a$ - 1': - >> a = 2; - >> --a - = 1 - >> a - = 1 - """ - - operator = "--" - precedence = 660 - attributes = ("HoldFirst", "ReadProtected") - - rules = { - "--x_": "x = x - 1", - } - - class LoadModule(Builtin): """
From eeab2850e6937d34592e205577f9c1463fa7614e Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 16 Oct 2021 22:53:21 -0400 Subject: [PATCH 189/193] Split out Types of Values in Assignments --- mathics/builtin/assignments/assignment.py | 321 +-------------------- mathics/builtin/assignments/information.py | 267 ++++++++++++++--- mathics/builtin/assignments/types.py | 146 ++++++++++ 3 files changed, 384 insertions(+), 350 deletions(-) create mode 100644 mathics/builtin/assignments/types.py diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py index 25da4b113..cc9d4c6e1 100644 --- a/mathics/builtin/assignments/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -23,6 +23,7 @@ class Set(BinaryOperator, _SetOperator): """
'Set[$expr$, $value$]' +
$expr$ = $value$
evaluates $value$ and assigns it to $expr$. @@ -167,10 +168,10 @@ def apply(self, lhs, rhs, evaluation): class TagSet(Builtin, _SetOperator): """
-
'TagSet[$f$, $expr$, $value$]' -
'$f$ /: $expr$ = $value$' -
assigns $value$ to $expr$, associating the corresponding - rule with the symbol $f$. +
'TagSet[$f$, $expr$, $value$]' + +
'$f$ /: $expr$ = $value$' +
assigns $value$ to $expr$, associating the corresponding assignment with the symbol $f$.
Create an upvalue without using 'UpSet': @@ -196,6 +197,7 @@ class TagSet(Builtin, _SetOperator): messages = { "tagnfd": "Tag `1` not found or too deep for an assigned rule.", } + summary_text = "assigns a value to an expression, associating the corresponding assignment with the a symbol." def apply(self, f, lhs, rhs, evaluation): "f_ /: lhs_ = rhs_" @@ -213,13 +215,15 @@ def apply(self, f, lhs, rhs, evaluation): class TagSetDelayed(TagSet): """
-
'TagSetDelayed[$f$, $expr$, $value$]' -
'$f$ /: $expr$ := $value$' -
is the delayed version of 'TagSet'. +
'TagSetDelayed[$f$, $expr$, $value$]' + +
'$f$ /: $expr$ := $value$' +
is the delayed version of 'TagSet'.
""" attributes = ("HoldAll", "SequenceHold") + summary_text = "assigns a delayed value to an expression, associating the corresponding assignment with the a symbol." def apply(self, f, lhs, rhs, evaluation): "f_ /: lhs_ := rhs_" @@ -235,309 +239,12 @@ def apply(self, f, lhs, rhs, evaluation): return SymbolFailed -class Definition(Builtin): - """ -
-
'Definition[$symbol$]' -
prints as the user-defined values and rules associated with $symbol$. -
- - 'Definition' does not print information for 'ReadProtected' symbols. - 'Definition' uses 'InputForm' to format values. - - >> a = 2; - >> Definition[a] - = a = 2 - - >> f[x_] := x ^ 2 - >> g[f] ^:= 2 - >> Definition[f] - = f[x_] = x ^ 2 - . - . g[f] ^= 2 - - Definition of a rather evolved (though meaningless) symbol: - >> Attributes[r] := {Orderless} - >> Format[r[args___]] := Infix[{args}, "~"] - >> N[r] := 3.5 - >> Default[r, 1] := 2 - >> r::msg := "My message" - >> Options[r] := {Opt -> 3} - >> r[arg_., OptionsPattern[r]] := {arg, OptionValue[Opt]} - - Some usage: - >> r[z, x, y] - = x ~ y ~ z - >> N[r] - = 3.5 - >> r[] - = {2, 3} - >> r[5, Opt->7] - = {5, 7} - - Its definition: - >> Definition[r] - = Attributes[r] = {Orderless} - . - . arg_. ~ OptionsPattern[r] = {arg, OptionValue[Opt]} - . - . N[r, MachinePrecision] = 3.5 - . - . Format[args___, MathMLForm] = Infix[{args}, "~"] - . - . Format[args___, OutputForm] = Infix[{args}, "~"] - . - . Format[args___, StandardForm] = Infix[{args}, "~"] - . - . Format[args___, TeXForm] = Infix[{args}, "~"] - . - . Format[args___, TraditionalForm] = Infix[{args}, "~"] - . - . Default[r, 1] = 2 - . - . Options[r] = {Opt -> 3} - - For 'ReadProtected' symbols, 'Definition' just prints attributes, default values and options: - >> SetAttributes[r, ReadProtected] - >> Definition[r] - = Attributes[r] = {Orderless, ReadProtected} - . - . Default[r, 1] = 2 - . - . Options[r] = {Opt -> 3} - This is the same for built-in symbols: - >> Definition[Plus] - = Attributes[Plus] = {Flat, Listable, NumericFunction, OneIdentity, Orderless, Protected} - . - . Default[Plus] = 0 - >> Definition[Level] - = Attributes[Level] = {Protected} - . - . Options[Level] = {Heads -> False} - - 'ReadProtected' can be removed, unless the symbol is locked: - >> ClearAttributes[r, ReadProtected] - 'Clear' clears values: - >> Clear[r] - >> Definition[r] - = Attributes[r] = {Orderless} - . - . Default[r, 1] = 2 - . - . Options[r] = {Opt -> 3} - 'ClearAll' clears everything: - >> ClearAll[r] - >> Definition[r] - = Null - - If a symbol is not defined at all, 'Null' is printed: - >> Definition[x] - = Null - """ - - attributes = ("HoldAll",) - precedence = 670 - - def format_definition(self, symbol, evaluation, grid=True): - "StandardForm,TraditionalForm,OutputForm: Definition[symbol_]" - - lines = [] - - def print_rule(rule, up=False, lhs=lambda k: k, rhs=lambda r: r): - evaluation.check_stopped() - if isinstance(rule, Rule): - r = rhs( - rule.replace.replace_vars( - { - "System`Definition": Expression( - "HoldForm", Symbol("Definition") - ) - }, - evaluation, - ) - ) - lines.append( - Expression( - "HoldForm", - Expression(up and "UpSet" or "Set", lhs(rule.pattern.expr), r), - ) - ) - - name = symbol.get_name() - if not name: - evaluation.message("Definition", "sym", symbol, 1) - return - attributes = evaluation.definitions.get_attributes(name) - definition = evaluation.definitions.get_user_definition(name, create=False) - all = evaluation.definitions.get_definition(name) - if attributes: - attributes = list(attributes) - attributes.sort() - lines.append( - Expression( - "HoldForm", - Expression( - "Set", - Expression("Attributes", symbol), - Expression( - "List", *(Symbol(attribute) for attribute in attributes) - ), - ), - ) - ) - - if definition is not None and "System`ReadProtected" not in attributes: - for rule in definition.ownvalues: - print_rule(rule) - for rule in definition.downvalues: - print_rule(rule) - for rule in definition.subvalues: - print_rule(rule) - for rule in definition.upvalues: - print_rule(rule, up=True) - for rule in definition.nvalues: - print_rule(rule) - formats = sorted(definition.formatvalues.items()) - for format, rules in formats: - for rule in rules: - - def lhs(expr): - return Expression("Format", expr, Symbol(format)) - - def rhs(expr): - if expr.has_form("Infix", None): - expr = Expression( - Expression("HoldForm", expr.head), *expr.leaves - ) - return Expression("InputForm", expr) - - print_rule(rule, lhs=lhs, rhs=rhs) - for rule in all.defaultvalues: - print_rule(rule) - if all.options: - options = sorted(all.options.items()) - lines.append( - Expression( - "HoldForm", - Expression( - "Set", - Expression("Options", symbol), - Expression( - "List", - *( - Expression("Rule", Symbol(name), value) - for name, value in options - ) - ), - ), - ) - ) - if grid: - if lines: - return Expression( - "Grid", - Expression("List", *(Expression("List", line) for line in lines)), - Expression("Rule", Symbol("ColumnAlignments"), Symbol("Left")), - ) - else: - return Symbol("Null") - else: - for line in lines: - evaluation.print_out(Expression("InputForm", line)) - return Symbol("Null") - - def format_definition_input(self, symbol, evaluation): - "InputForm: Definition[symbol_]" - return self.format_definition(symbol, evaluation, grid=False) - - -class SubValues(Builtin): - """ -
-
'SubValues[$symbol$]' -
gives the list of subvalues associated with $symbol$. -
- - >> f[1][x_] := x - >> f[2][x_] := x ^ 2 - >> SubValues[f] - = {HoldPattern[f[2][x_]] :> x ^ 2, HoldPattern[f[1][x_]] :> x} - >> Definition[f] - = f[2][x_] = x ^ 2 - . - . f[1][x_] = x - """ - - attributes = ("HoldAll",) - - def apply(self, symbol, evaluation): - "SubValues[symbol_]" - - return get_symbol_values(symbol, "SubValues", "sub", evaluation) - - -class Messages(Builtin): - """ -
-
'Messages[$symbol$]' -
gives the list of messages associated with $symbol$. -
- - >> a::b = "foo" - = foo - >> Messages[a] - = {HoldPattern[a::b] :> foo} - >> Messages[a] = {a::c :> "bar"}; - >> a::c // InputForm - = "bar" - >> Message[a::c] - : bar - """ - - attributes = ("HoldAll",) - - def apply(self, symbol, evaluation): - "Messages[symbol_]" - - return get_symbol_values(symbol, "Messages", "messages", evaluation) - - -class DefaultValues(Builtin): - """ -
-
'DefaultValues[$symbol$]' -
gives the list of default values associated with $symbol$. -
- - >> Default[f, 1] = 4 - = 4 - >> DefaultValues[f] - = {HoldPattern[Default[f, 1]] :> 4} - - You can assign values to 'DefaultValues': - >> DefaultValues[g] = {Default[g] -> 3}; - >> Default[g, 1] - = 3 - >> g[x_.] := {x} - >> g[a] - = {a} - >> g[] - = {3} - """ - - attributes = ("HoldAll",) - - def apply(self, symbol, evaluation): - "DefaultValues[symbol_]" - - return get_symbol_values(symbol, "System`DefaultValues", "default", evaluation) - - +# Placing this here is a bit weird, but it is not clear where else is better suited for this right now. class LoadModule(Builtin): """
-
'LoadModule[$module$]'
-
'Load Mathics definitions from the python module $module$
+
'LoadModule[$module$]'
+
'Load Mathics definitions from the python module $module$
>> LoadModule["nomodule"] : Python module nomodule does not exist. diff --git a/mathics/builtin/assignments/information.py b/mathics/builtin/assignments/information.py index ebc71e9b1..f7d05c16a 100644 --- a/mathics/builtin/assignments/information.py +++ b/mathics/builtin/assignments/information.py @@ -58,6 +58,228 @@ def _get_usage_string(symbol, evaluation, is_long_form: bool, htmlout=False): return None +# This could go under Symbol Handling when we get a module for that. +# It is not strictly in Assignment Information, but on the other hand, this +# is a reasonable place for it. +class Definition(Builtin): + """ +
+
'Definition[$symbol$]' +
prints as the definitions given for $symbol$. + This is in a form that can e stored in a package. +
+ + 'Definition' does not print information for 'ReadProtected' symbols. + 'Definition' uses 'InputForm' to format values. + + >> a = 2; + >> Definition[a] + = a = 2 + + >> f[x_] := x ^ 2 + >> g[f] ^:= 2 + >> Definition[f] + = f[x_] = x ^ 2 + . + . g[f] ^= 2 + + Definition of a rather evolved (though meaningless) symbol: + >> Attributes[r] := {Orderless} + >> Format[r[args___]] := Infix[{args}, "~"] + >> N[r] := 3.5 + >> Default[r, 1] := 2 + >> r::msg := "My message" + >> Options[r] := {Opt -> 3} + >> r[arg_., OptionsPattern[r]] := {arg, OptionValue[Opt]} + + Some usage: + >> r[z, x, y] + = x ~ y ~ z + >> N[r] + = 3.5 + >> r[] + = {2, 3} + >> r[5, Opt->7] + = {5, 7} + + Its definition: + >> Definition[r] + = Attributes[r] = {Orderless} + . + . arg_. ~ OptionsPattern[r] = {arg, OptionValue[Opt]} + . + . N[r, MachinePrecision] = 3.5 + . + . Format[args___, MathMLForm] = Infix[{args}, "~"] + . + . Format[args___, OutputForm] = Infix[{args}, "~"] + . + . Format[args___, StandardForm] = Infix[{args}, "~"] + . + . Format[args___, TeXForm] = Infix[{args}, "~"] + . + . Format[args___, TraditionalForm] = Infix[{args}, "~"] + . + . Default[r, 1] = 2 + . + . Options[r] = {Opt -> 3} + + For 'ReadProtected' symbols, 'Definition' just prints attributes, default values and options: + >> SetAttributes[r, ReadProtected] + >> Definition[r] + = Attributes[r] = {Orderless, ReadProtected} + . + . Default[r, 1] = 2 + . + . Options[r] = {Opt -> 3} + This is the same for built-in symbols: + >> Definition[Plus] + = Attributes[Plus] = {Flat, Listable, NumericFunction, OneIdentity, Orderless, Protected} + . + . Default[Plus] = 0 + >> Definition[Level] + = Attributes[Level] = {Protected} + . + . Options[Level] = {Heads -> False} + + 'ReadProtected' can be removed, unless the symbol is locked: + >> ClearAttributes[r, ReadProtected] + 'Clear' clears values: + >> Clear[r] + >> Definition[r] + = Attributes[r] = {Orderless} + . + . Default[r, 1] = 2 + . + . Options[r] = {Opt -> 3} + 'ClearAll' clears everything: + >> ClearAll[r] + >> Definition[r] + = Null + + If a symbol is not defined at all, 'Null' is printed: + >> Definition[x] + = Null + """ + + attributes = ("HoldAll",) + precedence = 670 + summary_text = "gives values of a symbol in a form that can be stored in a package" + + def format_definition(self, symbol, evaluation, grid=True): + "StandardForm,TraditionalForm,OutputForm: Definition[symbol_]" + + lines = [] + + def print_rule(rule, up=False, lhs=lambda k: k, rhs=lambda r: r): + evaluation.check_stopped() + if isinstance(rule, Rule): + r = rhs( + rule.replace.replace_vars( + { + "System`Definition": Expression( + "HoldForm", Symbol("Definition") + ) + }, + evaluation, + ) + ) + lines.append( + Expression( + "HoldForm", + Expression(up and "UpSet" or "Set", lhs(rule.pattern.expr), r), + ) + ) + + name = symbol.get_name() + if not name: + evaluation.message("Definition", "sym", symbol, 1) + return + attributes = evaluation.definitions.get_attributes(name) + definition = evaluation.definitions.get_user_definition(name, create=False) + all = evaluation.definitions.get_definition(name) + if attributes: + attributes = list(attributes) + attributes.sort() + lines.append( + Expression( + "HoldForm", + Expression( + "Set", + Expression("Attributes", symbol), + Expression( + "List", *(Symbol(attribute) for attribute in attributes) + ), + ), + ) + ) + + if definition is not None and "System`ReadProtected" not in attributes: + for rule in definition.ownvalues: + print_rule(rule) + for rule in definition.downvalues: + print_rule(rule) + for rule in definition.subvalues: + print_rule(rule) + for rule in definition.upvalues: + print_rule(rule, up=True) + for rule in definition.nvalues: + print_rule(rule) + formats = sorted(definition.formatvalues.items()) + for format, rules in formats: + for rule in rules: + + def lhs(expr): + return Expression("Format", expr, Symbol(format)) + + def rhs(expr): + if expr.has_form("Infix", None): + expr = Expression( + Expression("HoldForm", expr.head), *expr.leaves + ) + return Expression("InputForm", expr) + + print_rule(rule, lhs=lhs, rhs=rhs) + for rule in all.defaultvalues: + print_rule(rule) + if all.options: + options = sorted(all.options.items()) + lines.append( + Expression( + "HoldForm", + Expression( + "Set", + Expression("Options", symbol), + Expression( + "List", + *( + Expression("Rule", Symbol(name), value) + for name, value in options + ) + ), + ), + ) + ) + if grid: + if lines: + return Expression( + "Grid", + Expression("List", *(Expression("List", line) for line in lines)), + Expression("Rule", Symbol("ColumnAlignments"), Symbol("Left")), + ) + else: + return Symbol("Null") + else: + for line in lines: + evaluation.print_out(Expression("InputForm", line)) + return Symbol("Null") + + def format_definition_input(self, symbol, evaluation): + "InputForm: Definition[symbol_]" + return self.format_definition(symbol, evaluation, grid=False) + + +# In Mathematica 5, this appears under "Types of Values". class DownValues(Builtin): """
@@ -284,49 +506,7 @@ def format_definition_input(self, symbol, evaluation, options): return ret -class NValues(Builtin): - """ -
-
'NValues[$symbol$]' -
gives the list of numerical values associated with $symbol$. - - Note: this function is in Mathematica 5 but has been removed from current Mathematica. -
- - >> NValues[a] - = {} - >> N[a] = 3; - >> NValues[a] - = {HoldPattern[N[a, MachinePrecision]] :> 3} - - You can assign values to 'NValues': - >> NValues[b] := {N[b, MachinePrecision] :> 2} - >> N[b] - = 2. - Be sure to use 'SetDelayed', otherwise the left-hand side of the transformation rule will be evaluated immediately, - causing the head of 'N' to get lost. Furthermore, you have to include the precision in the rules; 'MachinePrecision' - will not be inserted automatically: - >> NValues[c] := {N[c] :> 3} - >> N[c] - = c - - Mathics will gracefully assign any list of rules to 'NValues'; however, inappropriate rules will never be used: - >> NValues[d] = {foo -> bar}; - >> NValues[d] - = {HoldPattern[foo] :> bar} - >> N[d] - = d - """ - - attributes = ("HoldAll",) - summary_text = "gives the list of numerical values associated with a symbol" - - def apply(self, symbol, evaluation): - "NValues[symbol_]" - - return get_symbol_values(symbol, "NValues", "n", evaluation) - - +# In Mathematica 5, this appears under "Types of Values". class OwnValues(Builtin): """
@@ -359,6 +539,7 @@ def apply(self, symbol, evaluation): return get_symbol_values(symbol, "OwnValues", "own", evaluation) +# In Mathematica 5, this appears under "Types of Values". class UpValues(Builtin): """
diff --git a/mathics/builtin/assignments/types.py b/mathics/builtin/assignments/types.py new file mode 100644 index 000000000..cf5975e4b --- /dev/null +++ b/mathics/builtin/assignments/types.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +# This module follows Mathematica 5 conventions. In current Mathematica a number of these functiions don't exist. +# Some of the functions in Mathematica 5 appear now under Information. +""" +Types of Values +""" + +from mathics.version import __version__ # noqa used in loading to check consistency. + +from mathics.builtin.base import Builtin + +from mathics.builtin.assignments.internals import get_symbol_values + + +class DefaultValues(Builtin): + """ +
+
'DefaultValues[$symbol$]' +
gives the list of default values associated with $symbol$. + + Note: this function is in Mathematica 5 but has been removed from current Mathematica. +
+ + >> Default[f, 1] = 4 + = 4 + >> DefaultValues[f] + = {HoldPattern[Default[f, 1]] :> 4} + + You can assign values to 'DefaultValues': + >> DefaultValues[g] = {Default[g] -> 3}; + >> Default[g, 1] + = 3 + >> g[x_.] := {x} + >> g[a] + = {a} + >> g[] + = {3} + """ + + attributes = ("HoldAll",) + summary_text = ( + "gives default values for the arguments associated with a function symbol" + ) + + def apply(self, symbol, evaluation): + "DefaultValues[symbol_]" + + return get_symbol_values(symbol, "System`DefaultValues", "default", evaluation) + + +class Messages(Builtin): + """ +
+
'Messages[$symbol$]' +
gives the list of messages associated with $symbol$. +
+ + >> a::b = "foo" + = foo + >> Messages[a] + = {HoldPattern[a::b] :> foo} + >> Messages[a] = {a::c :> "bar"}; + >> a::c // InputForm + = "bar" + >> Message[a::c] + : bar + """ + + attributes = ("HoldAll",) + summary_text = "gives the list the messages associated with a particular symbol" + + def apply(self, symbol, evaluation): + "Messages[symbol_]" + + return get_symbol_values(symbol, "Messages", "messages", evaluation) + + +class NValues(Builtin): + """ +
+
'NValues[$symbol$]' +
gives the list of numerical values associated with $symbol$. + + Note: this function is in Mathematica 5 but has been removed from current Mathematica. +
+ + >> NValues[a] + = {} + >> N[a] = 3; + >> NValues[a] + = {HoldPattern[N[a, MachinePrecision]] :> 3} + + You can assign values to 'NValues': + >> NValues[b] := {N[b, MachinePrecision] :> 2} + >> N[b] + = 2. + Be sure to use 'SetDelayed', otherwise the left-hand side of the transformation rule will be evaluated immediately, + causing the head of 'N' to get lost. Furthermore, you have to include the precision in the rules; 'MachinePrecision' + will not be inserted automatically: + >> NValues[c] := {N[c] :> 3} + >> N[c] + = c + + Mathics will gracefully assign any list of rules to 'NValues'; however, inappropriate rules will never be used: + >> NValues[d] = {foo -> bar}; + >> NValues[d] + = {HoldPattern[foo] :> bar} + >> N[d] + = d + """ + + attributes = ("HoldAll",) + summary_text = "gives the list of numerical values associated with a symbol" + + def apply(self, symbol, evaluation): + "NValues[symbol_]" + + return get_symbol_values(symbol, "NValues", "n", evaluation) + + +class SubValues(Builtin): + """ +
+
'SubValues[$symbol$]' +
gives the list of subvalues associated with $symbol$. + + Note: this function is not in current Mathematica. +
+ + >> f[1][x_] := x + >> f[2][x_] := x ^ 2 + >> SubValues[f] + = {HoldPattern[f[2][x_]] :> x ^ 2, HoldPattern[f[1][x_]] :> x} + >> Definition[f] + = f[2][x_] = x ^ 2 + . + . f[1][x_] = x + """ + + attributes = ("HoldAll",) + summary_text = "gives the list of subvalues associated with a symbol" + + def apply(self, symbol, evaluation): + "SubValues[symbol_]" + + return get_symbol_values(symbol, "SubValues", "sub", evaluation) From 0b3b37554e8bc683cd94f83fdf8c11e1d64adc63 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Tue, 19 Oct 2021 15:51:15 -0300 Subject: [PATCH 190/193] remove trailing exit() from debugging (#45) remove trailing exit() from debugging. --- mathics/builtin/assignments/internals.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mathics/builtin/assignments/internals.py b/mathics/builtin/assignments/internals.py index 953791850..637ee5814 100644 --- a/mathics/builtin/assignments/internals.py +++ b/mathics/builtin/assignments/internals.py @@ -256,7 +256,6 @@ def process_assign_context(self, lhs, rhs, evaluation, tags, upset): new_context, allow_initial_backquote=True ): evaluation.message(lhs_name, "cxset", rhs) - exit() raise AssignmentException(lhs, None) # With $Context in Mathematica you can do some strange From 54833261bcd25c4c9e015f0db36e5afd1d0a764b Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 25 Oct 2021 00:37:54 -0300 Subject: [PATCH 191/193] Singletonize (#47) * add subexpression support * walk_parts_new * adding comments * fix * ensuring that Module uses a copy of the variables * removing set_position method and old ExpressionPointer class, which now are useless * removing old walk_parts * fix Graphics and Graphics3D * fix graphics and strings * Using references to symbols instead of create them * update the name of the parameter * removing more on the fly building of system symbols * adding docstrings * adding CHANGES * improvements from comments * removing assert in get_class. Improving description in CHANGES.rst * removing heads argument in mathics.algorithm.parts._list_parts * rocky's rewording of the mathics.algorithm.parts._list_parts docstring * mathics.algorithm.parts - Improving docstrings. Renaming parameters for clarity. Removing unused parameters. * pspect -> part specification * Small doc tweaks Note: use `` `` in RsT/Sphinx for code inline markup. * tuple-> frozenset in system_symbols * Add dosctring for system_symbols; why frozeset Co-authored-by: rocky --- CHANGES.rst | 1 + mathics/algorithm/parts.py | 151 ++++----- mathics/builtin/arithfns/basic.py | 105 +++--- mathics/builtin/assignments/clear.py | 31 +- mathics/builtin/assignments/internals.py | 7 +- mathics/builtin/box/graphics.py | 22 +- mathics/builtin/box/graphics3d.py | 18 +- mathics/builtin/box/uniform_polyhedra.py | 3 +- mathics/builtin/colors/color_directives.py | 3 +- mathics/builtin/drawing/graphics_internals.py | 12 +- mathics/builtin/graphics.py | 82 ++--- mathics/builtin/inout.py | 95 +++--- mathics/builtin/intfns/divlike.py | 30 +- mathics/builtin/lists.py | 21 +- mathics/builtin/numbers/algebra.py | 142 +++++---- mathics/builtin/scoping.py | 2 +- mathics/builtin/sparse.py | 2 +- mathics/builtin/string/operations.py | 5 +- mathics/builtin/strings.py | 26 +- mathics/core/atoms.py | 54 ++-- mathics/core/convert.py | 90 ++++-- mathics/core/expression.py | 228 +++++-------- mathics/core/number.py | 13 +- mathics/core/pattern.py | 37 ++- mathics/core/subexpression.py | 299 ++++++++++++++++++ mathics/core/symbols.py | 140 +++++--- mathics/core/systemsymbols.py | 8 + 27 files changed, 1038 insertions(+), 589 deletions(-) create mode 100644 mathics/core/subexpression.py diff --git a/CHANGES.rst b/CHANGES.rst index 038672cea..f03268115 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,7 @@ CHANGES Internals ========= +* 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 * ``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`` was used to speed up critically used built-in function ``N``. Its use reduces the use of diff --git a/mathics/algorithm/parts.py b/mathics/algorithm/parts.py index 740f06022..134963d51 100644 --- a/mathics/algorithm/parts.py +++ b/mathics/algorithm/parts.py @@ -9,6 +9,7 @@ from mathics.core.symbols import Symbol from mathics.core.atoms import Integer, from_python from mathics.core.systemsymbols import SymbolInfinity +from mathics.core.subexpression import SubExpression from mathics.builtin.exceptions import ( InvalidLevelspecError, @@ -17,12 +18,20 @@ PartRangeError, ) +SymbolNothing = Symbol("Nothing") -def join_lists(lists): - new_list = [] - for list in lists: - new_list.extend(list) - return new_list +# TODO: delete me +# def join_lists(lists): +# """ +# flatten a list of list. +# Maybe there are better, standard options, like +# https://stackoverflow.com/questions/952914/how-to-make-a-flat-list-out-of-a-list-of-lists. +# In any case, is not used in the following code. +# """ +# new_list = [] +# for list in lists: +# new_list.extend(list) +# return new_list def get_part(varlist, indices): @@ -86,6 +95,9 @@ def rec(cur, rest): def _parts_all_selector(): + """ + Selector for `System`All` as a part specification. + """ start = 1 stop = None step = 1 @@ -102,6 +114,9 @@ def select(inner): def _parts_span_selector(pspec): + """ + Selector for `System`Span` part specification + """ if len(pspec.leaves) > 3: raise MessageException("Part", "span", pspec) start = 1 @@ -138,6 +153,9 @@ def select(inner): def _parts_sequence_selector(pspec): + """ + Selector for `System`Sequence` part specification + """ if not isinstance(pspec, (tuple, list)): indices = [pspec] else: @@ -170,6 +188,10 @@ def select(inner): def _part_selectors(indices): + """ + _part_selectors returns a suitable `selector` function according to + the kind of specifications in `indices`. + """ for index in indices: if index.has_form("Span", None): yield _parts_span_selector(index) @@ -183,10 +205,20 @@ def _part_selectors(indices): raise MessageException("Part", "pspec", index) -def _list_parts(items, selectors, heads, evaluation, assignment): +def _list_parts(exprs, selectors, evaluation): + """ + _list_parts returns a generator of Expressions using selectors to pick out parts of `exprs`. + If `selectors` is empty then a generator of items is returned. + + If a selector in `selectors` is a tuple it consists of a function to determine whether or + not to select an expression and a optional function to unwrap the resulting selected expressions. + + `evaluation` is used in expression restructuring an unwrapped expression when the there a + unwrapping function in the selector. + """ if not selectors: - for item in items: - yield item + for expr in exprs: + yield expr else: selector = selectors[0] if isinstance(selector, tuple): @@ -195,84 +227,61 @@ def _list_parts(items, selectors, heads, evaluation, assignment): select = selector unwrap = None - for item in items: - selected = list(select(item)) + for expr in exprs: + selected = list(select(expr)) - picked = list( - _list_parts(selected, selectors[1:], heads, evaluation, assignment) - ) + picked = list(_list_parts(selected, selectors[1:], evaluation)) if unwrap is None: - if assignment: - expr = Expression(item.head, *picked) - expr.original = None - expr.set_positions() - else: - expr = item.restructure(item.head, picked, evaluation) - + expr = expr.restructure(expr.head, picked, evaluation) yield expr else: yield unwrap(picked) -def _parts(items, selectors, evaluation, assignment=False): - heads = {} - return list(_list_parts([items], list(selectors), heads, evaluation, assignment))[0] +def _parts(expr, selectors, evaluation): + """ + Select from the `Expression` expr those elements indicated by + the `selectors`. + """ + return list(_list_parts([expr], list(selectors), evaluation))[0] -def walk_parts(list_of_list, indices, evaluation, assign_list=None): - walk_list = list_of_list[0] +def walk_parts(list_of_list, indices, evaluation, assign_rhs=None): + """ + walk_parts takes the first element of `list_of_list`, and builds + a subexpression composed of the expressions at the index positions + listed in `indices`. - if assign_list is not None: - # this double copying is needed to make the current logic in - # the assign_list and its access to original work. + `assign_rhs`, when not empty, indicates where to the store parts of the composed list. - walk_list = walk_list.copy() - walk_list.set_positions() - list_of_list = [walk_list] + list_of_list: a list of `Expression`s with a unique element. - walk_list = walk_list.copy() - walk_list.set_positions() + indices: a list of part specification `Expression`s, including + `Integer` indices, `Span` `Expression`s, `List` of `Integer`s + and + assign_rhs: None or an `Expression` object. + """ + walk_list = list_of_list[0] indices = [index.evaluate(evaluation) for index in indices] - - try: - result = _parts( - walk_list, _part_selectors(indices), evaluation, assign_list is not None - ) - except MessageException as e: - e.message(evaluation) - return False - - if assign_list is not None: - - def replace_item(all, item, new): - if item.position is None: - all[0] = new - else: - item.position.replace(new) - - def process_level(item, assignment): - if item.is_atom(): - replace_item(list_of_list, item.original, assignment) - elif assignment.get_head_name() != "System`List" or len(item.leaves) != len( - assignment.leaves - ): - if item.original: - replace_item(list_of_list, item.original, assignment) - else: - for leaf in item.leaves: - process_level(leaf, assignment) - else: - for sub_item, sub_assignment in zip(item.leaves, assignment.leaves): - process_level(sub_item, sub_assignment) - - process_level(result, assign_list) - - result = list_of_list[0] + if assign_rhs is not None: + try: + result = SubExpression(walk_list, indices) + result.replace(assign_rhs.copy()) + result = result.to_expression() + except MessageException as e: + e.message(evaluation) + return False result.clear_cache() - - return result + return result + else: + try: + result = _parts(walk_list, _part_selectors(indices), evaluation) + except MessageException as e: + e.message(evaluation) + return False + return result def is_in_level(current, depth, start=1, stop=None): @@ -502,7 +511,7 @@ def deletecases_with_levelspec(expr, pattern, evaluation, levelspec=1, n=-1): If a tuple (nmin, nmax) is provided, it just return those occurences with a number of "coordinates" between nmin and nmax. n indicates the number of occurrences to return. By default, it returns all the occurences. """ - nothing = Symbol("System`Nothing") + nothing = SymbolNothing from mathics.builtin.patterns import Matcher match = Matcher(pattern) diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index 3975f3c93..5c9b9a5ba 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -31,11 +31,20 @@ String, from_mpmath, ) -from mathics.core.symbols import Symbol, SymbolNull +from mathics.core.symbols import Symbol, SymbolList, SymbolNull, SymbolHoldForm from mathics.core.systemsymbols import ( + SymbolBlank, SymbolComplexInfinity, SymbolDirectedInfinity, + SymbolDivide, + SymbolIndeterminate, SymbolInfinity, + SymbolInfix, + SymbolMinus, + SymbolPattern, + SymbolPlus, + SymbolPower, + SymbolTimes, SymbolSequence, ) from mathics.core.number import min_prec, dps @@ -45,6 +54,9 @@ from mathics.builtin.numeric import apply_N +SymbolLeft = Symbol("Left") + + class CubeRoot(Builtin): """
@@ -95,7 +107,7 @@ def apply(self, n, evaluation): "CubeRoot[n_Complex]" evaluation.message("CubeRoot", "preal", n) - return Expression("Power", n, Expression("Divide", 1, 3)) + return Expression(SymbolPower, n, Expression(SymbolDivide, 1, 3)) class Divide(BinaryOperator): @@ -295,15 +307,15 @@ def negate(item): if len(item.leaves) == 1: return neg else: - return Expression("Times", *item.leaves[1:]) + return Expression(SymbolTimes, *item.leaves[1:]) else: - return Expression("Times", neg, *item.leaves[1:]) + return Expression(SymbolTimes, neg, *item.leaves[1:]) else: - return Expression("Times", -1, *item.leaves) + return Expression(SymbolTimes, -1, *item.leaves) elif isinstance(item, Number): return -item.to_sympy() else: - return Expression("Times", -1, item) + return Expression(SymbolTimes, -1, item) def is_negative(value): if isinstance(value, Complex): @@ -315,7 +327,7 @@ def is_negative(value): return False items = items.get_sequence() - values = [Expression("HoldForm", item) for item in items[:1]] + values = [Expression(SymbolHoldForm, item) for item in items[:1]] ops = [] for item in items[1:]: if ( @@ -325,14 +337,14 @@ def is_negative(value): op = "-" else: op = "+" - values.append(Expression("HoldForm", item)) + values.append(Expression(SymbolHoldForm, item)) ops.append(String(op)) return Expression( - "Infix", - Expression("List", *values), - Expression("List", *ops), + SymbolInfix, + Expression(SymbolList, *values), + Expression(SymbolList, *ops), 310, - Symbol("Left"), + SymbolLeft, ) def apply(self, items, evaluation): @@ -354,12 +366,12 @@ def append_last(): if last_item.has_form("Times", None): leaves.append( Expression( - "Times", from_sympy(last_count), *last_item.leaves + SymbolTimes, from_sympy(last_count), *last_item.leaves ) ) else: leaves.append( - Expression("Times", from_sympy(last_count), last_item) + Expression(SymbolTimes, from_sympy(last_count), last_item) ) for item in items: @@ -377,7 +389,7 @@ def append_last(): rest = rest[0] else: rest.sort() - rest = Expression("Times", *rest) + rest = Expression(SymbolTimes, *rest) break if count is None: count = sympy.Integer(1) @@ -415,7 +427,7 @@ def append_last(): return leaves[0] else: leaves.sort() - return Expression("Plus", *leaves) + return Expression(SymbolPlus, *leaves) class Power(BinaryOperator, _MPMathFunction): @@ -522,7 +534,7 @@ class Power(BinaryOperator, _MPMathFunction): formats = { Expression( "Power", - Expression("Pattern", Symbol("x"), Expression("Blank")), + Expression(SymbolPattern, Symbol("x"), Expression(SymbolBlank)), Rational(1, 2), ): "HoldForm[Sqrt[x]]", (("InputForm", "OutputForm"), "x_ ^ y_"): ( @@ -561,15 +573,21 @@ def apply_check(self, x, y, evaluation): if py_y > 0: return x elif py_y == 0.0: - evaluation.message("Power", "indet", Expression("Power", x, y_err)) - return Symbol("Indeterminate") + evaluation.message( + "Power", "indet", Expression(SymbolPower, x, y_err) + ) + return SymbolIndeterminate elif py_y < 0: - evaluation.message("Power", "infy", Expression("Power", x, y_err)) - return Symbol("ComplexInfinity") + evaluation.message( + "Power", "infy", Expression(SymbolPower, x, y_err) + ) + return SymbolComplexInfinity if isinstance(x, Complex) and x.real.is_zero: - yhalf = Expression("Times", y, Rational(1, 2)) - factor = self.apply(Expression("Sequence", x.imag, y), evaluation) - return Expression("Times", factor, Expression("Power", Integer(-1), yhalf)) + yhalf = Expression(SymbolTimes, y, Rational(1, 2)) + factor = self.apply(Expression(SymbolSequence, x.imag, y), evaluation) + return Expression( + SymbolTimes, factor, Expression(SymbolPower, Integer(-1), yhalf) + ) result = self.apply(Expression(SymbolSequence, x, y), evaluation) if result is None or result != SymbolNull: @@ -765,7 +783,7 @@ def inverse(item): if neg.sameQ(Integer1): return item.leaves[0] else: - return Expression("Power", item.leaves[0], neg) + return Expression(SymbolPower, item.leaves[0], neg) else: return item @@ -792,8 +810,8 @@ def inverse(item): minus = True else: minus = False - positive = [Expression("HoldForm", item) for item in positive] - negative = [Expression("HoldForm", item) for item in negative] + positive = [Expression(SymbolHoldForm, item) for item in positive] + negative = [Expression(SymbolHoldForm, item) for item in negative] if positive: positive = create_infix(positive, op, 400, "None") else: @@ -801,17 +819,17 @@ def inverse(item): if negative: negative = create_infix(negative, op, 400, "None") result = Expression( - "Divide", - Expression("HoldForm", positive), - Expression("HoldForm", negative), + SymbolDivide, + Expression(SymbolHoldForm, positive), + Expression(SymbolHoldForm, negative), ) else: result = positive if minus: result = Expression( - "Minus", result + SymbolMinus, result ) # Expression('PrecedenceForm', result, 481)) - result = Expression("HoldForm", result) + result = Expression(SymbolHoldForm, result) return result def format_inputform(self, items, evaluation): @@ -841,7 +859,7 @@ def apply(self, items, evaluation): if isinstance(item, Number): numbers.append(item) elif leaves and item == leaves[-1]: - leaves[-1] = Expression("Power", leaves[-1], Integer(2)) + leaves[-1] = Expression(SymbolPower, leaves[-1], Integer(2)) elif ( leaves and item.has_form("Power", 2) @@ -849,9 +867,9 @@ def apply(self, items, evaluation): and item.leaves[0].sameQ(leaves[-1].leaves[0]) ): leaves[-1] = Expression( - "Power", + SymbolPower, leaves[-1].leaves[0], - Expression("Plus", item.leaves[1], leaves[-1].leaves[1]), + Expression(SymbolPlus, item.leaves[1], leaves[-1].leaves[1]), ) elif ( leaves @@ -859,7 +877,9 @@ def apply(self, items, evaluation): and item.leaves[0].sameQ(leaves[-1]) ): leaves[-1] = Expression( - "Power", leaves[-1], Expression("Plus", item.leaves[1], Integer1) + SymbolPower, + leaves[-1], + Expression(SymbolPlus, item.leaves[1], Integer1), ) elif ( leaves @@ -867,7 +887,9 @@ def apply(self, items, evaluation): and leaves[-1].leaves[0].sameQ(item) ): leaves[-1] = Expression( - "Power", item, Expression("Plus", Integer1, leaves[-1].leaves[1]) + SymbolPower, + item, + Expression(SymbolPlus, Integer1, leaves[-1].leaves[1]), ) elif item.get_head().sameQ(SymbolDirectedInfinity): infinity_factor = True @@ -903,12 +925,15 @@ def apply(self, items, evaluation): number = None elif number.is_zero: if infinity_factor: - return Symbol("Indeterminate") + return SymbolIndeterminate return number elif number.sameQ(Integer(-1)) and leaves and leaves[0].has_form("Plus", None): leaves[0] = Expression( leaves[0].get_head(), - *[Expression("Times", Integer(-1), leaf) for leaf in leaves[0].leaves], + *[ + Expression(SymbolTimes, Integer(-1), leaf) + for leaf in leaves[0].leaves + ], ) number = None @@ -926,7 +951,7 @@ def apply(self, items, evaluation): if len(leaves) == 1: ret = leaves[0] else: - ret = Expression("Times", *leaves) + ret = Expression(SymbolTimes, *leaves) if infinity_factor: return Expression(SymbolDirectedInfinity, ret) else: diff --git a/mathics/builtin/assignments/clear.py b/mathics/builtin/assignments/clear.py index 19119d284..8ff773ae7 100644 --- a/mathics/builtin/assignments/clear.py +++ b/mathics/builtin/assignments/clear.py @@ -241,28 +241,20 @@ class Unset(PostfixOperator): def apply(self, expr, evaluation): "Unset[expr_]" - name = expr.get_head_name() - if name in system_symbols( - "OwnValues", - "DownValues", - "SubValues", - "UpValues", - "NValues", - "Options", - "Messages", - ): + head = expr.get_head() + if head in SYSTEM_SYMBOL_VALUES: if len(expr.leaves) != 1: - evaluation.message_args(name, len(expr.leaves), 1) + evaluation.message_args(expr.get_head_name(), len(expr.leaves), 1) return SymbolFailed symbol = expr.leaves[0].get_name() if not symbol: - evaluation.message(name, "fnsym", expr) + evaluation.message(expr.get_head_name(), "fnsym", expr) return SymbolFailed - if name == "System`Options": + if head is Symbol("System`Options"): empty = {} else: empty = [] - evaluation.definitions.set_values(symbol, name, empty) + evaluation.definitions.set_values(symbol, expr.get_head_name(), empty) return Symbol("Null") name = expr.get_lookup_name() if not name: @@ -273,3 +265,14 @@ def apply(self, expr, evaluation): evaluation.message("Unset", "norep", expr, Symbol(name)) return SymbolFailed return Symbol("Null") + + +SYSTEM_SYMBOL_VALUES = system_symbols( + "OwnValues", + "DownValues", + "SubValues", + "UpValues", + "NValues", + "Options", + "Messages", +) diff --git a/mathics/builtin/assignments/internals.py b/mathics/builtin/assignments/internals.py index 637ee5814..50ed33f40 100644 --- a/mathics/builtin/assignments/internals.py +++ b/mathics/builtin/assignments/internals.py @@ -479,6 +479,7 @@ def process_assign_format(self, lhs, rhs, evaluation, tags, upset): "TeXForm", "MathMLForm", ) + form = [f.name for f in form] lhs = focus = lhs.leaves[0] tags = process_tags_and_upset_dont_allow_custom( tags, upset, self, lhs, focus, evaluation @@ -681,10 +682,6 @@ def assign(self, lhs, rhs, evaluation): evaluation.message(self.get_name(), "noval", symbol) return False indices = lhs.leaves[1:] - result = walk_parts([rule.replace], indices, evaluation, rhs) - if result: - evaluation.definitions.set_ownvalue(name, result) - else: - return False + return walk_parts([rule.replace], indices, evaluation, rhs) else: return self.assign_elementary(lhs, rhs, evaluation) diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index 53ebea50a..20f8f4ac9 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -1196,16 +1196,16 @@ def vertices(): # FIXME: GLOBALS is a horrible name. GLOBALS.update( { - "System`RectangleBox": RectangleBox, - "System`DiskBox": DiskBox, - "System`LineBox": LineBox, - "System`BezierCurveBox": BezierCurveBox, - "System`FilledCurveBox": FilledCurveBox, - "System`ArrowBox": ArrowBox, - "System`CircleBox": CircleBox, - "System`PolygonBox": PolygonBox, - "System`RegularPolygonBox": RegularPolygonBox, - "System`PointBox": PointBox, - "System`InsetBox": InsetBox, + Symbol("RectangleBox"): RectangleBox, + Symbol("DiskBox"): DiskBox, + Symbol("LineBox"): LineBox, + Symbol("BezierCurveBox"): BezierCurveBox, + Symbol("FilledCurveBox"): FilledCurveBox, + Symbol("ArrowBox"): ArrowBox, + Symbol("CircleBox"): CircleBox, + Symbol("PolygonBox"): PolygonBox, + Symbol("RegularPolygonBox"): RegularPolygonBox, + Symbol("PointBox"): PointBox, + Symbol("InsetBox"): InsetBox, } ) diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index d1e51c3bc..37b1e5b02 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -953,14 +953,14 @@ def _apply_boxscaling(self, boxscale): # FIXME: GLOBALS3D is a horrible name. GLOBALS3D.update( { - "System`Arrow3DBox": Arrow3DBox, - "System`Cone3DBox": Cone3DBox, - "System`Cuboid3DBox": Cuboid3DBox, - "System`Cylinder3DBox": Cylinder3DBox, - "System`Line3DBox": Line3DBox, - "System`Point3DBox": Point3DBox, - "System`Polygon3DBox": Polygon3DBox, - "System`Sphere3DBox": Sphere3DBox, - "System`Tube3DBox": Tube3DBox, + Symbol("Arrow3DBox"): Arrow3DBox, + Symbol("Cone3DBox"): Cone3DBox, + Symbol("Cuboid3DBox"): Cuboid3DBox, + Symbol("Cylinder3DBox"): Cylinder3DBox, + Symbol("Line3DBox"): Line3DBox, + Symbol("Point3DBox"): Point3DBox, + Symbol("Polygon3DBox"): Polygon3DBox, + Symbol("Sphere3DBox"): Sphere3DBox, + Symbol("Tube3DBox"): Tube3DBox, } ) diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py index 1e295cb2b..f808af541 100644 --- a/mathics/builtin/box/uniform_polyhedra.py +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -5,6 +5,7 @@ from mathics.builtin.colors.color_directives import _Color import numbers +from mathics.core.symbols import Symbol class UniformPolyhedron3DBox(InstanceableBuiltin): @@ -55,6 +56,6 @@ def _apply_boxscaling(self, boxscale): # FIXME: GLOBALS3D is a horrible name. GLOBALS3D.update( { - "System`UniformPolyhedron3DBox": UniformPolyhedron3DBox, + Symbol("System`UniformPolyhedron3DBox"): UniformPolyhedron3DBox, } ) diff --git a/mathics/builtin/colors/color_directives.py b/mathics/builtin/colors/color_directives.py index 3a0d307e7..5a87c3738 100644 --- a/mathics/builtin/colors/color_directives.py +++ b/mathics/builtin/colors/color_directives.py @@ -149,7 +149,6 @@ def init(self, item=None, components=None): # we must not clip here; we copy the components, without clipping, # e.g. RGBColor[-1, 0, 0] stays RGBColor[-1, 0, 0]. this is especially # important for color spaces like LAB that have negative components. - components = [value.round_to_float() for value in leaves] if None in components: raise ColorError @@ -170,7 +169,7 @@ def init(self, item=None, components=None): @staticmethod def create(expr): - head = expr.get_head_name() + head = expr.get_head() cls = get_class(head) if cls is None: raise ColorError diff --git a/mathics/builtin/drawing/graphics_internals.py b/mathics/builtin/drawing/graphics_internals.py index b54b8dde1..17a749041 100644 --- a/mathics/builtin/drawing/graphics_internals.py +++ b/mathics/builtin/drawing/graphics_internals.py @@ -13,7 +13,7 @@ # Signals to Mathics doc processing not to include this module in its documentation. no_doc = True -from mathics.core.symbols import system_symbols_dict +from mathics.core.symbols import system_symbols_dict, Symbol class _GraphicsElement(InstanceableBuiltin): @@ -30,10 +30,14 @@ def create_as_style(klass, graphics, item): return klass(graphics, item) -def get_class(name): - c = GLOBALS.get(name) +def get_class(symbol: Symbol): + """ + Returns the Builtin graphic primitive associated to the + Symbol `symbol` + """ + c = GLOBALS.get(symbol) if c is None: - return GLOBALS3D.get(name) + return GLOBALS3D.get(symbol) else: return c diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index aea3d9a47..1b5172c30 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -41,6 +41,7 @@ system_symbols, system_symbols_dict, SymbolList, + SymbolNull, ) from mathics.core.atoms import ( Integer, @@ -55,6 +56,9 @@ from mathics.format.asy_fns import asy_bezier +SymbolEdgeForm = Symbol("System`EdgeForm") +SymbolFaceForm = Symbol("System`FaceForm") + GRAPHICS_OPTIONS = { "AspectRatio": "Automatic", "Axes": "False", @@ -276,29 +280,29 @@ def apply_makeboxes(self, content, evaluation, options): StandardForm|TraditionalForm|OutputForm]""" def convert(content): - head = content.get_head_name() + head = content.get_head() - if head == "System`List": + if head is SymbolList: return Expression( SymbolList, *[convert(item) for item in content.leaves] ) - elif head == "System`Style": + elif head is Symbol("System`Style"): return Expression( "StyleBox", *[convert(item) for item in content.leaves] ) if head in element_heads: - if head == "System`Text": - head = "System`Inset" + if head is Symbol("System`Text"): + head = Symbol("System`Inset") atoms = content.get_atoms(include_heads=False) if any( not isinstance(atom, (Integer, Real)) - and not atom.get_name() in GRAPHICS_SYMBOLS + and atom not in GRAPHICS_SYMBOLS for atom in atoms ): - if head == "System`Inset": + if head is Symbol("System`Inset"): inset = content.leaves[0] - if inset.get_head_name() == "System`Graphics": + if inset.get_head() is Symbol("System`Graphics"): opts = {} # opts = dict(opt._leaves[0].name:opt_leaves[1] for opt in inset._leaves[1:]) inset = self.apply_makeboxes( @@ -313,7 +317,7 @@ def convert(content): ) else: n_leaves = content.leaves - return Expression(head + self.box_suffix, *n_leaves) + return Expression(head.name + self.box_suffix, *n_leaves) return content for option in options: @@ -1027,13 +1031,13 @@ class FaceForm(Builtin): def _style(graphics, item): - head = item.get_head_name() + head = item.get_head() if head in style_heads: klass = get_class(head) style = klass.create_as_style(klass, graphics, item) - elif head in ("System`EdgeForm", "System`FaceForm"): + elif head in (SymbolEdgeForm, SymbolFaceForm): style = graphics.style_class( - graphics, edge=head == "System`EdgeForm", face=head == "System`FaceForm" + graphics, edge=head is SymbolEdgeForm, face=head is SymbolFaceForm ) if len(item.leaves) > 1: raise BoxConstructError @@ -1127,9 +1131,9 @@ def get_line_width(self, face_element=True): def _flatten(leaves): for leaf in leaves: - if leaf.get_head_name() == "System`List": - flattened = leaf.flatten(Symbol("List")) - if flattened.get_head_name() == "System`List": + if leaf.get_head() is SymbolList: + flattened = leaf.flatten(SymbolList) + if flattened.get_head() is SymbolList: for x in flattened.leaves: yield x else: @@ -1156,10 +1160,10 @@ def get_options(name): def stylebox_style(style, specs): new_style = style.clone() for spec in _flatten(specs): - head_name = spec.get_head_name() - if head_name in style_and_form_heads: + head = spec.get_head() + if head in style_and_form_heads: new_style.append(spec) - elif head_name == "System`Rule" and len(spec.leaves) == 2: + elif head is Symbol("System`Rule") and len(spec.leaves) == 2: option, expr = spec.leaves if not isinstance(option, Symbol): raise BoxConstructError @@ -1181,21 +1185,21 @@ def convert(content, style): items = [content] style = style.clone() for item in items: - if item.get_name() == "System`Null": + if item is SymbolNull: continue - head = item.get_head_name() + head = item.get_head() if head in style_and_form_heads: style.append(item) - elif head == "System`StyleBox": + elif head is Symbol("System`StyleBox"): if len(item.leaves) < 1: raise BoxConstructError for element in convert( item.leaves[0], stylebox_style(style, item.leaves[1:]) ): yield element - elif head[-3:] == "Box": # and head[:-3] in element_heads: + elif head.name[-3:] == "Box": # and head[:-3] in element_heads: element_class = get_class(head) - options = get_options(head[:-3]) + options = get_options(head.name[:-3]) if options: data, options = _data_and_options(item.leaves, options) new_item = Expression(head, *data) @@ -1203,7 +1207,7 @@ def convert(content, style): else: element = element_class(self, style, item) yield element - elif head == "System`List": + elif head is SymbolList: for element in convert(item, style): yield element else: @@ -1386,28 +1390,30 @@ class Large(Builtin): style_heads = frozenset(styles.keys()) style_and_form_heads = frozenset( - style_heads.union({"System`EdgeForm", "System`FaceForm"}) + style_heads.union(system_symbols("System`EdgeForm", "System`FaceForm")) ) GLOBALS.update( - { - "Rectangle": Rectangle, - "Disk": Disk, - "Circle": Circle, - "Polygon": Polygon, - "RegularPolygon": RegularPolygon, - "Inset": Inset, - "Text": Text, - } + system_symbols_dict( + { + "Rectangle": Rectangle, + "Disk": Disk, + "Circle": Circle, + "Polygon": Polygon, + "RegularPolygon": RegularPolygon, + "Inset": Inset, + "Text": Text, + } + ) ) GLOBALS.update(styles) GRAPHICS_SYMBOLS = { - "System`List", - "System`Rule", - "System`VertexColors", + Symbol("System`List"), + Symbol("System`Rule"), + Symbol("System`VertexColors"), *element_heads, - *[element + "Box" for element in element_heads], + *[Symbol(element.name + "Box") for element in element_heads], *style_heads, } diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index 860dfcaa5..c18565cea 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -27,7 +27,7 @@ from mathics.builtin.options import options_to_rules from mathics.core.expression import Expression, BoxError -from mathics.core.symbols import Symbol, SymbolList +from mathics.core.symbols import Symbol, SymbolList, SymbolTrue, SymbolFalse, SymbolNull from mathics.core.atoms import ( String, @@ -53,6 +53,18 @@ MULTI_NEWLINE_RE = re.compile(r"\n{2,}") +SymbolAutomatic = Symbol("Automatic") +SymbolFullForm = Symbol("FullForm") +SymbolInfinity = Symbol("Infinity") +SymbolMessageName = Symbol("MessageName") +SymbolNumberForm = Symbol("NumberForm") +SymbolOutputForm = Symbol("OutputForm") +SymbolRow = Symbol("Row") +SymbolRowBox = Symbol("RowBox") +SymbolRuleDelayed = Symbol("RuleDelayed") +SymbolSuperscriptBox = Symbol("SuperscriptBox") +SymbolSubscriptBox = Symbol("SubscriptBox") + class Format(Builtin): """ @@ -542,7 +554,7 @@ def apply_postprefix(self, p, expr, h, prec, f, evaluation): else: args = (h, leaf) - return Expression("RowBox", Expression(SymbolList, *args)) + return Expression(SymbolRowBox, Expression(SymbolList, *args)) else: return MakeBoxes(expr, f) @@ -831,7 +843,7 @@ def apply_makeboxes(self, array, f, evaluation, options) -> Expression: "List", *( Expression( - "List", + SymbolList, *(Expression(SymbolMakeBoxes, item, f) for item in row.leaves), ) for row in array.leaves @@ -844,6 +856,9 @@ def apply_makeboxes(self, array, f, evaluation, options) -> Expression: # return Expression('GridBox',Expression('List', *(Expression('List', *(Expression('MakeBoxes', item, f) for item in row.leaves)) for row in array.leaves)), *options_to_rules(options)) +SymbolTableDepth = Symbol("TableDepth") + + class TableForm(Builtin): """
@@ -888,7 +903,7 @@ def apply_makeboxes(self, table, f, evaluation, options): """MakeBoxes[%(name)s[table_, OptionsPattern[%(name)s]], f:StandardForm|TraditionalForm|OutputForm]""" - dims = len(get_dimensions(table, head=Symbol("List"))) + dims = len(get_dimensions(table, head=SymbolList)) depth = self.get_option(options, "TableDepth", evaluation).unformatted depth = expr_min((Integer(dims), depth)) depth = depth.get_int_value() @@ -901,7 +916,7 @@ def apply_makeboxes(self, table, f, evaluation, options): elif depth == 1: return GridBox( Expression( - "List", + SymbolList, *( Expression(SymbolList, Expression(SymbolMakeBoxes, item, f)) for item in table.leaves @@ -913,7 +928,7 @@ def apply_makeboxes(self, table, f, evaluation, options): # Expression('List', Expression('MakeBoxes', item, f)) # for item in table.leaves))) else: - new_depth = Expression(SymbolRule, Symbol("TableDepth"), depth - 2) + new_depth = Expression(SymbolRule, SymbolTableDepth, depth - 2) def transform_item(item): if depth > 2: @@ -1008,7 +1023,7 @@ def apply_makeboxes(self, x, y, f, evaluation) -> Expression: y = y.get_sequence() return Expression( - "SubscriptBox", Expression(SymbolMakeBoxes, x, f), *list_boxes(y, f) + SymbolSubscriptBox, Expression(SymbolMakeBoxes, x, f), *list_boxes(y, f) ) @@ -1230,7 +1245,7 @@ def apply(self, symbol, tag, params, evaluation): params = params.get_sequence() evaluation.message(symbol.name, tag.value, *params) - return Symbol("Null") + return SymbolNull def check_message(expr) -> bool: @@ -1365,7 +1380,7 @@ def get_msg_list(exprs): if type(out_msg) is not EvaluationMessage: continue pattern = Expression( - "MessageName", Symbol(out_msg.symbol), String(out_msg.tag) + SymbolMessageName, Symbol(out_msg.symbol), String(out_msg.tag) ) if pattern in check_messages: display_fail_expr = True @@ -1536,14 +1551,14 @@ def apply(self, expr, evaluation): for e in seq: if isinstance(e, Symbol): - quiet_messages.add(Expression("MessageName", e, String("trace"))) + quiet_messages.add(Expression(SymbolMessageName, e, String("trace"))) elif check_message(e): quiet_messages.add(e) else: evaluation.message("Message", "name", e) evaluation.set_quiet_messages(quiet_messages) - return Symbol("Null") + return SymbolNull class On(Builtin): @@ -1582,13 +1597,15 @@ def apply(self, expr, evaluation): for e in seq: if isinstance(e, Symbol): - quiet_messages.discard(Expression("MessageName", e, String("trace"))) + quiet_messages.discard( + Expression(SymbolMessageName, e, String("trace")) + ) elif check_message(e): quiet_messages.discard(e) else: evaluation.message("Message", "name", e) evaluation.set_quiet_messages(quiet_messages) - return Symbol("Null") + return SymbolNull class MessageName(BinaryOperator): @@ -1631,7 +1648,7 @@ class MessageName(BinaryOperator): def apply(self, symbol, tag, evaluation): "MessageName[symbol_Symbol, tag_String]" - pattern = Expression("MessageName", symbol, tag) + pattern = Expression(SymbolMessageName, symbol, tag) return evaluation.definitions.get_value( symbol.get_name(), "System`Messages", pattern, evaluation ) @@ -1855,9 +1872,9 @@ def apply(self, expr, evaluation): "Print[expr__]" expr = expr.get_sequence() - expr = Expression("Row", Expression(SymbolList, *expr)) + expr = Expression(SymbolRow, Expression(SymbolList, *expr)) evaluation.print_out(expr) - return Symbol("Null") + return SymbolNull class FullForm(Builtin): @@ -1968,7 +1985,7 @@ def apply_mathml(self, expr, evaluation) -> Expression: evaluation.message( "General", "notboxes", - Expression("FullForm", boxes).evaluate(evaluation), + Expression(SymbolFullForm, boxes).evaluate(evaluation), ) mathml = "" is_a_picture = mathml[:6] == " Expression: mathml = '%s' % mathml mathml = '%s' % mathml # convert_box(boxes) - return Expression("RowBox", Expression(SymbolList, String(mathml))) + return Expression(SymbolRowBox, Expression(SymbolList, String(mathml))) class PythonForm(Builtin): @@ -2084,10 +2101,10 @@ def apply_tex(self, expr, evaluation) -> Expression: evaluation.message( "General", "notboxes", - Expression("FullForm", boxes).evaluate(evaluation), + Expression(SymbolFullForm, boxes).evaluate(evaluation), ) tex = "" - return Expression("RowBox", Expression(SymbolList, String(tex))) + return Expression(SymbolRowBox, Expression(SymbolList, String(tex))) class Style(Builtin): @@ -2173,20 +2190,20 @@ def check_options(self, options, evaluation): def check_DigitBlock(self, value, evaluation): py_value = value.get_int_value() - if value.sameQ(Symbol("Infinity")): + if value.sameQ(SymbolInfinity): return [0, 0] elif py_value is not None and py_value > 0: return [py_value, py_value] elif value.has_form("List", 2): nleft, nright = value.leaves py_left, py_right = nleft.get_int_value(), nright.get_int_value() - if nleft.sameQ(Symbol("Infinity")): + if nleft.sameQ(SymbolInfinity): nleft = 0 elif py_left is not None and py_left > 0: nleft = py_left else: nleft = None - if nright.sameQ(Symbol("Infinity")): + if nright.sameQ(SymbolInfinity): nright = 0 elif py_right is not None and py_right > 0: nright = py_right @@ -2198,7 +2215,7 @@ def check_DigitBlock(self, value, evaluation): return evaluation.message(self.get_name(), "dblk", value) def check_ExponentFunction(self, value, evaluation): - if value.sameQ(Symbol("Automatic")): + if value.sameQ(SymbolAutomatic): return self.default_ExponentFunction def exp_function(x): @@ -2207,7 +2224,7 @@ def exp_function(x): return exp_function def check_NumberFormat(self, value, evaluation): - if value.sameQ(Symbol("Automatic")): + if value.sameQ(SymbolAutomatic): return self.default_NumberFormat def num_function(man, base, exp, options): @@ -2234,9 +2251,9 @@ def check_ExponentStep(self, value, evaluation): return result def check_SignPadding(self, value, evaluation): - if value.sameQ(Symbol("True")): + if value.sameQ(SymbolTrue): return True - elif value.sameQ(Symbol("False")): + elif value.sameQ(SymbolFalse): return False return evaluation.message(self.get_name(), "opttf", value) @@ -2501,7 +2518,7 @@ class NumberForm(_NumberForm): def default_ExponentFunction(value): n = value.get_int_value() if -5 <= n <= 5: - return Symbol("Null") + return SymbolNull else: return value @@ -2511,9 +2528,9 @@ def default_NumberFormat(man, base, exp, options): if py_exp: mul = String(options["NumberMultiplier"]) return Expression( - "RowBox", + SymbolRowBox, Expression( - SymbolList, man, mul, Expression("SuperscriptBox", base, exp) + SymbolList, man, mul, Expression(SymbolSuperscriptBox, base, exp) ), ) else: @@ -2522,24 +2539,26 @@ def default_NumberFormat(man, base, exp, options): def apply_list_n(self, expr, n, evaluation, options) -> Expression: "NumberForm[expr_List, n_, OptionsPattern[NumberForm]]" options = [ - Expression("RuleDelayed", Symbol(key), value) + Expression(SymbolRuleDelayed, Symbol(key), value) for key, value in options.items() ] return Expression( - "List", - *[Expression("NumberForm", leaf, n, *options) for leaf in expr.leaves], + SymbolList, + *[Expression(SymbolNumberForm, leaf, n, *options) for leaf in expr.leaves], ) def apply_list_nf(self, expr, n, f, evaluation, options) -> Expression: "NumberForm[expr_List, {n_, f_}, OptionsPattern[NumberForm]]" options = [ - Expression("RuleDelayed", Symbol(key), value) + Expression(SymbolRuleDelayed, Symbol(key), value) for key, value in options.items() ] return Expression( - "List", + SymbolList, *[ - Expression("NumberForm", leaf, Expression(SymbolList, n, f), *options) + Expression( + SymbolNumberForm, leaf, Expression(SymbolList, n, f), *options + ) for leaf in expr.leaves ], ) @@ -2686,7 +2705,7 @@ def apply_makeboxes(self, expr, n, f, evaluation): except ValueError: return evaluation.message("BaseForm", "basf", n) - if f.get_name() == "System`OutputForm": + if f is SymbolOutputForm: return from_python("%s_%d" % (val, base)) else: - return Expression("SubscriptBox", String(val), String(base)) + return Expression(SymbolSubscriptBox, String(val), String(base)) diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index c26d958ba..f183b694f 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -11,7 +11,11 @@ from mathics.builtin.base import Builtin, Test, SympyFunction from mathics.core.expression import Expression from mathics.core.atoms import Integer -from mathics.core.symbols import Symbol +from mathics.core.symbols import Symbol, SymbolTrue, SymbolFalse, SymbolList +from mathics.core.systemsymbols import SymbolComplexInfinity + +SymbolQuotient = Symbol("Quotient") +SymbolQuotientRemainder = Symbol("QuotientRemainder") class CoprimeQ(Builtin): @@ -51,12 +55,12 @@ def apply(self, args, evaluation): py_args = [arg.to_python() for arg in args.get_sequence()] if not all(isinstance(i, int) or isinstance(i, complex) for i in py_args): - return Symbol("False") + return SymbolFalse if all(sympy.gcd(n, m) == 1 for (n, m) in combinations(py_args, 2)): - return Symbol("True") + return SymbolTrue else: - return Symbol("False") + return SymbolFalse class EvenQ(Test): @@ -282,13 +286,13 @@ def apply(self, n, evaluation): n = n.get_int_value() if n is None: - return Symbol("False") + return SymbolFalse n = abs(n) if sympy.isprime(n): - return Symbol("True") + return SymbolTrue else: - return Symbol("False") + return SymbolFalse class Quotient(Builtin): @@ -323,8 +327,8 @@ def apply(self, m, n, evaluation): py_m = m.get_int_value() py_n = n.get_int_value() if py_n == 0: - evaluation.message("Quotient", "infy", Expression("Quotient", m, n)) - return Symbol("ComplexInfinity") + evaluation.message("Quotient", "infy", Expression(SymbolQuotient, m, n)) + return SymbolComplexInfinity return Integer(py_m // py_n) @@ -370,8 +374,10 @@ def apply(self, m, n, evaluation): py_n = n.to_python() if py_n == 0: return evaluation.message( - "QuotientRemainder", "divz", Expression("QuotientRemainder", m, n) + "QuotientRemainder", + "divz", + Expression(SymbolQuotientRemainder, m, n), ) - return Expression("List", Integer(py_m // py_n), (py_m % py_n)) + return Expression(SymbolList, Integer(py_m // py_n), (py_m % py_n)) else: - return Expression("QuotientRemainder", m, n) + return Expression(SymbolQuotientRemainder, m, n) diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index a9c2ff339..d6e01782c 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -62,7 +62,14 @@ min_prec, ) -from mathics.core.symbols import Symbol, SymbolList, strip_context +from mathics.core.symbols import ( + Symbol, + SymbolList, + strip_context, + SymbolTrue, + SymbolFalse, + SymbolSequence, +) from mathics.core.systemsymbols import ( SymbolByteArray, @@ -212,8 +219,8 @@ def sameQ(a, b) -> bool: self.check_options(None, evaluation, options) for a in list1.leaves: if not any(sameQ(a, b) for b in list2.leaves): - return Symbol("False") - return Symbol("True") + return SymbolFalse + return SymbolTrue def apply_msg(self, e1, e2, evaluation, options={}): "ContainsOnly[e1_, e2_, OptionsPattern[ContainsOnly]]" @@ -993,7 +1000,7 @@ def apply_iter(self, expr, i, imin, imax, di, evaluation): ) while True: cont = Expression(compare_type, index, imax).evaluate(evaluation) - if cont == Symbol("False"): + if cont == SymbolFalse: break if not cont.is_true(): if self.throw_iterb: @@ -2473,9 +2480,9 @@ def apply(self, expr, subset, evaluation): ) if set(subset.leaves).issubset(set(expr.leaves)): - return Symbol("True") + return SymbolTrue else: - return Symbol("False") + return SymbolFalse def delete_one(expr, pos): @@ -2483,7 +2490,7 @@ def delete_one(expr, pos): raise PartDepthError(pos) leaves = expr.leaves if pos == 0: - return Expression(Symbol("System`Sequence"), *leaves) + return Expression(SymbolSequence, *leaves) s = len(leaves) truepos = pos if truepos < 0: diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index 1b9ca89e2..7798f71c9 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -26,6 +26,14 @@ Number, ) +from mathics.core.systemsymbols import ( + SymbolAlternatives, + SymbolDirectedInfinity, + SymbolPlus, + SymbolPower, + SymbolTimes, +) + from mathics.core.convert import from_sympy, sympy_symbol_prefix from mathics.core.rules import Pattern from mathics.builtin.scoping import dynamic_scoping @@ -33,6 +41,15 @@ import sympy +SymbolSin = Symbol("Sin") +SymbolSinh = Symbol("Sinh") +SymbolCos = Symbol("Cos") +SymbolCosh = Symbol("Cosh") +SymbolTan = Symbol("Tan") +SymbolTanh = Symbol("Tanh") +SymbolCot = Symbol("Cot") +SymbolCoth = Symbol("Coth") + def sympy_factor(expr_sympy): try: @@ -49,7 +66,7 @@ def sympy_factor(expr_sympy): def cancel(expr): if expr.has_form("Plus", None): - return Expression("Plus", *[cancel(leaf) for leaf in expr.leaves]) + return Expression(SymbolPlus, *[cancel(leaf) for leaf in expr.leaves]) else: try: result = expr.to_sympy() @@ -91,84 +108,84 @@ def _expand(expr): theta = _expand(theta) if theta.has_form("Plus", 2, None): - x, y = theta.leaves[0], Expression("Plus", *theta.leaves[1:]) - if head == Symbol("Sin"): + x, y = theta.leaves[0], Expression(SymbolPlus, *theta.leaves[1:]) + if head is SymbolSin: a = Expression( - "Times", - _expand(Expression("Sin", x)), - _expand(Expression("Cos", y)), + SymbolTimes, + _expand(Expression(SymbolSin, x)), + _expand(Expression(SymbolCos, y)), ) b = Expression( - "Times", - _expand(Expression("Cos", x)), - _expand(Expression("Sin", y)), + SymbolTimes, + _expand(Expression(SymbolCos, x)), + _expand(Expression(SymbolSin, y)), ) - return _expand(Expression("Plus", a, b)) + return _expand(Expression(SymbolPlus, a, b)) elif head == Symbol("Cos"): a = Expression( "Times", - _expand(Expression("Cos", x)), - _expand(Expression("Cos", y)), + _expand(Expression(SymbolCos, x)), + _expand(Expression(SymbolCos, y)), ) b = Expression( "Times", - _expand(Expression("Sin", x)), - _expand(Expression("Sin", y)), + _expand(Expression(SymbolSin, x)), + _expand(Expression(SymbolSin, y)), ) - return _expand(Expression("Plus", a, -b)) + return _expand(Expression(SymbolPlus, a, -b)) elif head == Symbol("Sinh"): a = Expression( "Times", - _expand(Expression("Sinh", x)), - _expand(Expression("Cosh", y)), + _expand(Expression(SymbolSinh, x)), + _expand(Expression(SymbolCosh, y)), ) b = Expression( "Times", - _expand(Expression("Cosh", x)), - _expand(Expression("Sinh", y)), + _expand(Expression(SymbolCosh, x)), + _expand(Expression(SymbolSinh, y)), ) - return _expand(Expression("Plus", a, b)) + return _expand(Expression(SymbolPlus, a, b)) elif head == Symbol("Cosh"): a = Expression( "Times", - _expand(Expression("Cosh", x)), - _expand(Expression("Cosh", y)), + _expand(Expression(SymbolCosh, x)), + _expand(Expression(SymbolCosh, y)), ) b = Expression( "Times", - _expand(Expression("Sinh", x)), - _expand(Expression("Sinh", y)), + _expand(Expression(SymbolSinh, x)), + _expand(Expression(SymbolSinh, y)), ) - return _expand(Expression("Plus", a, b)) - elif head == Symbol("Tan"): - a = _expand(Expression("Sin", theta)) + return _expand(Expression(SymbolPlus, a, b)) + elif head is Symbol("Tan"): + a = _expand(Expression(SymbolSin, theta)) b = Expression( - "Power", _expand(Expression("Cos", theta)), Integer(-1) + SymbolPower, _expand(Expression(SymbolCos, theta)), Integer(-1) ) - return _expand(Expression("Times", a, b)) - elif head == Symbol("Cot"): - a = _expand(Expression("Cos", theta)) + return _expand(Expression(SymbolTimes, a, b)) + elif head is SymbolCot: + a = _expand(Expression(SymbolCos, theta)) b = Expression( - "Power", _expand(Expression("Sin", theta)), Integer(-1) + "Power", _expand(Expression(SymbolSin, theta)), Integer(-1) ) - return _expand(Expression("Times", a, b)) - elif head == Symbol("Tanh"): - a = _expand(Expression("Sinh", theta)) + return _expand(Expression(SymbolTimes, a, b)) + elif head is SymbolTanh: + a = _expand(Expression(SymbolSinh, theta)) b = Expression( - "Power", _expand(Expression("Cosh", theta)), Integer(-1) + SymbolPower, _expand(Expression(SymbolCosh, theta)), Integer(-1) ) - return _expand(Expression("Times", a, b)) - elif head == Symbol("Coth"): - a = _expand(Expression("Times", "Cosh", theta)) + return _expand(Expression(SymbolTimes, a, b)) + elif head is SymbolCoth: + a = _expand(Expression(SymbolTimes, "Cosh", theta)) b = Expression( - "Power", _expand(Expression("Sinh", theta)), Integer(-1) + SymbolPower, _expand(Expression(SymbolSinh, theta)), Integer(-1) ) return _expand(Expression(a, b)) @@ -571,9 +588,9 @@ class FactorTermsList(Builtin): def apply_list(self, expr, vars, evaluation): "FactorTermsList[expr_, vars_List]" if expr == Integer0: - return Expression("List", Integer1, Integer0) + return Expression(SymbolList, Integer1, Integer0) elif isinstance(expr, Number): - return Expression("List", expr, Integer1) + return Expression(SymbolList, expr, Integer1) for x in vars.leaves: if not (isinstance(x, Atom)): @@ -581,7 +598,7 @@ def apply_list(self, expr, vars, evaluation): sympy_expr = expr.to_sympy() if sympy_expr is None: - return Expression("List", Integer1, expr) + return Expression(SymbolList, Integer1, expr) sympy_expr = sympy.together(sympy_expr) sympy_vars = [ @@ -632,7 +649,7 @@ def apply_list(self, expr, vars, evaluation): result.append(sympy.expand(numer)) # evaluation.message(self.get_name(), 'poly', expr) - return Expression("List", *[from_sympy(i) for i in result]) + return Expression(SymbolList, *[from_sympy(i) for i in result]) class Apart(Builtin): @@ -1017,7 +1034,7 @@ def apply(self, expr, evaluation): variables = find_all_vars(expr) - variables = Expression("List", *variables) + variables = Expression(SymbolList, *variables) variables.sort() # MMA doesn't do this return variables @@ -1336,13 +1353,13 @@ def apply(self, expr, form, evaluation): e_null = expr == SymbolNull f_null = form == SymbolNull if expr == Integer0: - return Expression("List") + return Expression(SymbolList) elif e_null and f_null: return Expression(SymbolList, Integer0) elif e_null and not f_null: - return Expression("List", SymbolNull) + return Expression(SymbolList, SymbolNull) elif f_null: - return Expression("List", expr) + return Expression(SymbolList, expr) elif form.has_form("List", 0): return expr @@ -1393,7 +1410,7 @@ def _nth(poly, dims, exponents): subs = _nth(poly, dims[1:], exponents) leaves.append(subs) exponents.pop() - result = Expression("List", *leaves) + result = Expression(SymbolList, *leaves) return result return _nth(sympy_poly, dimensions, []) @@ -1450,7 +1467,7 @@ def apply_novar(self, expr, evaluation): def apply(self, expr, form, h, evaluation): "Exponent[expr_, form_, h_]" if expr == Integer0: - return Expression("DirectedInfinity", Integer(-1)) + return Expression(SymbolDirectedInfinity, Integer(-1)) if not form.has_form("List", None): return Expression(h, *[i for i in find_exponents(expr, form)]) @@ -1474,12 +1491,12 @@ def coeff_power_internal(self, expr, var_exprs, filt, evaluation, form="expr"): target_pat = Pattern.create(var_exprs[0]) var_pats = [target_pat] else: - target_pat = Pattern.create(Expression("Alternatives", *var_exprs)) + target_pat = Pattern.create(Expression(SymbolAlternatives, *var_exprs)) var_pats = [Pattern.create(var) for var in var_exprs] ####### Auxiliary functions ######### def key_powers(lst): - key = Expression("Plus", *lst) + key = Expression(SymbolPlus, *lst) key = key.evaluate(evaluation) if key.is_numeric(evaluation): return key.to_python() @@ -1508,9 +1525,9 @@ def powers_list(pf): if pf.has_form("Times", None): contrib = [powers_list(factor) for factor in pf._leaves] for i in range(len(var_pats)): - powers[i] = Expression("Plus", *[c[i] for c in contrib]).evaluate( - evaluation - ) + powers[i] = Expression( + SymbolPlus, *[c[i] for c in contrib] + ).evaluate(evaluation) return powers return powers @@ -1653,7 +1670,7 @@ def split_coeff_pow(term): elif len(val) == 1: coeff = val[0] else: - coeff = Expression("Plus", *val) + coeff = Expression(SymbolPlus, *val) if filt: coeff = Expression(filt, coeff).evaluate(evaluation) @@ -1666,7 +1683,7 @@ def split_coeff_pow(term): else: terms.append([powerfactor, coeff]) if form == "expr": - return Expression("Plus", *terms) + return Expression(SymbolPlus, *terms) else: return terms else: @@ -1738,7 +1755,9 @@ def apply_list(self, polys, varlist, evaluation, options): return for idxcoeff in component: idx, coeff = idxcoeff - order = Expression("Plus", *idx).evaluate(evaluation).get_int_value() + order = ( + Expression(SymbolPlus, *idx).evaluate(evaluation).get_int_value() + ) if order is None: evaluation.message("CoefficientArrays", "poly", polys, varlist) return @@ -1769,10 +1788,9 @@ def apply_list(self, polys, varlist, evaluation, options): if dim1 == 1 and order == 0: arrays[0] = coeff else: - arrays[order] = walk_parts( - [curr_array], arrayidx, evaluation, coeff - ) - return Expression("List", *arrays) + walk_parts([curr_array], arrayidx, evaluation, coeff) + arrays[order] = curr_array + return Expression(SymbolList, *arrays) class Collect(_CoefficientHandler): diff --git a/mathics/builtin/scoping.py b/mathics/builtin/scoping.py index f2a6bede8..1a2b72442 100644 --- a/mathics/builtin/scoping.py +++ b/mathics/builtin/scoping.py @@ -274,7 +274,7 @@ def apply(self, vars, expr, evaluation): for name, new_def in scoping_vars: new_name = "%s$%d" % (name, number) if new_def is not None: - evaluation.definitions.set_ownvalue(new_name, new_def) + evaluation.definitions.set_ownvalue(new_name, new_def.copy()) replace[name] = Symbol(new_name) new_expr = expr.replace_vars(replace, in_scoping=False) result = new_expr.evaluate(evaluation) diff --git a/mathics/builtin/sparse.py b/mathics/builtin/sparse.py index c3101c0e2..91cc4fd85 100644 --- a/mathics/builtin/sparse.py +++ b/mathics/builtin/sparse.py @@ -121,7 +121,7 @@ def apply_normal(self, dims, default, data, evaluation): for item in data.leaves: pos, val = item.leaves if pos.has_form("List", None): - table = walk_parts([table], pos.leaves, evaluation, val) + walk_parts([table], pos.leaves, evaluation, val) return table def find_dimensions(self, rules, evaluation): diff --git a/mathics/builtin/string/operations.py b/mathics/builtin/string/operations.py index a83f4d5ee..b804d534e 100644 --- a/mathics/builtin/string/operations.py +++ b/mathics/builtin/string/operations.py @@ -35,6 +35,9 @@ ) +SymbolAll = Symbol("All") + + class StringDrop(Builtin): """
@@ -488,7 +491,7 @@ def apply_n(self, string, patt, n, evaluation, options): overlap = True elif options["System`Overlaps"] == SymbolFalse: overlap = False - elif options["System`Overlaps"] == Symbol("All"): + elif options["System`Overlaps"] == SymbolAll: # TODO evaluation.message("StringPosition", "overall") overlap = True diff --git a/mathics/builtin/strings.py b/mathics/builtin/strings.py index b1729150c..0c34b1708 100644 --- a/mathics/builtin/strings.py +++ b/mathics/builtin/strings.py @@ -25,9 +25,7 @@ SymbolTrue, SymbolList, ) -from mathics.core.systemsymbols import ( - SymbolFailed, -) +from mathics.core.systemsymbols import SymbolFailed, SymbolDirectedInfinity from mathics.core.atoms import ( String, Integer, @@ -39,6 +37,12 @@ from mathics.settings import SYSTEM_CHARACTER_ENCODING from mathics_scanner import TranslateError + +SymbolBlank = Symbol("Blank") +SymbolOutputForm = Symbol("OutputForm") +SymbolToExpression = Symbol("ToExpression") +SymbolInputForm = Symbol("InputForm") + _regex_longest = { "+": "+", "*": "*", @@ -211,7 +215,9 @@ def recurse(x, quantifiers=q): return r"(.|\n)" + q["*"] if expr.has_form("Except", 1, 2): if len(expr.leaves) == 1: - leaves = [expr.leaves[0], Expression("Blank")] + # TODO: Check if this shouldn't be SymbolBlank + # instad of SymbolBlank[] + leaves = [expr.leaves[0], Expression(SymbolBlank)] else: leaves = [expr.leaves[0], expr.leaves[1]] leaves = [recurse(leaf) for leaf in leaves] @@ -654,7 +660,7 @@ def convert_rule(r): # convert n if n is None: py_n = 0 - elif n == Expression("DirectedInfinity", Integer1): + elif n == Expression(SymbolDirectedInfinity, Integer1): py_n = 0 else: py_n = n.get_int_value() @@ -668,7 +674,7 @@ def convert_rule(r): if isinstance(py_strings, list): return Expression( - "List", + SymbolList, *[ self._find(py_stri, py_rules, py_n, flags, evaluation) for py_stri in py_strings @@ -788,7 +794,7 @@ class ToString(Builtin): def apply_default(self, value, evaluation, options): "ToString[value_, OptionsPattern[ToString]]" - return self.apply_form(value, Symbol("System`OutputForm"), evaluation, options) + return self.apply_form(value, SymbolOutputForm, evaluation, options) def apply_form(self, value, form, evaluation, options): "ToString[value_, form_, OptionsPattern[ToString]]" @@ -822,7 +828,7 @@ def apply_dummy(self, boxes, evaluation): # In the first place, this should handle different kind # of boxes in different ways. reinput = boxes.boxes_to_text() - return Expression("ToExpression", reinput).evaluate(evaluation) + return Expression(SymbolToExpression, reinput).evaluate(evaluation) class ToExpression(Builtin): @@ -894,7 +900,7 @@ def apply(self, seq, evaluation): # Organise Arguments py_seq = seq.get_sequence() if len(py_seq) == 1: - (inp, form, head) = (py_seq[0], Symbol("InputForm"), None) + (inp, form, head) = (py_seq[0], SymbolInputForm, None) elif len(py_seq) == 2: (inp, form, head) = (py_seq[0], py_seq[1], None) elif len(py_seq) == 3: @@ -912,7 +918,7 @@ def apply(self, seq, evaluation): return # Apply the different forms - if form == Symbol("InputForm"): + if form == SymbolInputForm: if isinstance(inp, String): # TODO: turn the below up into a function and call that. diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index be9fe86e5..39fadb55c 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -15,7 +15,9 @@ Atom, BaseExpression, Symbol, + SymbolHoldForm, SymbolFalse, + SymbolFullForm, SymbolList, SymbolNull, SymbolTrue, @@ -23,7 +25,7 @@ system_symbols, ) -from mathics.core.systemsymbols import SymbolByteArray +from mathics.core.systemsymbols import SymbolByteArray, SymbolRowBox, SymbolRule from mathics.core.number import dps, get_type, prec, min_prec, machine_precision import base64 @@ -32,6 +34,16 @@ # We have to be able to match mpmath values with sympy values COMPARE_PREC = 50 +SymbolComplex = Symbol("Complex") +SymbolDivide = Symbol("Divide") +SymbolI = Symbol("I") +SymbolMinus = Symbol("Minus") +SymbolPlus = Symbol("Plus") +SymbolRational = Symbol("Rational") +SymbolTimes = Symbol("Times") + +SYSTEM_SYMBOLS_INPUT_OR_FULL_FORM = system_symbols("InputForm", "FullForm") + @lru_cache(maxsize=1024) def from_mpmath(value, prec=None): @@ -77,12 +89,14 @@ def _NumberFormat(man, base, exp, options): "System`OutputForm", "System`FullForm", ): - return Expression("RowBox", Expression(SymbolList, man, String("*^"), exp)) + return Expression( + SymbolRowBox, Expression(SymbolList, man, String("*^"), exp) + ) else: return Expression( - "RowBox", + SymbolRowBox, Expression( - "List", + SymbolList, man, String(options["NumberMultiplier"]), Expression("SuperscriptBox", base, exp), @@ -230,10 +244,10 @@ def denominator(self) -> "Integer": def do_format(self, evaluation, form) -> "Expression": from mathics.core.expression import Expression - assert fully_qualified_symbol_name(form) - if form == "System`FullForm": + assert isinstance(form, Symbol) + if form is SymbolFullForm: return Expression( - Expression("HoldForm", Symbol("Rational")), + Expression(SymbolHoldForm, SymbolRational), self.numerator(), self.denominator(), ).do_format(evaluation, form) @@ -242,10 +256,10 @@ def do_format(self, evaluation, form) -> "Expression": minus = numerator.value < 0 if minus: numerator = Integer(-numerator.value) - result = Expression("Divide", numerator, self.denominator()) + result = Expression(SymbolDivide, numerator, self.denominator()) if minus: - result = Expression("Minus", result) - result = Expression("HoldForm", result) + result = Expression(SymbolMinus, result) + result = Expression(SymbolHoldForm, result) return result.do_format(evaluation, form) def default_format(self, evaluation, form) -> str: @@ -559,25 +573,27 @@ def to_mpmath(self): def do_format(self, evaluation, form) -> "Expression": from mathics.core.expression import Expression - if form == "System`FullForm": + assert isinstance(form, Symbol) + + if form is SymbolFullForm: return Expression( - Expression("HoldForm", Symbol("Complex")), self.real, self.imag + Expression(SymbolHoldForm, SymbolComplex), self.real, self.imag ).do_format(evaluation, form) parts: typing.List[Any] = [] if self.is_machine_precision() or not self.real.is_zero: parts.append(self.real) if self.imag.sameQ(Integer(1)): - parts.append(Symbol("I")) + parts.append(SymbolI) else: - parts.append(Expression("Times", self.imag, Symbol("I"))) + parts.append(Expression(SymbolTimes, self.imag, SymbolI)) if len(parts) == 1: result = parts[0] else: - result = Expression("Plus", *parts) + result = Expression(SymbolPlus, *parts) - return Expression("HoldForm", result).do_format(evaluation, form) + return Expression(SymbolHoldForm, result).do_format(evaluation, form) def default_format(self, evaluation, form) -> str: return "Complex[%s, %s]" % ( @@ -676,6 +692,7 @@ class String(Atom): def __new__(cls, value): self = super().__new__(cls) + self.value = str(value) return self @@ -802,8 +819,7 @@ def render(format, string, in_text=False): def atom_to_boxes(self, f, evaluation): inner = str(self.value) - - if f.get_name() in system_symbols("InputForm", "FullForm"): + if f in SYSTEM_SYMBOLS_INPUT_OR_FULL_FORM: inner = inner.replace("\\", "\\\\") return String('"' + inner + '"') @@ -977,7 +993,7 @@ def from_python(arg): elif isinstance(arg, dict): entries = [ Expression( - "Rule", + SymbolRule, from_python(key), from_python(arg[key]), ) diff --git a/mathics/core/convert.py b/mathics/core/convert.py index 7cfd15592..c2c327de7 100644 --- a/mathics/core/convert.py +++ b/mathics/core/convert.py @@ -8,13 +8,39 @@ import sympy -sympy_symbol_prefix = "_Mathics_User_" -sympy_slot_prefix = "_Mathics_Slot_" - BasicSympy = sympy.Expr +from mathics.core.symbols import ( + Symbol, + SymbolFalse, + SymbolTrue, + sympy_symbol_prefix, + sympy_slot_prefix, +) + + +SymbolEqual = Symbol("Equal") +SymbolFunction = Symbol("Function") +SymbolGreater = Symbol("Greater") +SymbolGreaterEqual = Symbol("GreaterEqual") +SymbolIndeterminate = Symbol("Indeterminate") +SymbolInfinity = Symbol("Infinity") +SymbolLess = Symbol("Less") +SymbolLessEqual = Symbol("LessEqual") +SymbolO = Symbol("O") +SymbolPiecewise = Symbol("Piecewise") +SymbolPlus = Symbol("Plus") +SymbolPower = Symbol("Power") +SymbolPrime = Symbol("Prime") +SymbolRoot = Symbol("Root") +SymbolRootSum = Symbol("RootSum") +SymbolSlot = Symbol("Slot") +SymbolTimes = Symbol("Times") +SymbolUnequal = Symbol("Unequal") + + def is_Cn_expr(name) -> bool: if name.startswith(sympy_symbol_prefix) or name.startswith(sympy_slot_prefix): return False @@ -183,7 +209,7 @@ def from_sympy(expr): ): return Symbol(expr.__class__.__name__) elif isinstance(expr, sympy.core.numbers.NegativeInfinity): - return Expression("Times", Integer(-1), Symbol("Infinity")) + return Expression(SymbolTimes, Integer(-1), SymbolInfinity) elif isinstance(expr, sympy.core.numbers.ImaginaryUnit): return Complex(Integer0, Integer1) elif isinstance(expr, sympy.Integer): @@ -192,37 +218,37 @@ def from_sympy(expr): numerator, denominator = map(int, expr.as_numer_denom()) if denominator == 0: if numerator > 0: - return Symbol("Infinity") + return SymbolInfinity elif numerator < 0: - return Expression("Times", Integer(-1), Symbol("Infinity")) + return Expression(SymbolTimes, Integer(-1), SymbolInfinity) else: assert numerator == 0 - return Symbol("Indeterminate") + return SymbolIndeterminate return Rational(numerator, denominator) elif isinstance(expr, sympy.Float): if expr._prec == machine_precision: return MachineReal(float(expr)) return Real(expr) elif isinstance(expr, sympy.core.numbers.NaN): - return Symbol("Indeterminate") + return SymbolIndeterminate elif isinstance(expr, sympy.core.function.FunctionClass): return Symbol(str(expr)) elif expr is sympy.true: - return Symbol("True") + return SymbolTrue elif expr is sympy.false: - return Symbol("False") + return SymbolFalse elif expr.is_number and all([x.is_Number for x in expr.as_real_imag()]): # Hack to convert 3 * I to Complex[0, 3] return Complex(*[from_sympy(arg) for arg in expr.as_real_imag()]) elif expr.is_Add: - return Expression("Plus", *sorted([from_sympy(arg) for arg in expr.args])) + return Expression(SymbolPlus, *sorted([from_sympy(arg) for arg in expr.args])) elif expr.is_Mul: - return Expression("Times", *sorted([from_sympy(arg) for arg in expr.args])) + return Expression(SymbolTimes, *sorted([from_sympy(arg) for arg in expr.args])) elif expr.is_Pow: - return Expression("Power", *[from_sympy(arg) for arg in expr.args]) + return Expression(SymbolPower, *[from_sympy(arg) for arg in expr.args]) elif expr.is_Equality: - return Expression("Equal", *[from_sympy(arg) for arg in expr.args]) + return Expression(SymbolEqual, *[from_sympy(arg) for arg in expr.args]) elif isinstance(expr, SympyExpression): return expr.expr @@ -230,7 +256,7 @@ def from_sympy(expr): elif isinstance(expr, sympy.Piecewise): args = expr.args return Expression( - "Piecewise", + SymbolPiecewise, Expression( SymbolList, *[ @@ -241,9 +267,9 @@ def from_sympy(expr): ) elif isinstance(expr, SympyPrime): - return Expression("Prime", from_sympy(expr.args[0])) + return Expression(SymbolPrime, from_sympy(expr.args[0])) elif isinstance(expr, sympy.RootSum): - return Expression("RootSum", from_sympy(expr.poly), from_sympy(expr.fun)) + return Expression(SymbolRootSum, from_sympy(expr.poly), from_sympy(expr.fun)) elif isinstance(expr, sympy.PurePoly): coeffs = expr.coeffs() monoms = expr.monoms() @@ -254,34 +280,34 @@ def from_sympy(expr): factors.append(from_sympy(coeff)) for index, exp in enumerate(monom): if exp != 0: - slot = Expression("Slot", index + 1) + slot = Expression(SymbolSlot, index + 1) if exp == 1: factors.append(slot) else: - factors.append(Expression("Power", slot, from_sympy(exp))) + factors.append(Expression(SymbolPower, slot, from_sympy(exp))) if factors: - result.append(Expression("Times", *factors)) + result.append(Expression(SymbolTimes, *factors)) else: result.append(Integer1) - return Expression("Function", Expression("Plus", *result)) + return Expression(SymbolFunction, Expression(SymbolPlus, *result)) elif isinstance(expr, sympy.CRootOf): try: e, i = expr.args except ValueError: - return Expression("Null") + return SymbolNull try: e = sympy.PurePoly(e) except Exception: pass - return Expression("Root", from_sympy(e), i + 1) + return Expression(SymbolRoot, from_sympy(e), i + 1) elif isinstance(expr, sympy.Lambda): vars = [ sympy.Symbol("%s%d" % (sympy_slot_prefix, index + 1)) for index in range(len(expr.variables)) ] - return Expression("Function", from_sympy(expr(*vars))) + return Expression(SymbolFunction, from_sympy(expr(*vars))) elif expr.is_Function or isinstance( expr, (sympy.Integral, sympy.Derivative, sympy.Sum, sympy.Product) @@ -328,25 +354,25 @@ def from_sympy(expr): # return Expression('Sum', ) elif isinstance(expr, sympy.LessThan): - return Expression("LessEqual", *[from_sympy(arg) for arg in expr.args]) + return Expression(SymbolLessEqual, *[from_sympy(arg) for arg in expr.args]) elif isinstance(expr, sympy.StrictLessThan): - return Expression("Less", *[from_sympy(arg) for arg in expr.args]) + return Expression(SymbolLess, *[from_sympy(arg) for arg in expr.args]) elif isinstance(expr, sympy.GreaterThan): - return Expression("GreaterEqual", *[from_sympy(arg) for arg in expr.args]) + return Expression(SymbolGreaterEqual, *[from_sympy(arg) for arg in expr.args]) elif isinstance(expr, sympy.StrictGreaterThan): - return Expression("Greater", *[from_sympy(arg) for arg in expr.args]) + return Expression(SymbolGreater, *[from_sympy(arg) for arg in expr.args]) elif isinstance(expr, sympy.Unequality): - return Expression("Unequal", *[from_sympy(arg) for arg in expr.args]) + return Expression(SymbolUnequal, *[from_sympy(arg) for arg in expr.args]) elif isinstance(expr, sympy.Equality): - return Expression("Equal", *[from_sympy(arg) for arg in expr.args]) + return Expression(SymbolEqual, *[from_sympy(arg) for arg in expr.args]) elif isinstance(expr, sympy.O): if expr.args[0].func == sympy.core.power.Pow: [var, power] = [from_sympy(arg) for arg in expr.args[0].args] o = Expression("O", var) - return Expression("Power", o, power) + return Expression(SymbolPower, o, power) else: - return Expression("O", from_sympy(expr.args[0])) + return Expression(SymbolO, from_sympy(expr.args[0])) else: raise ValueError( "Unknown SymPy expression: {} (instance of {})".format( diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 2dad45035..64f8472c4 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -19,47 +19,46 @@ Symbol, SymbolList, SymbolN, + SymbolSequence, system_symbols, + ensure_context, + strip_context, ) from mathics.core.systemsymbols import SymbolSequence -def fully_qualified_symbol_name(name) -> bool: - return ( - isinstance(name, str) - and "`" in name - and not name.startswith("`") - and not name.endswith("`") - and "``" not in name - ) - - -def valid_context_name(ctx, allow_initial_backquote=False) -> bool: - return ( - isinstance(ctx, str) - and ctx.endswith("`") - and "``" not in ctx - and (allow_initial_backquote or not ctx.startswith("`")) - ) - - -def ensure_context(name, context="System`") -> str: - assert isinstance(name, str) - assert name != "" - if "`" in name: - # Symbol has a context mark -> it came from the parser - assert fully_qualified_symbol_name(name) - return name - # Symbol came from Python code doing something like - # Expression('Plus', ...) -> use System` or more generally - # context + name - return context + name - - -def strip_context(name) -> str: - if "`" in name: - return name[name.rindex("`") + 1 :] - return name +SymbolAborted = Symbol("$Aborted") +SymbolAlternatives = Symbol("Alternatives") +SymbolBlank = Symbol("System`Blank") +SymbolBlankSequence = Symbol("System`BlankSequence") +SymbolBlankNullSequence = Symbol("System`BlankNullSequence") +SymbolCompile = Symbol("Compile") +SymbolCompiledFunction = Symbol("CompiledFunction") +SymbolCondition = Symbol("Condition") +SymbolDefault = Symbol("Default") +SymbolDirectedInfinity = Symbol("DirectedInfinity") +SymbolFunction = Symbol("Function") +SymbolOptional = Symbol("Optional") +SymbolOptionsPattern = Symbol("OptionsPattern") +SymbolPattern = Symbol("Pattern") +SymbolPatternTest = Symbol("PatternTest") +SymbolSlot = Symbol("Slot") +SymbolSlotSequence = Symbol("SlotSequence") +SymbolTimes = Symbol("Times") +SymbolVerbatim = Symbol("Verbatim") + + +symbols_arithmetic_operations = system_symbols( + "Sqrt", + "Times", + "Plus", + "Subtract", + "Minus", + "Power", + "Abs", + "Divide", + "Sin", +) class BoxError(Exception): @@ -69,49 +68,6 @@ def __init__(self, box, form) -> None: self.form = form -class ExpressionPointer(object): - def __init__(self, parent, position) -> None: - self.parent = parent - self.position = position - - def replace(self, new) -> None: - if self.position == 0: - self.parent.set_head(new) - else: - self.parent.set_leaf(self.position - 1, new) - - def __str__(self) -> str: - return "%s[[%s]]" % (self.parent, self.position) - - -# class KeyComparable(object): -# def get_sort_key(self): -# raise NotImplementedError - -# def __lt__(self, other) -> bool: -# return self.get_sort_key() < other.get_sort_key() - -# def __gt__(self, other) -> bool: -# return self.get_sort_key() > other.get_sort_key() - -# def __le__(self, other) -> bool: -# return self.get_sort_key() <= other.get_sort_key() - -# def __ge__(self, other) -> bool: -# return self.get_sort_key() >= other.get_sort_key() - -# def __eq__(self, other) -> bool: -# return ( -# hasattr(other, "get_sort_key") -# and self.get_sort_key() == other.get_sort_key() -# ) - -# def __ne__(self, other) -> bool: -# return ( -# not hasattr(other, "get_sort_key") -# ) or self.get_sort_key() != other.get_sort_key() - - # ExpressionCache keeps track of the following attributes for one Expression instance: # time: (1) the last time (in terms of Definitions.now) this expression was evaluated @@ -226,12 +182,13 @@ def equal2(self, rhs: Any) -> Optional[bool]: elif isinstance(rhs, Atom): return None + head = self._head # Here we only need to deal with Expressions. - equal_heads = self._head.equal2(rhs._head) + equal_heads = head.equal2(rhs._head) if not equal_heads: return equal_heads # From here, we can assume that both heads are the same - if self.get_head_name() in ("System`List", "System`Sequence"): + if head in (SymbolList, SymbolSequence): if len(self._leaves) != len(rhs._leaves): return False for item1, item2 in zip(self._leaves, rhs._leaves): @@ -239,7 +196,7 @@ def equal2(self, rhs: Any) -> Optional[bool]: if not result: return result return True - elif self.get_head_name() in ("System`DirectedInfinity",): + elif head in (SymbolDirectedInfinity,): return self._leaves[0].equal2(rhs._leaves[0]) return None @@ -322,7 +279,7 @@ def sequence(leaf): def flatten_pattern_sequence(self, evaluation): def sequence(leaf): flattened = leaf.flatten_pattern_sequence(evaluation) - if leaf.get_head_name() == "System`Sequence" and leaf.pattern_sequence: + if leaf.get_head() is SymbolSequence and leaf.pattern_sequence: return flattened._leaves else: return [flattened] @@ -395,6 +352,10 @@ def copy(self, reevaluate=False) -> "Expression": def do_format(self, evaluation, form): if self._format_cache is None: self._format_cache = {} + if isinstance(form, str): + + raise Exception("Expression.do_format\n", form, " should be a Symbol") + form = Symbol(form) last_evaluated, expr = self._format_cache.get(form, (None, None)) if last_evaluated is not None and expr is not None: @@ -420,12 +381,6 @@ def shallow_copy(self) -> "Expression": # expr.last_evaluated = self.last_evaluated return expr - def set_positions(self, position=None) -> None: - self.position = position - self._head.set_positions(ExpressionPointer(self, 0)) - for index, leaf in enumerate(self._leaves): - leaf.set_positions(ExpressionPointer(self, index + 1)) - def get_head(self): return self._head @@ -451,7 +406,7 @@ def set_reordered_leaves(self, leaves): # same leaves, but in a different order self._cache = self._cache.reordered() def get_attributes(self, definitions): - if self.get_head_name() == "System`Function" and len(self._leaves) > 2: + if self.get_head() is SymbolFunction and len(self._leaves) > 2: res = self._leaves[2] if res.is_symbol(): return (str(res),) @@ -554,25 +509,26 @@ def to_python(self, *args, **kwargs): from mathics.builtin.base import mathics_to_python n_evaluation = kwargs.get("n_evaluation") - head_name = self._head.get_name() + head = self._head if n_evaluation is not None: - if head_name == "System`Function": - compiled = Expression("System`Compile", *(self._leaves)) + if head is SymbolFunction: + compiled = Expression(SymbolCompile, *(self._leaves)) compiled = compiled.evaluate(n_evaluation) - if compiled.get_head_name() == "System`CompiledFunction": + if compiled.get_head() is SymbolCompiledFunction: return compiled.leaves[2].cfunc value = Expression(SymbolN, self).evaluate(n_evaluation) return value.to_python() - if head_name == "System`DirectedInfinity" and len(self._leaves) == 1: + if head is SymbolDirectedInfinity and len(self._leaves) == 1: direction = self._leaves[0].get_int_value() if direction == 1: return math.inf if direction == -1: return -math.inf - elif head_name == "System`List": + elif head is SymbolList: return [leaf.to_python(*args, **kwargs) for leaf in self._leaves] + head_name = head.get_name() if head_name in mathics_to_python: py_obj = mathics_to_python[head_name] # Start here @@ -602,13 +558,13 @@ def get_sort_key(self, pattern_sort=False): 7: 0/1: 0 for Condition """ - name = self._head.get_name() + head = self._head pattern = 0 - if name == "System`Blank": + if head is SymbolBlank: pattern = 1 - elif name == "System`BlankSequence": + elif head is SymbolBlankSequence: pattern = 2 - elif name == "System`BlankNullSequence": + elif head is SymbolBlankNullSequence: pattern = 3 if pattern > 0: if self._leaves: @@ -622,36 +578,36 @@ def get_sort_key(self, pattern_sort=False): 1, 1, 0, - self._head.get_sort_key(True), + head.get_sort_key(True), tuple(leaf.get_sort_key(True) for leaf in self._leaves), 1, ] - if name == "System`PatternTest": + if head is SymbolPatternTest: if len(self._leaves) != 2: - return [3, 0, 0, 0, 0, self._head, self._leaves, 1] + return [3, 0, 0, 0, 0, head, self._leaves, 1] sub = self._leaves[0].get_sort_key(True) sub[2] = 0 return sub - elif name == "System`Condition": + elif head is SymbolCondition: if len(self._leaves) != 2: - return [3, 0, 0, 0, 0, self._head, self._leaves, 1] + return [3, 0, 0, 0, 0, head, self._leaves, 1] sub = self._leaves[0].get_sort_key(True) sub[7] = 0 return sub - elif name == "System`Pattern": + elif head is SymbolPattern: if len(self._leaves) != 2: - return [3, 0, 0, 0, 0, self._head, self._leaves, 1] + return [3, 0, 0, 0, 0, head, self._leaves, 1] sub = self._leaves[1].get_sort_key(True) sub[3] = 0 return sub - elif name == "System`Optional": + elif head is SymbolOptional: if len(self._leaves) not in (1, 2): - return [3, 0, 0, 0, 0, self._head, self._leaves, 1] + return [3, 0, 0, 0, 0, head, self._leaves, 1] sub = self._leaves[0].get_sort_key(True) sub[4] = 1 return sub - elif name == "System`Alternatives": + elif head is SymbolAlternatives: min_key = [4] min = None for leaf in self._leaves: @@ -663,12 +619,12 @@ def get_sort_key(self, pattern_sort=False): # empty alternatives -> very restrictive pattern return [2, 1] return min_key - elif name == "System`Verbatim": + elif head is SymbolVerbatim: if len(self._leaves) != 1: - return [3, 0, 0, 0, 0, self._head, self._leaves, 1] + return [3, 0, 0, 0, 0, head, self._leaves, 1] return self._leaves[0].get_sort_key(True) - elif name == "System`OptionsPattern": - return [2, 40, 0, 1, 1, 0, self._head, self._leaves, 1] + elif head is SymbolOptionsPattern: + return [2, 40, 0, 1, 1, 0, head, self._leaves, 1] else: # Append [4] to leaves so that longer expressions have higher # precedence @@ -678,7 +634,7 @@ def get_sort_key(self, pattern_sort=False): 1, 1, 0, - self._head.get_sort_key(True), + head.get_sort_key(True), tuple( chain( (leaf.get_sort_key(True) for leaf in self._leaves), ([4],) @@ -688,8 +644,8 @@ def get_sort_key(self, pattern_sort=False): ] else: exps = {} - head = self._head.get_name() - if head == "System`Times": + head = self._head + if head is SymbolTimes: for leaf in self._leaves: name = leaf.get_name() if leaf.has_form("Power", 2): @@ -710,19 +666,17 @@ def get_sort_key(self, pattern_sort=False): 2, Monomial(exps), 1, - self._head, + head, self._leaves, 1, ] else: - return [1 if self.is_numeric() else 2, 3, self._head, self._leaves, 1] + return [1 if self.is_numeric() else 2, 3, head, self._leaves, 1] def sameQ(self, other) -> bool: """Mathics SameQ""" if id(self) == id(other): return True - if self.get_head_name() != other.get_head_name(): - return False if not self._head.sameQ(other.get_head()): return False if len(self._leaves) != len(other.get_leaves()): @@ -803,7 +757,7 @@ def evaluate(self, evaluation) -> typing.Union["Expression", "Symbol"]: limit = "inf" if limit != "inf" and iteration > limit: evaluation.error("$IterationLimit", "itlim", limit) - return Symbol("$Aborted") + return SymbolAborted # "Return gets discarded only if it was called from within the r.h.s. # of a user-defined rule." @@ -1015,7 +969,7 @@ def boxes_to_mathml(self, **options) -> str: if ( name == "System`RowBox" and len(self._leaves) == 1 - and self._leaves[0].get_head_name() == "System`List" # nopep8 + and self._leaves[0].get_head() is SymbolList # nopep8 ): result = [] inside_row = options.get("inside_row") @@ -1227,7 +1181,7 @@ def replace_vars( leaves = self._leaves if in_function: if ( - self._head.get_name() == "System`Function" + self._head is SymbolFunction and len(self._leaves) > 1 and ( self._leaves[0].has_form("List", None) or self._leaves[0].get_name() @@ -1258,7 +1212,7 @@ def replace_vars( ) def replace_slots(self, slots, evaluation): - if self._head.get_name() == "System`Slot": + if self._head is SymbolSlot: if len(self._leaves) != 1: evaluation.message_args("Slot", len(self._leaves), 1) else: @@ -1269,7 +1223,7 @@ def replace_slots(self, slots, evaluation): evaluation.message("Function", "slotn", slot) else: return slots[int(slot)] - elif self._head.get_name() == "System`SlotSequence": + elif self._head is SymbolSlotSequence: if len(self._leaves) != 1: evaluation.message_args("SlotSequence", len(self._leaves), 1) else: @@ -1277,7 +1231,7 @@ def replace_slots(self, slots, evaluation): if slot is None or slot < 1: evaluation.error("Function", "slot", self._leaves[0]) return Expression(SymbolSequence, *slots[slot:]) - elif self._head.get_name() == "System`Function" and len(self._leaves) == 1: + elif self._head is SymbolFunction and len(self._leaves) == 1: # do not replace Slots in nested Functions return self return Expression( @@ -1287,7 +1241,7 @@ def replace_slots(self, slots, evaluation): def thread(self, evaluation, head=None) -> typing.Tuple[bool, "Expression"]: if head is None: - head = Symbol("List") + head = SymbolList items = [] dim = None @@ -1322,20 +1276,8 @@ def is_numeric(self, evaluation=None) -> bool: return False return all(leaf.is_numeric(evaluation) for leaf in self._leaves) else: - return ( - self._head.get_name() - in system_symbols( - "Sqrt", - "Times", - "Plus", - "Subtract", - "Minus", - "Power", - "Abs", - "Divide", - "Sin", - ) - and all(leaf.is_numeric() for leaf in self._leaves) + return self._head in symbols_arithmetic_operations and all( + leaf.is_numeric() for leaf in self._leaves ) def numerify(self, evaluation) -> "Expression": @@ -1398,7 +1340,7 @@ def get_default_value(name, evaluation, k=None, n=None): for pos_len in reversed(list(range(len(pos) + 1))): # Try patterns from specific to general defaultexpr = Expression( - "Default", Symbol(name), *[Integer(index) for index in pos[:pos_len]] + SymbolDefault, Symbol(name), *[Integer(index) for index in pos[:pos_len]] ) result = evaluation.definitions.get_value( name, "System`DefaultValues", defaultexpr, evaluation diff --git a/mathics/core/number.py b/mathics/core/number.py index 2e41ef9cd..d9e0b820f 100644 --- a/mathics/core/number.py +++ b/mathics/core/number.py @@ -9,6 +9,12 @@ import typing +from mathics.core.symbols import ( + SymbolMinPrecision, + SymbolMaxPrecision, + SymbolMachinePrecision, +) + C = log(10, 2) # ~ 3.3219280948873626 @@ -51,14 +57,13 @@ def _get_float_inf(value, evaluation) -> typing.Optional[float]: def get_precision(value, evaluation) -> typing.Optional[int]: - if value.get_name() == "System`MachinePrecision": + if value is SymbolMachinePrecision: return None else: - from mathics.core.symbols import Symbol from mathics.core.atoms import MachineReal - dmin = _get_float_inf(Symbol("$MinPrecision"), evaluation) - dmax = _get_float_inf(Symbol("$MaxPrecision"), evaluation) + dmin = _get_float_inf(SymbolMinPrecision, evaluation) + dmax = _get_float_inf(SymbolMaxPrecision, evaluation) d = value.round_to_float(evaluation) assert dmin is not None and dmax is not None if d is None: diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py index 286dd369f..a2fd8df4e 100644 --- a/mathics/core/pattern.py +++ b/mathics/core/pattern.py @@ -14,6 +14,21 @@ # from mathics.core import pattern_nocython +SYSTEM_SYMBOLS_PATTERNS = system_symbols( + "Pattern", + "PatternTest", + "Condition", + "Optional", + "Blank", + "BlankSequence", + "BlankNullSequence", + "Alternatives", + "OptionsPattern", + "Repeated", + "RepeatedNull", +) + + def Pattern_create(expr): from mathics.builtin import pattern_objects @@ -187,7 +202,6 @@ def match( wrap_oneid=True, ): evaluation.check_stopped() - attributes = self.head.get_attributes(evaluation.definitions) if "System`Flat" not in attributes: fully = True @@ -296,8 +310,8 @@ def yield_head(head_vars, _): wrap_oneid and not evaluation.ignore_oneidentity and "System`OneIdentity" in attributes - and expression.get_head() != self.head # nopep8 - and expression != self.head + and not self.head.expr.sameQ(expression.get_head()) # nopep8 + and not self.head.expr.sameQ(expression) ): # and 'OneIdentity' not in # (expression.get_attributes(evaluation.definitions) | @@ -510,22 +524,7 @@ def match_leaf( # of pattern. # TODO: This could be further optimized! try_flattened = ("System`Flat" in attributes) and ( - leaf.get_head_name() - in ( - system_symbols( - "Pattern", - "PatternTest", - "Condition", - "Optional", - "Blank", - "BlankSequence", - "BlankNullSequence", - "Alternatives", - "OptionsPattern", - "Repeated", - "RepeatedNull", - ) - ) + leaf.get_head() in SYSTEM_SYMBOLS_PATTERNS ) if try_flattened: diff --git a/mathics/core/subexpression.py b/mathics/core/subexpression.py new file mode 100644 index 000000000..22328a715 --- /dev/null +++ b/mathics/core/subexpression.py @@ -0,0 +1,299 @@ +# cython: language_level=3 +# -*- coding: utf-8 -*- + + +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol, Atom +from mathics.core.atoms import Integer +from mathics.builtin.base import MessageException + +""" +This module provides some infraestructure to deal with SubExpressions. + +""" + + +def _pspec_span_to_tuple(pspec, expr): + """ + This function takes an expression and a Mathics + `Span` Expression and returns a tuple with the positions + of the leaves. + """ + start = 1 + stop = None + step = 1 + leaves = pspec.leaves + if len(leaves) > 3: + raise MessageException("Part", "span", leaves) + if len(leaves) > 0: + start = leaves[0].get_int_value() + if len(leaves) > 1: + stop = leaves[1].get_int_value() + if stop is None: + if leaves[1].get_name() == "System`All": + stop = None + else: + raise MessageException("Part", "span", pspec) + else: + stop = stop - 1 if stop > 0 else len(expr.leaves) + stop + + if len(pspec.leaves) > 2: + step = leaves[2].get_int_value() + + if start is None or step is None: + raise MessageException("Part", "span", pspec) + + if start == 0 or stop == 0: + # index 0 is undefined + raise MessageException("Part", "span", Integer(0)) + + if start < 0: + start = len(expr.leaves) - start + else: + start = start - 1 + + if stop is None: + stop = 0 if step < 0 else len(expr.leaves) - 1 + + stop = stop + 1 if step > 0 else stop - 1 + return tuple(k for k in range(start, stop, step)) + + +class ExpressionPointer(object): + """ + This class represents a reference to a leaf in an expression. + Supports a minimal part of the basic interface of `mathics.core.symbols.BaseExpression`. + """ + + def __init__(self, expr, pos=None): + """ + Initializes a ExpressionPointer pointing to the leaf in position `pos` + of `expr`. + + expr: can be an Expression, a Symbol, or another ExpressionPointer + pos: int or None + + If `pos==0`, then the pointer points to the `head` of the expression. + If `pos` is `None`, it points out the whole expression. + + """ + if pos is None: + if type(expr) is ExpressionPointer: + self.parent = expr.parent + self.position = expr.position + else: + self.parent = expr + self.position = None + else: + self.parent = expr + self.position = pos + + def __str__(self) -> str: + return "%s[[%s]]" % (self.parent, self.position) + + def __repr__(self) -> str: + return self.__str__() + + @property + def original(self): + return None + + @original.setter + def original(self, value): + raise ValueError("Expression.original is write protected.") + + @property + def head(self): + pos = self.position + if pos is None: + return self.parent.head + elif pos == 0: + return self.parent.head.head + return self.parent.leaves[pos - 1].head + + @head.setter + def head(self, value): + raise ValueError("ExpressionPointer.head is write protected.") + + @property + def leaves(self): + pos = self.position + if pos is None: + return self.parent.leaves + elif pos == 0: + self.parent.head.leaves + return self.parent.leaves[pos - 1].leaves + + @leaves.setter + def leaves(self, value): + raise ValueError("ExpressionPointer.leaves is write protected.") + + def get_head_name(self): + return self.head.get_name() + + def is_atom(self): + pos = self.position + if pos is None: + return self.parent.is_atom() + elif pos == 0: + return self.parent.head.is_atom() + return self.parent.leaves[pos - 1].is_atom() + + def to_expression(self): + parent = self.parent + p = self.position + if p == 0: + if type(parent) is Symbol: + return parent + else: + return parent.head.copy() + else: + leaf = self.parent.leaves[p - 1] + if leaf.is_atom(): + return leaf + else: + return leaf.copy() + + def replace(self, new): + """ + This method replaces the value pointed out by a `new` value. + """ + # First, look for the ancestor that is not an ExpressionPointer, + # keeping the positions of each step: + parent = self.parent + pos = [self.position] + while type(parent) is ExpressionPointer: + position = parent.position + if position is None: + parent = parent.parent + continue + pos.append(parent.position) + parent = parent.parent + # At this point, we hit the expression, and we have + # the path to reach the position + root = parent + i = pos.pop() + try: + while pos: + if i == 0: + parent = parent._head + else: + parent = parent._leaves[i - 1] + i = pos.pop() + except Exception: + raise MessageException("Part", "span", pos) + + # Now, we have a pointer to a leaf in a true `Expression`. + # Now, set it to the new value. + if i == 0: + parent.set_head(new) + else: + parent.set_leaf(i - 1, new) + + +class SubExpression(object): + """ + This class represents a Subexpression of an existing Expression. + Assignment to a subexpression results in the change of the original Expression. + """ + + def __new__(cls, expr, pos=None): + """ + `expr` can be an `Expression`, a `ExpressionPointer` or + another `SubExpression` + `pos` can be `None`, an integer value or an `Expression` that + indicates a subset of leaves in the original `Expression`. + If `pos` points out to a single whole leaf of `expr`, then + returns an `ExpressionPointer`. + """ + # If pos is a list, take the first element, and + # store the remainder. + if type(pos) in (tuple, list): + pos, rem_pos = pos[0], pos[1:] + if len(rem_pos) == 0: + rem_pos = None + else: + rem_pos = None + + # Trivial conversion: if pos is an `Integer`, convert + # to a Python native int + if type(pos) is Integer: + pos = pos.get_int_value() + # pos == `System`All` + elif type(pos) is Symbol and pos.get_name() == "System`All": + pos = None + elif type(pos) is Expression: + if pos.has_form("System`List", None): + tuple_pos = [i.get_int_value() for i in pos.leaves] + if any([i is None for i in tuple_pos]): + raise MessageException("Part", "pspec", pos) + pos = tuple_pos + elif pos.has_form("System`Span", None): + pos = _pspec_span_to_tuple(pos, expr) + else: + raise MessageException("Part", "pspec", pos) + + if pos is None or type(pos) is int: + if rem_pos is None: + return ExpressionPointer(expr, pos) + else: + return SubExpression(ExpressionPointer(expr, pos), rem_pos) + elif type(pos) is tuple: + self = super(SubExpression, cls).__new__(cls) + self._headp = ExpressionPointer(expr.head, 0) + self._leavesp = [ + SubExpression(ExpressionPointer(expr, k + 1), rem_pos) for k in pos + ] + return self + + def is_atom(self): + return False + + def __str__(self): + return ( + self.head.__str__() + + "[\n" + + ",\n".join(["\t " + leaf.__str__() for leaf in self.leaves]) + + "\n\t]" + ) + + def __repr__(self): + return self.__str__() + + @property + def head(self): + return self._headp + + @head.setter + def head(self, value): + raise ValueError("SubExpression.head is write protected.") + + def get_head_name(self): + return self._headp.parent.get_head_name() + + @property + def leaves(self): + return self._leavesp + + @leaves.setter + def leaves(self, value): + raise ValueError("SubExpression.leaves is write protected.") + + def to_expression(self): + return Expression( + self._headp.to_expression(), + *(leaf.to_expression() for leaf in self._leavesp) + ) + + def replace(self, new): + """ + Asigns `new` to the subexpression, according to the logic of `mathics.core.walk_parts` + """ + if (new.has_form("List", None) or new.get_head_name() == "System`List") and len( + new.leaves + ) == len(self._leavesp): + for leaf, sub_new in zip(self._leavesp, new.leaves): + leaf.replace(sub_new) + else: + for leaf in self._leavesp: + leaf.replace(new) diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 7115d56f5..ad1e008d9 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -6,20 +6,33 @@ import typing from typing import Any, Optional -from mathics.core.convert import sympy_symbol_prefix +# I put this constants here instead of inside `mathics.core.convert` +# to avoid a circular reference. Maybe they should be in its own module. +sympy_symbol_prefix = "_Mathics_User_" +sympy_slot_prefix = "_Mathics_Slot_" -# system_symbols('A', 'B', ...) -> ['System`A', 'System`B', ...] -def system_symbols(*symbols) -> typing.List[str]: - return [ensure_context(s) for s in symbols] +# system_symbols('A', 'B', ...) -> [Symbol('System`A'), Symbol('System`B'), ...] +def system_symbols(*symbols) -> typing.FrozenSet[str]: + """ + Return a frozenset of symbols from a list of names (strings). + We will use this in testing membership, so an immutable object is fine. + + In 2021, we benchmarked frozenset versus list, tuple, and set and frozenset was the fastest. + """ + return frozenset(Symbol(s) for s in symbols) -# system_symbols_dict({'SomeSymbol': ...}) -> {'System`SomeSymbol': ...} + +# system_symbols_dict({'SomeSymbol': ...}) -> {Symbol('System`SomeSymbol'): ...} def system_symbols_dict(d): - return {ensure_context(k): v for k, v in d.items()} + return {Symbol(k): v for k, v in d.items()} def fully_qualified_symbol_name(name) -> bool: + """ + Checks if `name` is a fully qualified symbol name. + """ return ( isinstance(name, str) and "`" in name @@ -213,7 +226,7 @@ def sameQ(self, rhs) -> bool: return id(self) == id(rhs) def get_sequence(self): - if self.get_head().get_name() == "System`Sequence": + if self.get_head() is SymbolSequence: return self.leaves else: return [self] @@ -243,20 +256,14 @@ def do_format(self, evaluation, form): superfluous enclosing formats. """ - formats = system_symbols( - "InputForm", - "OutputForm", - "StandardForm", - "FullForm", - "TraditionalForm", - "TeXForm", - "MathMLForm", - ) + if isinstance(form, str): + form = Symbol(form) + formats = format_symbols evaluation.inc_recursion_depth() try: expr = self - head = self.get_head_name() + head = self.get_head() leaves = self.get_leaves() include_form = False # If the expression is enclosed by a Format @@ -264,12 +271,12 @@ def do_format(self, evaluation, form): # removes the format from the expression. if head in formats and len(leaves) == 1: expr = leaves[0] - if not (form == "System`OutputForm" and head == "System`StandardForm"): + if not (form is SymbolOutputForm and head is SymbolStandardForm): form = head include_form = True unformatted = expr # If form is Fullform, return it without changes - if form == "System`FullForm": + if form is SymbolFullForm: if include_form: expr = self.create_expression(form, expr) expr.unformatted = unformatted @@ -277,32 +284,32 @@ def do_format(self, evaluation, form): # Repeated and RepeatedNull confuse the formatter, # so we need to hardlink their format rules: - if head == "System`Repeated": + if head is SymbolRepeated: if len(leaves) == 1: return self.create_expression( - "System`HoldForm", + SymbolHoldForm, self.create_expression( - "System`Postfix", - self.create_expression("System`List", leaves[0]), + SymbolPostfix, + self.create_expression(SymbolList, leaves[0]), "..", 170, ), ) else: - return self.create_expression("System`HoldForm", expr) - elif head == "System`RepeatedNull": + return self.create_expression(SymbolHoldForm, expr) + elif head is SymbolRepeatedNull: if len(leaves) == 1: return self.create_expression( - "System`HoldForm", + SymbolHoldForm, self.create_expression( - "System`Postfix", - self.create_expression("System`List", leaves[0]), + SymbolPostfix, + self.create_expression(SymbolList, leaves[0]), "...", 170, ), ) else: - return self.create_expression("System`HoldForm", expr) + return self.create_expression(SymbolHoldForm, expr) # If expr is not an atom, looks for formats in its definition # and apply them. @@ -311,7 +318,7 @@ def format_expr(expr): # expr is of the form f[...][...] return None name = expr.get_lookup_name() - formats = evaluation.definitions.get_formats(name, form) + formats = evaluation.definitions.get_formats(name, form.get_name()) for rule in formats: result = rule.apply(expr, evaluation) if result is not None and result != expr: @@ -330,14 +337,14 @@ def format_expr(expr): # iterate. # If the expression is not atomic or of certain # specific cases, iterate over the leaves. - head = expr.get_head_name() + head = expr.get_head() if head in formats: expr = expr.do_format(evaluation, form) elif ( - head != "System`NumberForm" + head is not SymbolNumberForm and not expr.is_atom() - and head != "System`Graphics" - and head != "System`Graphics3D" + and head is not SymbolGraphics + and head is not SymbolGraphics3D ): # print("Not inside graphics or numberform, and not is atom") new_leaves = [leaf.do_format(evaluation, form) for leaf in expr.leaves] @@ -356,9 +363,10 @@ def format(self, evaluation, form, **kwargs) -> "BaseExpression": """ Applies formats associated to the expression, and then calls Makeboxes """ - + if isinstance(form, str): + form = Symbol(form) expr = self.do_format(evaluation, form) - result = self.create_expression(SymbolMakeBoxes, expr, Symbol(form)).evaluate( + result = self.create_expression(SymbolMakeBoxes, expr, form).evaluate( evaluation ) return result @@ -605,9 +613,6 @@ def copy(self, reevaluate=False) -> "Atom": result.original = self return result - def set_positions(self, position=None) -> None: - self.position = position - def get_sort_key(self, pattern_sort=False): if pattern_sort: return [0, 0, 1, 1, 0, 0, 0, 1] @@ -633,7 +638,7 @@ def __new__(cls, name, sympy_dummy=None): self = super(Symbol, cls).__new__(cls) self.name = name self.sympy_dummy = sympy_dummy - # cls.defined_symbols[name] = self + cls.defined_symbols[name] = self return self def __str__(self) -> str: @@ -710,7 +715,7 @@ def get_sort_key(self, pattern_sort=False): def equal2(self, rhs: Any) -> Optional[bool]: """Mathics two-argument Equal (==)""" - if self.sameQ(rhs): + if self is rhs: return True # Booleans are treated like constants, but all other symbols @@ -723,7 +728,13 @@ def equal2(self, rhs: Any) -> Optional[bool]: def sameQ(self, rhs: Any) -> bool: """Mathics SameQ""" - return id(self) == id(rhs) or isinstance(rhs, Symbol) and self.name == rhs.name + return self is rhs + + def __eq__(self, other) -> bool: + return self is other + + def __ne__(self, other) -> bool: + return self is not other def replace_vars(self, vars, options={}, in_scoping=True): assert all(fully_qualified_symbol_name(v) for v in vars) @@ -748,9 +759,7 @@ def is_true(self) -> bool: return self == Symbol("True") def is_numeric(self, evaluation=None) -> bool: - return self.name in system_symbols( - "Pi", "E", "EulerGamma", "GoldenRatio", "MachinePrecision", "Catalan" - ) + return self in system_numeric_constants def __hash__(self): return hash(("Symbol", self.name)) # to distinguish from String @@ -762,9 +771,50 @@ def __getnewargs__(self): return (self.name, self.sympy_dummy) +# Symbols used in this module. + SymbolFalse = Symbol("System`False") +SymbolGraphics = Symbol("System`Graphics") +SymbolGraphics3D = Symbol("System`Graphics3D") +SymbolHoldForm = Symbol("System`HoldForm") SymbolList = Symbol("System`List") +SymbolMachinePrecision = Symbol("MachinePrecision") SymbolMakeBoxes = Symbol("System`MakeBoxes") +SymbolMaxPrecision = Symbol("$MaxPrecision") +SymbolMinPrecision = Symbol("$MinPrecision") SymbolN = Symbol("System`N") SymbolNull = Symbol("System`Null") +SymbolNumberForm = Symbol("System`NumberForm") +SymbolPostfix = Symbol("System`Postfix") +SymbolRepeated = Symbol("System`Repeated") +SymbolRepeatedNull = Symbol("System`RepeatedNull") +SymbolSequence = Symbol("System`Sequence") SymbolTrue = Symbol("System`True") + + +# The available formats. + +format_symbols = system_symbols( + "InputForm", + "OutputForm", + "StandardForm", + "FullForm", + "TraditionalForm", + "TeXForm", + "MathMLForm", +) + + +SymbolInputForm = Symbol("InputForm") +SymbolOutputForm = Symbol("OutputForm") +SymbolStandardForm = Symbol("StandardForm") +SymbolFullForm = Symbol("FullForm") +SymbolTraditionalForm = Symbol("TraditionalForm") +SymbolTeXForm = Symbol("TeXForm") +SymbolMathMLForm = Symbol("MathMLForm") + + +# Used to check if a symbol is `Numeric` without evaluation. +system_numeric_constants = system_symbols( + "Pi", "E", "EulerGamma", "GoldenRatio", "MachinePrecision", "Catalan" +) diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 19410b22c..1ae89c987 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -4,18 +4,26 @@ # Some other common Symbols. This list is sorted in alphabetic order. SymbolAborted = Symbol("$Aborted") +SymbolAlternatives = Symbol("Alternatives") SymbolAssociation = Symbol("Association") +SymbolBlank = Symbol("Blank") SymbolByteArray = Symbol("ByteArray") SymbolComplexInfinity = Symbol("ComplexInfinity") SymbolDirectedInfinity = Symbol("DirectedInfinity") +SymbolDivide = Symbol("Divide") SymbolFailed = Symbol("$Failed") SymbolGreater = Symbol("Greater") +SymbolIndeterminate = Symbol("Indeterminate") SymbolInfinity = Symbol("Infinity") +SymbolInfix = Symbol("Infix") SymbolLess = Symbol("Less") SymbolMachinePrecision = Symbol("MachinePrecision") SymbolMakeBoxes = Symbol("MakeBoxes") +SymbolMinus = Symbol("Minus") +SymbolPattern = Symbol("Pattern") SymbolPlus = Symbol("Plus") SymbolPower = Symbol("Power") +SymbolRowBox = Symbol("RowBox") SymbolRule = Symbol("Rule") SymbolSequence = Symbol("Sequence") SymbolTimes = Symbol("Times") From 9a46bb9d04a966d3fbd5623f7c9cb9af9fa94e08 Mon Sep 17 00:00:00 2001 From: Tiago Cavalcante Trindade Date: Mon, 25 Oct 2021 12:03:08 -0400 Subject: [PATCH 192/193] Replace from_python by a more specific function in some places --- mathics/algorithm/parts.py | 4 ++-- mathics/builtin/datentime.py | 10 +++++----- mathics/builtin/fileformats/xmlformat.py | 2 +- mathics/builtin/files_io/files.py | 8 ++++---- mathics/builtin/files_io/filesystem.py | 16 ++++++++-------- mathics/builtin/files_io/importexport.py | 4 ++-- mathics/builtin/inout.py | 3 +-- mathics/builtin/numbers/numbertheory.py | 12 ++++++------ mathics/builtin/numeric.py | 18 +++++++++--------- mathics/builtin/physchemdata.py | 2 +- 10 files changed, 39 insertions(+), 40 deletions(-) diff --git a/mathics/algorithm/parts.py b/mathics/algorithm/parts.py index 134963d51..1e477cc21 100644 --- a/mathics/algorithm/parts.py +++ b/mathics/algorithm/parts.py @@ -7,7 +7,7 @@ from mathics.core.expression import Expression from mathics.core.symbols import Symbol -from mathics.core.atoms import Integer, from_python +from mathics.core.atoms import Integer from mathics.core.systemsymbols import SymbolInfinity from mathics.core.subexpression import SubExpression @@ -605,7 +605,7 @@ def find_matching_indices_with_levelspec(expr, pattern, evaluation, levelspec=1, continue curr_leave = tree[-1][curr_index[-1]] if match(curr_leave, evaluation) and (len(curr_index) >= lsmin): - found.append([from_python(i) for i in curr_index]) + found.append([Integer(i) for i in curr_index]) curr_index[-1] = curr_index[-1] + 1 n = n - 1 continue diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index 383423337..3752871a6 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -373,7 +373,7 @@ class AbsoluteTime(_DateFormat): def apply_now(self, evaluation): "AbsoluteTime[]" - return from_python(total_seconds(datetime.now() - EPOCH_START)) + return Real(total_seconds(datetime.now() - EPOCH_START)) def apply_spec(self, epochtime, evaluation): "AbsoluteTime[epochtime_]" @@ -386,8 +386,8 @@ def apply_spec(self, epochtime, evaluation): date = _Date(datelist=datelist) tdelta = date.date - EPOCH_START if tdelta.microseconds == 0: - return from_python(int(total_seconds(tdelta))) - return from_python(total_seconds(tdelta)) + return Integer(int(total_seconds(tdelta))) + return Real(total_seconds(tdelta)) class AbsoluteTiming(Builtin): @@ -561,7 +561,7 @@ def intdiv(a, b, flag=True): if len(result) == 1: if pyunits[0] == "Day": - return from_python(result[0][0]) + return Integer(result[0][0]) return from_python(result[0]) return from_python(result) @@ -944,7 +944,7 @@ def apply(self, epochtime, form, evaluation): datestrs.append(tmp) - return from_python("".join(datestrs)) + return String("".join(datestrs)) class DateStringFormat(Predefined): diff --git a/mathics/builtin/fileformats/xmlformat.py b/mathics/builtin/fileformats/xmlformat.py index 3bd7e5f06..5d73239d3 100644 --- a/mathics/builtin/fileformats/xmlformat.py +++ b/mathics/builtin/fileformats/xmlformat.py @@ -103,7 +103,7 @@ def attributes(): if name == "xmlns": name = _namespace_key else: - name = from_python(name) + name = String(name) yield Expression("Rule", name, from_python(value)) if namespace is None or namespace == default_namespace: diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 4d8071d99..3feb8906d 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -608,7 +608,7 @@ def apply(self, channel, expr, evaluation): expr = Expression("Row", Expression("List", *expr)) evaluation.format = "text" - text = evaluation.format_output(from_python(expr)) + text = evaluation.format_output(expr) stream.io.write(str(text) + "\n") return SymbolNull @@ -2333,7 +2333,7 @@ def apply(self, path, evaluation, options): result = result[:-1] for res in result: - evaluation.print_out(from_python(res)) + evaluation.print_out(String(res)) return SymbolNull @@ -2416,7 +2416,7 @@ def apply_input(self, name, n, evaluation): evaluation.message("General", "openx", name) return - return from_python(stream.io.tell()) + return Integer(stream.io.tell()) def apply_output(self, name, n, evaluation): "StreamPosition[OutputStream[name_, n_]]" @@ -2498,7 +2498,7 @@ def apply_input(self, name, n, m, evaluation): except IOError: evaluation.message("SetStreamPosition", "seek") - return from_python(stream.io.tell()) + return Integer(stream.io.tell()) def apply_output(self, name, n, m, evaluation): "SetStreamPosition[OutputStream[name_, n_], m_]" diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py index 961033438..231732c2b 100644 --- a/mathics/builtin/files_io/filesystem.py +++ b/mathics/builtin/files_io/filesystem.py @@ -15,7 +15,7 @@ from mathics.version import __version__ # noqa used in loading to check consistency. from mathics.core.expression import Expression -from mathics.core.atoms import String, from_python +from mathics.core.atoms import Real, Integer, String, from_python from mathics.core.symbols import ( Symbol, SymbolFalse, @@ -684,7 +684,7 @@ def apply(self, filename, evaluation, options): path = filename.to_python()[1:-1] filename_base, filename_ext = osp.splitext(path) - return from_python(filename_base) + return String(filename_base) class FileByteCount(Builtin): @@ -727,7 +727,7 @@ def apply(self, filename, evaluation): e.message(evaluation) return - return from_python(count) + return Integer(count) class FileDate(Builtin): @@ -825,7 +825,7 @@ def apply(self, path, timetype, evaluation): ).to_python(n_evaluation=evaluation) result += epochtime - return Expression("DateList", from_python(result)) + return Expression("DateList", Real(result)) def apply_default(self, path, evaluation): "FileDate[path_]" @@ -898,7 +898,7 @@ def apply(self, filename, evaluation, options): path = filename.to_python()[1:-1] filename_base, filename_ext = osp.splitext(path) filename_ext = filename_ext.lstrip(".") - return from_python(filename_ext) + return String(filename_ext) class FileHash(Builtin): @@ -1093,7 +1093,7 @@ def apply(self, pathlist, evaluation, options): else: result = osp.join(*py_pathlist) - return from_python(result) + return String(result) class FileType(Builtin): @@ -1416,7 +1416,7 @@ class FileNameTake(Builtin): def apply(self, filename, evaluation, options): "FileNameTake[filename_String, OptionsPattern[FileBaseName]]" path = pathlib.Path(filename.to_python()[1:-1]) - return from_python(path.name) + return String(path.name) def apply_n(self, filename, n, evaluation, options): "FileNameTake[filename_String, n_Integer, OptionsPattern[FileBaseName]]" @@ -1426,7 +1426,7 @@ def apply_n(self, filename, n, evaluation, options): subparts = parts[:n_int] else: subparts = parts[n_int:] - return from_python(str(pathlib.PurePath(*subparts))) + return String(str(pathlib.PurePath(*subparts))) class FindList(Builtin): diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index dd227f3d9..0e548919d 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -1488,7 +1488,7 @@ def get_results(tmp_function, findfile): result = defaults.get(default_element.get_string_value()) if result is None: evaluation.message( - "Import", "noelem", default_element, from_python(filetype) + "Import", "noelem", default_element, String(filetype) ) evaluation.predetermined_out = current_predetermined_out return SymbolFailed @@ -1539,7 +1539,7 @@ def get_results(tmp_function, findfile): return defaults[el] else: evaluation.message( - "Import", "noelem", from_python(el), from_python(filetype) + "Import", "noelem", from_python(el), String(filetype) ) evaluation.predetermined_out = current_predetermined_out return SymbolFailed diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index c18565cea..b974c776f 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -34,7 +34,6 @@ StringFromPython, Integer, Real, - from_python, MachineReal, PrecisionReal, ) @@ -2706,6 +2705,6 @@ def apply_makeboxes(self, expr, n, f, evaluation): return evaluation.message("BaseForm", "basf", n) if f is SymbolOutputForm: - return from_python("%s_%d" % (val, base)) + return String("%s_%d" % (val, base)) else: return Expression(SymbolSubscriptBox, String(val), String(base)) diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index fdf66ed74..8f3402ca0 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -322,7 +322,7 @@ def apply(self, n, b, evaluation): while py_n % (py_b ** result) == 0: result += 1 - return from_python(result - 1) + return Integer(result - 1) class MantissaExponent(Builtin): @@ -492,7 +492,7 @@ def apply(self, n, k, evaluation): py_n = n.to_python(n_evaluation=evaluation) if py_k >= 0: - return from_python(sympy.ntheory.nextprime(py_n, py_k)) + return Integer(sympy.ntheory.nextprime(py_n, py_k)) # Hack to get earlier primes result = n.to_python() @@ -501,9 +501,9 @@ def apply(self, n, k, evaluation): result = sympy.ntheory.prevprime(result) except ValueError: # No earlier primes - return from_python(-1 * sympy.ntheory.nextprime(0, py_k - i)) + return Integer(-1 * sympy.ntheory.nextprime(0, py_k - i)) - return from_python(result) + return Integer(result) class PartitionsP(SympyFunction): @@ -599,7 +599,7 @@ class PrimePi(SympyFunction): def apply(self, n, evaluation): "PrimePi[n_?NumericQ]" result = sympy.ntheory.primepi(n.to_python(n_evaluation=evaluation)) - return from_python(result) + return Integer(result) class PrimePowerQ(Builtin): @@ -743,7 +743,7 @@ def apply(self, interval, n, evaluation): try: if py_n == 1: - return from_python(sympy.ntheory.randprime(imin, imax + 1)) + return Integer(sympy.ntheory.randprime(imin, imax + 1)) return from_python( [sympy.ntheory.randprime(imin, imax + 1) for i in range(py_n)] ) diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 7917b3f46..a4614f7d0 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -1621,8 +1621,8 @@ def apply_rational_with_base(self, n, b, evaluation): leaves = [] for x in head: - if not x == "0": - leaves.append(from_python(int(x))) + if x != "0": + leaves.append(Integer(int(x))) leaves.append(from_python(tails)) list_str = Expression(SymbolList, *leaves) return Expression(SymbolList, list_str, exp) @@ -1630,7 +1630,7 @@ def apply_rational_with_base(self, n, b, evaluation): def apply_rational_without_base(self, n, evaluation): "%(name)s[n_Rational]" - return self.apply_rational_with_base(n, from_python(10), evaluation) + return self.apply_rational_with_base(n, Integer(10), evaluation) def apply(self, n, evaluation): "%(name)s[n_]" @@ -1730,11 +1730,11 @@ def apply_with_base(self, n, b, evaluation, nr_elements=None, pos=None): if x == "e" or x == "E": break # Convert to Mathics' list format - leaves.append(from_python(int(x))) + leaves.append(Integer(int(x))) if not rational_no: while len(leaves) < display_len: - leaves.append(from_python(0)) + leaves.append(Integer0) if nr_elements is not None: # display_len == nr_elements @@ -1744,7 +1744,7 @@ def apply_with_base(self, n, b, evaluation, nr_elements=None, pos=None): else: if isinstance(n, Integer): while len(leaves) < nr_elements: - leaves.append(from_python(0)) + leaves.append(Integer0) else: # Adding Indeterminate if the length is greater than the precision while len(leaves) < nr_elements: @@ -1848,13 +1848,13 @@ def compute(user_hash, py_hashtype, py_format): user_hash(h.update) res = h.hexdigest() if py_format in ("HexString", "HexStringLittleEndian"): - return from_python(res) + return String(res) res = int(res, 16) if py_format == "DecimalString": - return from_python(str(res)) + return String(str(res)) elif py_format == "ByteArray": return from_python(bytearray(res)) - return from_python(res) + return Integer(res) def apply(self, expr, hashtype, outformat, evaluation): "Hash[expr_, hashtype_String, outformat_String]" diff --git a/mathics/builtin/physchemdata.py b/mathics/builtin/physchemdata.py index b59b466eb..db848bcf4 100644 --- a/mathics/builtin/physchemdata.py +++ b/mathics/builtin/physchemdata.py @@ -143,7 +143,7 @@ def apply_name(self, expr, prop, evaluation): return # Enter in the next if, but with expr being the index - expr = from_python(indx) + expr = Integer(indx) if isinstance(expr, Integer): py_n = expr.value py_prop = prop.to_python() From 461c8a21ad0bf201b82f3504f1f1730b2cd89993 Mon Sep 17 00:00:00 2001 From: mmatera Date: Sat, 6 Nov 2021 16:22:11 -0300 Subject: [PATCH 193/193] leaf.unevaluate -> unevaluated_leaves[leaf] in expression.evaluate_next --- mathics/core/expression.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 64f8472c4..ee0f5d86f 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -822,8 +822,9 @@ def eval_range(indices): new = new.flatten_sequence(evaluation) leaves = new._leaves + unevaluated_leaves = dict({}) for leaf in leaves: - leaf.unevaluated = False + unevaluated_leaves[id(leaf)] = False if "System`HoldAllComplete" not in attributes: dirty_leaves = None @@ -833,7 +834,7 @@ def eval_range(indices): if dirty_leaves is None: dirty_leaves = list(leaves) dirty_leaves[index] = leaf._leaves[0] - dirty_leaves[index].unevaluated = True + unevaluated_leaves[id(dirty_leaves[index])] = True if dirty_leaves: new = Expression(head) @@ -842,7 +843,7 @@ def eval_range(indices): def flatten_callback(new_leaves, old): for leaf in new_leaves: - leaf.unevaluated = old.unevaluated + unevaluated_leaves[id(leaf)] = unevaluated_leaves[id(old)] if "System`Flat" in attributes: new = new.flatten(new._head, callback=flatten_callback) @@ -893,7 +894,7 @@ def rules(): # Expression did not change, re-apply Unevaluated for index, leaf in enumerate(new._leaves): - if leaf.unevaluated: + if unevaluated_leaves[id(leaf)]: if dirty_leaves is None: dirty_leaves = list(new._leaves) dirty_leaves[index] = Expression("Unevaluated", leaf)