From d150cb5948bc81a5c9b674a8d6b93c77e6ff73cb Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 29 Nov 2022 05:22:49 -0500 Subject: [PATCH 001/121] Move eval_ fns out from math_ops and strings ... and go over docstring tags for these. --- SYMBOLS_MANIFEST.txt | 2 +- mathics/builtin/atomic/strings.py | 138 +++++++++++++---------- mathics/builtin/matrices/constrmatrix.py | 1 - mathics/builtin/vectors/math_ops.py | 52 +-------- mathics/data/.gitignore | 1 + mathics/eval/math_ops.py | 52 +++++++++ mathics/eval/strings.py | 13 +++ 7 files changed, 149 insertions(+), 110 deletions(-) create mode 100644 mathics/eval/math_ops.py create mode 100644 mathics/eval/strings.py diff --git a/SYMBOLS_MANIFEST.txt b/SYMBOLS_MANIFEST.txt index 7a9a64774..1e2e49d9d 100644 --- a/SYMBOLS_MANIFEST.txt +++ b/SYMBOLS_MANIFEST.txt @@ -462,7 +462,7 @@ System`Hash System`Haversine System`Head System`HermiteH -System`HexidecimalCharacter +System`HexadecimalCharacter System`Histogram System`Hold System`HoldAll diff --git a/mathics/builtin/atomic/strings.py b/mathics/builtin/atomic/strings.py index 67afbfd23..0f6a03248 100644 --- a/mathics/builtin/atomic/strings.py +++ b/mathics/builtin/atomic/strings.py @@ -7,50 +7,30 @@ import re import unicodedata from binascii import hexlify, unhexlify -from heapq import heappush, heappop +from heapq import heappop, heappush from typing import Any, List +from mathics_scanner import TranslateError -from mathics.builtin.base import ( - Builtin, - Test, - Predefined, - PrefixOperator, -) - -from mathics.core.atoms import ( - String, - Integer, - Integer0, - Integer1, -) +from mathics.builtin.base import Builtin, Predefined, PrefixOperator, Test +from mathics.core.atoms import Integer, Integer0, Integer1, String from mathics.core.attributes import A_LISTABLE, A_PROTECTED -from mathics.core.evaluation import Evaluation -from mathics.core.expression import Expression from mathics.core.convert.expression import to_mathics_list -from mathics.core.convert.op import ascii_op_to_unicode from mathics.core.convert.python import from_bool -from mathics.core.element import BaseElement +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.parser import MathicsFileLineFeeder, parse -from mathics.core.symbols import ( - Symbol, - SymbolTrue, -) - +from mathics.core.symbols import Symbol, SymbolTrue from mathics.core.systemsymbols import ( SymbolBlank, - SymbolFailed, SymbolDirectedInfinity, + SymbolFailed, SymbolInputForm, SymbolOutputForm, ) - -from mathics.eval.makeboxes import format_element - +from mathics.eval.strings import eval_ToString from mathics.settings import SYSTEM_CHARACTER_ENCODING -from mathics_scanner import TranslateError - SymbolToExpression = Symbol("ToExpression") @@ -271,7 +251,7 @@ def recurse(x, quantifiers=q): "System`EndOfString": r"\Z", "System`WordBoundary": r"\b", "System`LetterCharacter": r"(?u)[^\W_0-9]", - "System`HexidecimalCharacter": r"[0-9a-fA-F]", + "System`HexadecimalCharacter": r"[0-9a-fA-F]", }.get(expr.get_name()) if expr.has_form("CharacterRange", 2): @@ -429,7 +409,10 @@ def to_python_encoding(encoding): class Alphabet(Builtin): """ -
+ + :WMA link: + https://reference.wolfram.com/language/ref/Alphabet.html +
'Alphabet'[]
gives the list of lowercase letters a-z in the English alphabet . @@ -473,6 +456,9 @@ def apply(self, alpha, evaluation): class CharacterEncoding(Predefined): """ + + :WMA link: + https://reference.wolfram.com/language/ref/CharacterEncoding.html
'CharacterEncoding'
specifies the default raw character encoding to use for input and output when no encoding is explicitly specified. Initially this is set to '$SystemCharacterEncoding'. @@ -497,6 +483,9 @@ class CharacterEncoding(Predefined): class CharacterEncodings(Predefined): """ + + :WMA link: + https://reference.wolfram.com/language/ref/$CharacterEncodings.html
'$CharacterEncodings'
stores the list of available character encodings. @@ -514,14 +503,17 @@ class CharacterEncodings(Predefined): summary_text = "available character encodings" -class HexidecimalCharacter(Builtin): +class HexadecimalCharacter(Builtin): """ + + :WMA link: + https://reference.wolfram.com/language/ref/HexadecimalCharacter.html
-
'HexidecimalCharacter' +
'HexadecimalCharacter'
represents the characters 0-9, a-f and A-F.
- >> StringMatchQ[#, HexidecimalCharacter] & /@ {"a", "1", "A", "x", "H", " ", "."} + >> StringMatchQ[#, HexadecimalCharacter] & /@ {"a", "1", "A", "x", "H", " ", "."} = {True, True, True, False, False, False, False} """ @@ -532,10 +524,12 @@ class HexidecimalCharacter(Builtin): # in mathics.builtin.box for now. class InterpretedBox(PrefixOperator): r""" + + :WMA link: + https://reference.wolfram.com/language/ref/InterpretedBox.html
'InterpretedBox[$box$]' -
is the ad hoc fullform for \! $box$. just - for internal use... +
is the ad hoc fullform for \! $box$. just for internal use...
>> \! \(2+2\) = 4 @@ -557,6 +551,9 @@ def apply_dummy(self, boxes, evaluation: Evaluation): class LetterNumber(Builtin): r""" + + :WMA link: + https://reference.wolfram.com/language/ref/LetterNumber.html
'LetterNumber'[$c$]
returns the position of the character $c$ in the English alphabet. @@ -673,8 +670,11 @@ def apply(self, chars: List[Any], evaluation): class NumberString(Builtin): """ + + :WMA link: + https://reference.wolfram.com/language/ref/NumberString.html
-
'NumberString' +
'NumberString'
represents the characters in a number.
@@ -693,9 +693,12 @@ class NumberString(Builtin): class RemoveDiacritics(Builtin): """ + + :WMA link: + https://reference.wolfram.com/language/ref/RemoveDiacritics.html
-
'RemoveDiacritics[$s$]' -
returns a version of $s$ with all diacritics removed. +
'RemoveDiacritics[$s$]' +
returns a version of $s$ with all diacritics removed.
>> RemoveDiacritics["en prononçant pêcher et pécher"] @@ -804,6 +807,9 @@ def convert_rule(r): class String_(Builtin): """ + + :WMA link: + https://reference.wolfram.com/language/ref/String.html
'String'
is the head of strings. @@ -829,13 +835,18 @@ class String_(Builtin): class StringContainsQ(Builtin): """ + + :WMA link: + https://reference.wolfram.com/language/ref/StringContainsQ.html
-
'StringContainsQ["$string$", $patt$]' -
returns True if any part of $string$ matches $patt$, and returns False otherwise. -
'StringContainsQ[{"s1", "s2", ...}, patt]' -
returns the list of results for each element of string list. -
'StringContainsQ[patt]' -
represents an operator form of StringContainsQ that can be applied to an expression. +
'StringContainsQ["$string$", $patt$]' +
returns True if any part of $string$ matches $patt$, and returns False otherwise. + +
'StringContainsQ[{"s1", "s2", ...}, patt]' +
returns the list of results for each element of string list. + +
'StringContainsQ[patt]' +
represents an operator form of StringContainsQ that can be applied to an expression.
>> StringContainsQ["mathics", "m" ~~ __ ~~ "s"] @@ -926,8 +937,11 @@ def apply(self, string, patt, evaluation, options): class StringQ(Test): """ + + :WMA link: + https://reference.wolfram.com/language/ref/StringQ.html
-
'StringQ[$expr$]' +
'StringQ[$expr$]'
returns 'True' if $expr$ is a 'String', or 'False' otherwise.
@@ -947,6 +961,9 @@ def test(self, expr): class StringRepeat(Builtin): """ + + :WMA link: + https://reference.wolfram.com/language/ref/StringRepeat.html
'StringRepeat["$string$", $n$]'
gives $string$ repeated $n$ times. @@ -1000,6 +1017,9 @@ def apply_truncated(self, s, n, m, expression, evaluation): class SystemCharacterEncoding(Predefined): """ + + :WMA link: + https://reference.wolfram.com/language/ref/$SystemCharacterEncoding.html
$SystemCharacterEncoding
gives the default character encoding of the system. @@ -1022,6 +1042,9 @@ class SystemCharacterEncoding(Predefined): class ToExpression(Builtin): r""" + + :WMA link: + https://reference.wolfram.com/language/ref/ToExpression.html
'ToExpression[$input$]'
inteprets a given string as Mathics input. @@ -1148,16 +1171,11 @@ def apply_empty(self, evaluation: Evaluation): return -def eval_ToString( - expr: BaseElement, form: Symbol, encoding: String, evaluation: Evaluation -) -> String: - boxes = format_element(expr, evaluation, form, encoding=encoding) - text = boxes.boxes_to_text(evaluation=evaluation) - return String(text) - - class ToString(Builtin): """ + + :WMA link: + https://reference.wolfram.com/language/ref/ToString.html
'ToString[$expr$]'
returns a string representation of $expr$. @@ -1206,9 +1224,12 @@ def apply_form(self, expr, form, evaluation, options): class Transliterate(Builtin): """ + + :WMA link: + https://reference.wolfram.com/language/ref/Transliterate.html
-
'Transliterate[$s$]' -
transliterates a text in some script into an ASCII string. +
'Transliterate[$s$]' +
transliterates a text in some script into an ASCII string.
ASCII translateration examples: @@ -1240,8 +1261,11 @@ def apply(self, s, evaluation: Evaluation): class Whitespace(Builtin): r""" + + :WMA link: + https://reference.wolfram.com/language/ref/Whitespace.html
-
'Whitespace' +
'Whitespace'
represents a sequence of whitespace characters.
diff --git a/mathics/builtin/matrices/constrmatrix.py b/mathics/builtin/matrices/constrmatrix.py index 22608e3f1..d8a7ae761 100644 --- a/mathics/builtin/matrices/constrmatrix.py +++ b/mathics/builtin/matrices/constrmatrix.py @@ -8,7 +8,6 @@ from mathics.builtin.base import Builtin from mathics.core.atoms import Integer0 -from mathics.core.expression import Expression from mathics.core.list import ListExpression diff --git a/mathics/builtin/vectors/math_ops.py b/mathics/builtin/vectors/math_ops.py index fc6c5455b..7e3cb5de8 100644 --- a/mathics/builtin/vectors/math_ops.py +++ b/mathics/builtin/vectors/math_ops.py @@ -4,62 +4,12 @@ Mathematical Operations """ -from typing import Optional import sympy from mathics.builtin.base import Builtin, SympyFunction -from mathics.core.atoms import Integer, Real, String from mathics.core.attributes import A_PROTECTED -from mathics.core.evaluation import Evaluation -from mathics.core.expression import Expression - from mathics.core.convert.sympy import from_sympy, to_sympy_matrix -from mathics.core.symbols import Symbol - - -def eval_2_Norm(m: Expression, evaluation: Evaluation) -> Optional[Expression]: - """ - 2-Norm[] evaluation function - """ - sympy_m = to_sympy_matrix(m) - if sympy_m is None: - return evaluation.message("Norm", "nvm") - - return from_sympy(sympy_m.norm()) - - -def eval_p_norm( - m: Expression, p: Expression, evaluation: Evaluation -) -> Optional[Expression]: - """ - p2-Norm[] evaluation function - """ - if isinstance(p, Symbol): - sympy_p = p.to_sympy() - elif isinstance(p, String): - sympy_p = p.value - if sympy_p == "Frobenius": - sympy_p = "fro" - elif isinstance(p, (Real, Integer)) and p.to_python() >= 1: - sympy_p = p.to_sympy() - else: - return evaluation.message("Norm", "ptype", p) - - if sympy_p is None: - return - matrix = to_sympy_matrix(m) - - if matrix is None: - return evaluation.message("Norm", "nvm") - if len(matrix) == 0: - return - - try: - res = matrix.norm(sympy_p) - except NotImplementedError: - return evaluation.message("Norm", "normnotimplemented") - - return from_sympy(res) +from mathics.eval.math_ops import eval_2_Norm, eval_p_norm class Cross(Builtin): diff --git a/mathics/data/.gitignore b/mathics/data/.gitignore index ddd675846..280c1b447 100644 --- a/mathics/data/.gitignore +++ b/mathics/data/.gitignore @@ -1 +1,2 @@ /doc_tex_data.pcl +/op-tables.json diff --git a/mathics/eval/math_ops.py b/mathics/eval/math_ops.py new file mode 100644 index 000000000..7925fe7d6 --- /dev/null +++ b/mathics/eval/math_ops.py @@ -0,0 +1,52 @@ +from typing import Optional + +from mathics.core.atoms import Integer, Real, String +from mathics.core.convert.sympy import from_sympy, to_sympy_matrix +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol + + +def eval_2_Norm(m: Expression, evaluation: Evaluation) -> Optional[Expression]: + """ + 2-Norm[] evaluation function + """ + sympy_m = to_sympy_matrix(m) + if sympy_m is None: + return evaluation.message("Norm", "nvm") + + return from_sympy(sympy_m.norm()) + + +def eval_p_norm( + m: Expression, p: Expression, evaluation: Evaluation +) -> Optional[Expression]: + """ + p2-Norm[] evaluation function + """ + if isinstance(p, Symbol): + sympy_p = p.to_sympy() + elif isinstance(p, String): + sympy_p = p.value + if sympy_p == "Frobenius": + sympy_p = "fro" + elif isinstance(p, (Real, Integer)) and p.to_python() >= 1: + sympy_p = p.to_sympy() + else: + return evaluation.message("Norm", "ptype", p) + + if sympy_p is None: + return + matrix = to_sympy_matrix(m) + + if matrix is None: + return evaluation.message("Norm", "nvm") + if len(matrix) == 0: + return + + try: + res = matrix.norm(sympy_p) + except NotImplementedError: + return evaluation.message("Norm", "normnotimplemented") + + return from_sympy(res) diff --git a/mathics/eval/strings.py b/mathics/eval/strings.py new file mode 100644 index 000000000..1d92f7ae0 --- /dev/null +++ b/mathics/eval/strings.py @@ -0,0 +1,13 @@ +from mathics.core.atoms import String +from mathics.core.element import BaseElement +from mathics.core.evaluation import Evaluation +from mathics.core.symbols import Symbol +from mathics.eval.makeboxes import format_element + + +def eval_ToString( + expr: BaseElement, form: Symbol, encoding: String, evaluation: Evaluation +) -> String: + boxes = format_element(expr, evaluation, form, encoding=encoding) + text = boxes.boxes_to_text(evaluation=evaluation) + return String(text) From c41690df52036de4f0e497ce49f5cef3f81494b0 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 29 Nov 2022 05:31:32 -0500 Subject: [PATCH 002/121] Go over url links in changed modules; apply->eval --- mathics/builtin/atomic/strings.py | 30 ++++++++++++++--------------- mathics/builtin/vectors/math_ops.py | 27 ++++++++++++++++++++------ 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/mathics/builtin/atomic/strings.py b/mathics/builtin/atomic/strings.py index 0f6a03248..8e6a6f182 100644 --- a/mathics/builtin/atomic/strings.py +++ b/mathics/builtin/atomic/strings.py @@ -440,7 +440,7 @@ class Alphabet(Builtin): summary_text = "lowercase letters in an alphabet" - def apply(self, alpha, evaluation): + def eval(self, alpha, evaluation): """Alphabet[alpha_String]""" alphakey = alpha.value alphakey = alphabet_alias.get(alphakey, alphakey) @@ -539,7 +539,7 @@ class InterpretedBox(PrefixOperator): precedence = 670 summary_text = "interpret boxes as an expression" - def apply_dummy(self, boxes, evaluation: Evaluation): + def eval_dummy(self, boxes, evaluation: Evaluation): """InterpretedBox[boxes_]""" # TODO: the following is a very raw and dummy way to # handle these expressions. @@ -604,7 +604,7 @@ class LetterNumber(Builtin): summary_text = "position of a letter in an alphabet" - def apply_alpha_str(self, chars: List[Any], alpha: String, evaluation): + def eval_alpha_str(self, chars: List[Any], alpha: String, evaluation): "LetterNumber[chars_, alpha_String]" alphakey = alpha.value alphakey = alphabet_alias.get(alphakey, alphakey) @@ -643,7 +643,7 @@ def apply_alpha_str(self, chars: List[Any], alpha: String, evaluation): return evaluation.message(self.__class__.__name__, "nas", chars) return None - def apply(self, chars: List[Any], evaluation): + def eval(self, chars: List[Any], evaluation): "LetterNumber[chars_]" start_ord = ord("a") - 1 @@ -661,7 +661,7 @@ def apply(self, chars: List[Any], evaluation): elif chars.has_form("List", 1, None): result = [] for element in chars.elements: - result.append(self.apply(element, evaluation)) + result.append(self.eval(element, evaluation)) return ListExpression(*result) else: return evaluation.message(self.__class__.__name__, "nas", chars) @@ -710,7 +710,7 @@ class RemoveDiacritics(Builtin): summary_text = "remove diacritics" - def apply(self, s, evaluation: Evaluation): + def eval(self, s, evaluation: Evaluation): "RemoveDiacritics[s_String]" return String( unicodedata.normalize("NFKD", s.value) @@ -928,7 +928,7 @@ class StringContainsQ(Builtin): summary_text = "test whether a pattern matches with a substring" - def apply(self, string, patt, evaluation, options): + def eval(self, string, patt, evaluation, options): "StringContainsQ[string_, patt_, OptionsPattern[%(name)s]]" return _pattern_search( self.__class__.__name__, string, patt, evaluation, options, True @@ -990,7 +990,7 @@ class StringRepeat(Builtin): summary_text = "build a string by concatenating repetitions" - def apply(self, s, n, expression, evaluation): + def eval(self, s, n, expression, evaluation): "StringRepeat[s_String, n_]" py_n = n.value if isinstance(n, Integer) else 0 if py_n < 1: @@ -998,7 +998,7 @@ def apply(self, s, n, expression, evaluation): else: return String(s.value * py_n) - def apply_truncated(self, s, n, m, expression, evaluation): + def eval_truncated(self, s, n, m, expression, evaluation): "StringRepeat[s_String, n_Integer, m_Integer]" # The above rule insures that n and m are boht Integer type py_n = n.value @@ -1107,7 +1107,7 @@ class ToExpression(Builtin): } summary_text = "build an expression from formatted text" - def apply(self, seq, evaluation: Evaluation): + def eval(self, seq, evaluation: Evaluation): "ToExpression[seq__]" # Organise Arguments @@ -1163,7 +1163,7 @@ def apply(self, seq, evaluation: Evaluation): return result - def apply_empty(self, evaluation: Evaluation): + def eval_empty(self, evaluation: Evaluation): "ToExpression[]" evaluation.message( "ToExpression", "argb", "ToExpression", Integer0, Integer1, Integer(3) @@ -1212,11 +1212,11 @@ class ToString(Builtin): summary_text = "format an expression and produce a string" - def apply_default(self, value, evaluation, options): + def eval_default(self, value, evaluation, options): "ToString[value_, OptionsPattern[ToString]]" - return self.apply_form(value, SymbolOutputForm, evaluation, options) + return self.eval_form(value, SymbolOutputForm, evaluation, options) - def apply_form(self, expr, form, evaluation, options): + def eval_form(self, expr, form, evaluation, options): "ToString[expr_, form_, OptionsPattern[ToString]]" encoding = options["System`CharacterEncoding"] return eval_ToString(expr, form, encoding.value, evaluation) @@ -1252,7 +1252,7 @@ class Transliterate(Builtin): requires = ("unidecode",) summary_text = "transliterate an UTF string in different alphabets to ASCII" - def apply(self, s, evaluation: Evaluation): + def eval(self, s, evaluation: Evaluation): "Transliterate[s_String]" from unidecode import unidecode diff --git a/mathics/builtin/vectors/math_ops.py b/mathics/builtin/vectors/math_ops.py index 7e3cb5de8..941efec61 100644 --- a/mathics/builtin/vectors/math_ops.py +++ b/mathics/builtin/vectors/math_ops.py @@ -14,7 +14,13 @@ class Cross(Builtin): """ - :Cross product: https://en.wikipedia.org/wiki/Cross_product (:SymPy: https://docs.sympy.org/latest/modules/physics/vector/api/functions.html#sympy.physics.vector.functions.cross, :WMA: https://reference.wolfram.com/language/ref/Cross.html) + + :Cross product: + https://en.wikipedia.org/wiki/Cross_product ( + :SymPy: + https://docs.sympy.org/latest/modules/physics/vector/api/functions.html#sympy.physics.vector.functions.cross, + :WMA: + https://reference.wolfram.com/language/ref/Cross.html)
'Cross[$a$, $b$]' @@ -57,7 +63,7 @@ class Cross(Builtin): rules = {"Cross[{x_, y_}]": "{-y, x}"} summary_text = "vector cross product" - def apply(self, a, b, evaluation): + def eval(self, a, b, evaluation): "Cross[a_, b_]" a = to_sympy_matrix(a) b = to_sympy_matrix(b) @@ -74,7 +80,11 @@ def apply(self, a, b, evaluation): class Curl(SympyFunction): """ - :Curl: https://en.wikipedia.org/wiki/Curl_(mathematics) (:SymPy: https://docs.sympy.org/latest/modules/vector/api/vectorfunctions.html#sympy.vector.curl, :WMA: https://reference.wolfram.com/language/ref/Curl.html) + + :Curl: + https://en.wikipedia.org/wiki/Curl_(mathematics) ( + :SymPy: https://docs.sympy.org/latest/modules/vector/api/vectorfunctions.html#sympy.vector.curl, + :WMA: https://reference.wolfram.com/language/ref/Curl.html)
'Curl[{$f1$, $f2$}, {$x1$, $x2$}]' @@ -115,7 +125,12 @@ class Curl(SympyFunction): class Norm(Builtin): """ - :Matrix norms induced by vector p-norms: https://en.wikipedia.org/wiki/Matrix_norm#Matrix_norms_induced_by_vector_p-norms (:SymPy: https://docs.sympy.org/latest/modules/matrices/matrices.html#sympy.matrices.matrices.MatrixBase.norm, :WMA: https://reference.wolfram.com/language/ref/Norm.html) + + :Matrix norms induced by vector p-norms: https://en.wikipedia.org/wiki/Matrix_norm#Matrix_norms_induced_by_vector_p-norms ( + :SymPy: + https://docs.sympy.org/latest/modules/matrices/matrices.html#sympy.matrices.matrices.MatrixBase.norm, + :WMA: + https://reference.wolfram.com/language/ref/Norm.html)
'Norm[$m$, $p$]' @@ -171,11 +186,11 @@ class Norm(Builtin): } summary_text = "norm of a vector or matrix" - def apply_two_norm(self, m, evaluation): + def eval_two_norm(self, m, evaluation): "Norm[m_]" return eval_2_Norm(m, evaluation) - def apply_p_norm(self, m, p, evaluation): + def eval_p_norm(self, m, p, evaluation): "Norm[m_, p_]" return eval_p_norm(m, p, evaluation) From 9774aacf1a3e64b8e752aaf3c6e055a82f0b8fd2 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 29 Nov 2022 08:37:55 -0500 Subject: [PATCH 003/121] Document some top-level python modules Note: the decriptive text will also be duplicated in the Mathics Developer's Guide. --- mathics/builtin/__init__.py | 19 +++++++++++++++++-- mathics/compile/__init__.py | 8 +++++++- mathics/core/__init__.py | 13 +++++++++++++ mathics/core/convert/__init__.py | 6 ++++++ mathics/core/parser/__init__.py | 22 ++++++++++++++++++++++ mathics/eval/__init__.py | 14 ++++++++------ 6 files changed, 73 insertions(+), 9 deletions(-) diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index d9d3cfbb4..584d3998f 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -1,8 +1,23 @@ # -*- coding: utf-8 -*- """ -Mathics Built-in Functions and Variables. +Mathics Builtin Functions and Variables. -Mathics has over a thousand Built-in Functions and variables, all of which are defined here. +Mathics has over a thousand Built-in functions and variables, all of +which are defined here. + +Note that there are other modules to collect specific aspects a +Builtin, such as ``mathics.eval`` for evaluation specifics, or +``mathics.format`` for rendering details, or ``mathics.compile`` for +compilation details. + +What remains here is then mostly the top-level definition of a Mathics +Builtin, and attributes that have not been segregated elsewhere such +as has been done for one of the other modules listed above. + +A Mathics Builtin is implemented one of a particular kind of Python +class. Within these classes class variables give properties of the +builtin class such as the Builtin's Attributes, it Information text, +among other things. """ import glob diff --git a/mathics/compile/__init__.py b/mathics/compile/__init__.py index dd1ab6150..819f007d8 100644 --- a/mathics/compile/__init__.py +++ b/mathics/compile/__init__.py @@ -1,5 +1,11 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- +""" +Mathics ``Compile`` implementation. + +Here we have routines for compiling Mathics code. + +At present, we use LLVM for this. +""" try: import llvmlite diff --git a/mathics/core/__init__.py b/mathics/core/__init__.py index 40a96afc6..ee0e0082a 100644 --- a/mathics/core/__init__.py +++ b/mathics/core/__init__.py @@ -1 +1,14 @@ # -*- coding: utf-8 -*- +""" +This is the core of ``mathics-core`` package. + +Here you find the lowest, and most fundamental modules and classes. + +Objects here are fundamental to the system. These include objects like +``Symbols``, ``Numbers``, ``Rational``, ``Expressions``, ``Patterns`` and +``Rules`` to name a few. + +While some parts of ``mathics-core`` could conceivably be written in +Mathics, but are instead written in Python for efficiency, everything +here pretty much has to written in Python. +""" diff --git a/mathics/core/convert/__init__.py b/mathics/core/convert/__init__.py index 40a96afc6..13cc331bb 100644 --- a/mathics/core/convert/__init__.py +++ b/mathics/core/convert/__init__.py @@ -1 +1,7 @@ # -*- coding: utf-8 -*- +""" +Routines here convert between various internal representations such as +between ``Expressions``, LLVM functions, SymPy Arguments, MPMath datattypes and +so on. However this does not include the inital conversion a parsed string into +one of the internal representations. That is done in the parser. +""" diff --git a/mathics/core/parser/__init__.py b/mathics/core/parser/__init__.py index 67babafa0..ecfdaa3d1 100644 --- a/mathics/core/parser/__init__.py +++ b/mathics/core/parser/__init__.py @@ -1,4 +1,14 @@ # -*- coding: utf-8 -*- +""" +This module contains routines that takes tokens from the scanner (in a +separate module and repository) and parses this into some sort of +M-Expression as its AST (Abstract Syntax Tree). + +There is a separate `README +`_ +for decribing how this works. +""" + from mathics_scanner import is_symbol_name @@ -10,3 +20,15 @@ ) from mathics.core.parser.util import parse, parse_builtin_rule from mathics.core.parser.operators import all_operator_names + +__all__ = [ + "MathicsFileLineFeeder", + "MathicsFileLineFeeder", + "MathicsLineFeeder", + "MathicsMultiLineFeeder", + "MathicsSingleLineFeeder", + "all_operator_names", + "is_symbol_name", + "parse", + "parse_builtin_rule", +] diff --git a/mathics/eval/__init__.py b/mathics/eval/__init__.py index 1e955dad4..5186dd92e 100644 --- a/mathics/eval/__init__.py +++ b/mathics/eval/__init__.py @@ -1,14 +1,16 @@ """ +Mathics Evaluation Functions -mathics.eval +Routines here are core operations or functions that implement evaluation. If there +were an instruction interpreter, these would be the instructions. -This module contains routines that implement different kind of evaluations over expressions, like +These operatations then should include the most commonly-used Builtin-functions like +``N[]`` and routines in support of performing those evaluation operations/instructions. - * eval_N - * eval_makeboxes - * numerify - * test helpers that check properties on expressions. +Performance of the operations here can be important for overall interpreter performance. +It may be even be that some of the functions here should be written in faster +language like C, Cython, or Rust. """ # Ideally, this module should depend on modules inside ``mathics.core`` but not in modules stored in ``mathics.builtin`` to avoid circular references. From 6c60ab9df1557827ee967a4a99e172be72c2945f Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 30 Nov 2022 04:46:25 -0500 Subject: [PATCH 004/121] Revise for current standards ... Break url lines. "def eval" -> "def apply" (where appropriate) This was inspired by the desire to update the developer docs which include some of the Builtins defined here. See corresponding changes in the developer doc as well. --- mathics/builtin/numbers/constants.py | 132 +++++++++++++----- .../vectors/vector_space_operations.py | 26 ++-- 2 files changed, 112 insertions(+), 46 deletions(-) diff --git a/mathics/builtin/numbers/constants.py b/mathics/builtin/numbers/constants.py index 1c81c9fc5..aaf1972d8 100644 --- a/mathics/builtin/numbers/constants.py +++ b/mathics/builtin/numbers/constants.py @@ -11,29 +11,16 @@ import math + import mpmath import numpy import sympy - from mathics.builtin.base import Builtin, Predefined, SympyObject - -from mathics.core.atoms import ( - MachineReal, - PrecisionReal, -) -from mathics.core.attributes import ( - A_CONSTANT, - A_PROTECTED, - A_READ_PROTECTED, -) - -from mathics.core.number import get_precision, PrecisionValueError, machine_precision -from mathics.core.symbols import ( - Atom, - Symbol, - strip_context, -) +from mathics.core.atoms import MachineReal, PrecisionReal +from mathics.core.attributes import A_CONSTANT, A_PROTECTED, A_READ_PROTECTED +from mathics.core.number import PrecisionValueError, get_precision, machine_precision +from mathics.core.symbols import Atom, Symbol, strip_context from mathics.core.systemsymbols import SymbolIndeterminate @@ -84,7 +71,7 @@ class _Constant_Common(Predefined): nargs = {0} options = {"Method": "Automatic"} - def apply_N(self, precision, evaluation): + def eval_N(self, precision, evaluation): "N[%(name)s, precision_?NumericQ]" return self.get_constant(precision, evaluation) @@ -208,7 +195,12 @@ def to_sympy(self, expr=None, **kwargs): class Catalan(_MPMathConstant, _SympyConstant): """ - :Catalan's constant: https://en.wikipedia.org/wiki/Catalan%27s_constant (:SymPy: https://docs.sympy.org/latest/modules/core.html#sympy.core.numbers.Catalan, :WMA: https://reference.wolfram.com/language/ref/Catalan.html) + + :Catalan's constant: + https://en.wikipedia.org/wiki/Catalan%27s_constant ( + :SymPy: + https://docs.sympy.org/latest/modules/core.html#sympy.core.numbers.Catalan, + :WMA: https://reference.wolfram.com/language/ref/Catalan.html)
'Catalan' @@ -230,7 +222,14 @@ class Catalan(_MPMathConstant, _SympyConstant): class ComplexInfinity(_SympyConstant): """ - :Complex Infinity: https://en.wikipedia.org/wiki/Infinity#Complex_analysis (:SymPy: https://docs.sympy.org/latest/modules/core.html?highlight=zoo#complexinfinity, :WMA: https://reference.wolfram.com/language/ref/ComplexInfinity.html) + + :Complex Infinity: + https://en.wikipedia.org/wiki/Infinity#Complex_analysis ( + :SymPy: + https://docs.sympy.org/latest/modules/core.html?highlight=zoo#complexinfinity, + :WMA: + https://reference.wolfram.com/language/ref/ComplexInfinity.html) +
'ComplexInfinity'
represents an infinite complex quantity of undetermined direction. @@ -262,7 +261,11 @@ class ComplexInfinity(_SympyConstant): class Degree(_MPMathConstant, _NumpyConstant, _SympyConstant): """ - :Degree (angle): https://en.wikipedia.org/wiki/Degree_(angle) (:WMA: https://reference.wolfram.com/language/ref/Degree.html) + + :Degree (angle): + https://en.wikipedia.org/wiki/Degree_(angle) ( + :WMA: + https://reference.wolfram.com/language/ref/Degree.html)
'Degree' @@ -301,7 +304,7 @@ def to_numpy(self, expr=None, **kwargs): # return mpmath.degree return numpy.pi / 180 - def apply_N(self, precision, evaluation): + def eval_N(self, precision, evaluation): "N[Degree, precision_]" try: if precision: @@ -325,7 +328,13 @@ def apply_N(self, precision, evaluation): class E(_MPMathConstant, _NumpyConstant, _SympyConstant): """ - :Euler's number: https://en.wikipedia.org/wiki/E_(mathematical_constant) (:SymPy: https://docs.sympy.org/latest/modules/core.html#exp1, :WMA: https://reference.wolfram.com/language/ref/E.html) + + :Euler's number: + https://en.wikipedia.org/wiki/E_(mathematical_constant) ( + :SymPy: + https://docs.sympy.org/latest/modules/core.html#exp1, + :WMA: + https://reference.wolfram.com/language/ref/E.html)
'E' @@ -346,14 +355,18 @@ class E(_MPMathConstant, _NumpyConstant, _SympyConstant): numpy_name = "e" sympy_name = "E" - def apply_N(self, precision, evaluation): + def eval_N(self, precision, evaluation): "N[E, precision_]" return self.get_constant(precision, evaluation) class EulerGamma(_MPMathConstant, _NumpyConstant, _SympyConstant): """ - :Euler's constant: https://en.wikipedia.org/wiki/Euler%27s_constant (:SymPy: https://docs.sympy.org/latest/modules/core.html#sympy.core.numbers.EulerGamma, :WMA: https://reference.wolfram.com/language/ref/EulerGamma.html) + + :Euler's constant: + https://en.wikipedia.org/wiki/Euler%27s_constant ( + :SymPy: https://docs.sympy.org/latest/modules/core.html#sympy.core.numbers.EulerGamma, + :WMA: https://reference.wolfram.com/language/ref/EulerGamma.html)
'EulerGamma' @@ -375,7 +388,13 @@ class EulerGamma(_MPMathConstant, _NumpyConstant, _SympyConstant): class Glaisher(_MPMathConstant): """ - :Glaisher–Kinkelin constant: https://en.wikipedia.org/wiki/Glaisher%E2%80%93Kinkelin_constant (:mpmath: https://mpmath.org/doc/current/functions/constants.html#glaisher-s-constant-glaisher, :WMA: https://reference.wolfram.com/language/ref/Glaisher.html) + + :Glaisher–Kinkelin constant: + https://en.wikipedia.org/wiki/Glaisher%E2%80%93Kinkelin_constant ( + :mpmath: + https://mpmath.org/doc/current/functions/constants.html#glaisher-s-constant-glaisher, + :WMA: + https://reference.wolfram.com/language/ref/Glaisher.html)
'Glaisher'
is Glaisher's constant, with numerical value \u2243 1.28243. @@ -394,7 +413,13 @@ class Glaisher(_MPMathConstant): class GoldenRatio(_MPMathConstant, _SympyConstant): """ - :Golden ratio: https://en.wikipedia.org/wiki/Golden_ratio (:mpmath: https://mpmath.org/doc/current/functions/constants.html#golden-ratio-phi, :WMA: https://reference.wolfram.com/language/ref/GoldenRatio.html) + + :Golden ratio: + https://en.wikipedia.org/wiki/Golden_ratio ( + :mpmath: + https://mpmath.org/doc/current/functions/constants.html#golden-ratio-phi, + :WMA: + https://reference.wolfram.com/language/ref/GoldenRatio.html)
'GoldenRatio' @@ -414,7 +439,13 @@ class GoldenRatio(_MPMathConstant, _SympyConstant): class Indeterminate(_SympyConstant): """ - :Indeterminate form: https://en.wikipedia.org/wiki/Indeterminate_form (:SymPy: https://docs.sympy.org/latest/modules/core.html#sympy.core.numbers.NaN, :WMA: https://reference.wolfram.com/language/ref/Indeterminate.html) + + :Indeterminate form: + https://en.wikipedia.org/wiki/Indeterminate_form ( + :SymPy: https://docs.sympy.org/latest/modules/core.html#sympy.core.numbers.NaN, + :WMA: + https://reference.wolfram.com/language/ref/Indeterminate.html) +
'Indeterminate'
represents an indeterminate result. @@ -431,14 +462,20 @@ class Indeterminate(_SympyConstant): summary_text = "indeterminate value" sympy_name = "nan" - def apply_N(self, precision, evaluation, options={}): + def eval_N(self, precision, evaluation, options={}): "N[%(name)s, precision_?NumericQ, OptionsPattern[%(name)s]]" return SymbolIndeterminate class Infinity(_SympyConstant): """ - :Infinity: https://en.wikipedia.org/wiki/Infinity (:SymPy: https://docs.sympy.org/latest/modules/core.html#sympy.core.numbers.Infinity, :WMA: https://reference.wolfram.com/language/ref/Infinity.html) + : + Infinity: + https://en.wikipedia.org/wiki/Infinity ( + :SymPy: + https://docs.sympy.org/latest/modules/core.html#sympy.core.numbers.Infinity, + :WMA: + https://reference.wolfram.com/language/ref/Infinity.html)
'Infinity' @@ -478,7 +515,14 @@ class Infinity(_SympyConstant): class Khinchin(_MPMathConstant): """ - :Khinchin's constant: https://en.wikipedia.org/wiki/Khinchin%27s_constant (:mpmath: https://mpmath.org/doc/current/functions/constants.html#mpmath.mp.khinchin, :WMA: https://reference.wolfram.com/language/ref/Khinchin.html) + + :Khinchin's constant: + https://en.wikipedia.org/wiki/Khinchin%27s_constant ( + :mpmath: + https://mpmath.org/doc/current/functions/constants.html#mpmath.mp.khinchin, + :WMA: + https://reference.wolfram.com/language/ref/Khinchin.html) +
'Khinchin'
is Khinchin's constant, with numerical value \u2243 2.68545. @@ -497,7 +541,14 @@ class Khinchin(_MPMathConstant): class Overflow(Builtin): """ - Overflow (:WMA: https://reference.wolfram.com/language/ref/Overflow.html) + Numeric Overflow ( + :WMA: + https://reference.wolfram.com/language/ref/Overflow.html + ) + + See also + :Integer Overflow: + .
'Overflow[]' @@ -522,7 +573,12 @@ class Overflow(Builtin): class Pi(_MPMathConstant, _SympyConstant): """ - :Pi, \u03c0: https://en.wikipedia.org/wiki/Pi (:SymPy: https://docs.sympy.org/latest/modules/core.html#sympy.core.numbers.Pi, :WMA: https://reference.wolfram.com/language/ref/Pi.html) + + :Pi, \u03c0: https://en.wikipedia.org/wiki/Pi ( + :SymPy: + https://docs.sympy.org/latest/modules/core.html#sympy.core.numbers.Pi, + :WMA: + https://reference.wolfram.com/language/ref/Pi.html)
'Pi' @@ -557,7 +613,9 @@ class Pi(_MPMathConstant, _SympyConstant): class Undefined(Builtin): """ - Undefined symbol/value (:WMA: https://reference.wolfram.com/language/ref/Undefined.html) + Undefined symbol/value ( + :WMA: + https://reference.wolfram.com/language/ref/Undefined.html)
'Undefined' @@ -576,7 +634,9 @@ class Undefined(Builtin): class Underflow(Builtin): """ - :Arithmetic underflow: https://en.wikipedia.org/wiki/Arithmetic_underflow (:WMA: https://reference.wolfram.com/language/ref/Underflow.html) + :Arithmetic underflow: + https://en.wikipedia.org/wiki/Arithmetic_underflow ( + :WMA: https://reference.wolfram.com/language/ref/Underflow.html)
'Overflow[]' diff --git a/mathics/builtin/vectors/vector_space_operations.py b/mathics/builtin/vectors/vector_space_operations.py index 1b4893cea..de8231a82 100644 --- a/mathics/builtin/vectors/vector_space_operations.py +++ b/mathics/builtin/vectors/vector_space_operations.py @@ -8,23 +8,29 @@ from mathics.builtin.base import Builtin, SympyFunction from mathics.core.atoms import Complex, Integer, Integer0, Integer1, Real -from mathics.core.convert.sympy import from_sympy, to_sympy_matrix -from mathics.core.evaluation import Evaluation -from mathics.core.expression import Expression -from mathics.core.list import ListExpression -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import SymbolDot, SymbolConjugate from mathics.core.attributes import ( # A_LISTABLE, # A_NUMERIC_FUNCTION, A_PROTECTED, A_READ_PROTECTED, ) +from mathics.core.convert.sympy import from_sympy, to_sympy_matrix +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import SymbolConjugate, SymbolDot class KroneckerProduct(SympyFunction): """ - :Kronecker product: https://en.wikipedia.org/wiki/Kronecker_product (:SymPy: https://docs.sympy.org/latest/modules/physics/quantum/tensorproduct.html, :WMA: https://reference.wolfram.com/language/ref/KroneckerProduct.html) + + :Kronecker product: + https://en.wikipedia.org/wiki/Kronecker_product ( + :SymPy: + https://docs.sympy.org/latest/modules/physics/quantum/tensorproduct.html, + :WMA: + https://reference.wolfram.com/language/ref/KroneckerProduct.html)
'KroneckerProduct[$m1$, $m2$, ...]' @@ -57,7 +63,7 @@ class KroneckerProduct(SympyFunction): summary_text = "Kronecker product" sympy_name = "physics.quantum.TensorProduct" - def apply(self, mi: ListExpression, evaluation: Evaluation): + def eval(self, mi: ListExpression, evaluation: Evaluation): "KroneckerProduct[mi__List]" sympy_mi = [to_sympy_matrix(m) for m in mi.elements] return from_sympy(TensorProduct(*sympy_mi)) @@ -132,7 +138,7 @@ class Projection(Builtin): summary_text = "find the projection of one vector on another" - def apply(self, u: ListExpression, v: ListExpression, evaluation): + def eval(self, u: ListExpression, v: ListExpression, evaluation): "Projection[u_, v_]" all_elements = u.elements + v.elements @@ -177,7 +183,7 @@ class UnitVector(Builtin): } summary_text = "unit vector along a coordinate direction" - def apply(self, n: Integer, k: Integer, evaluation): + def eval(self, n: Integer, k: Integer, evaluation): "UnitVector[n_Integer, k_Integer]" py_n = n.value From 8b77ce35983674c4f1dcc28f57a786160bc9842b Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 3 Dec 2022 16:20:44 -0500 Subject: [PATCH 005/121] Add missing summary texts & ... isort imports, and change 'apply()' to 'eval()' where appropriate. --- mathics/builtin/graphics.py | 104 +++++++++++++++------------------- mathics/builtin/manipulate.py | 7 ++- 2 files changed, 51 insertions(+), 60 deletions(-) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 79d65e72b..41380562c 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -5,52 +5,44 @@ Drawing Graphics """ -# This tells documentation how to sort this module +# This following line tells documentation how to sort this module sort_order = "mathics.builtin.drawing-graphics" from math import sqrt - -from mathics.eval.nevaluator import eval_N - from mathics.builtin.base import Builtin - from mathics.builtin.colors.color_directives import ( - _ColorObject, - Opacity, CMYKColor, GrayLevel, Hue, LABColor, LCHColor, LUVColor, + Opacity, RGBColor, XYZColor, + _ColorObject, ) - from mathics.builtin.drawing.graphics_internals import ( + GLOBALS, _GraphicsDirective, _GraphicsElementBox, - GLOBALS, get_class, ) from mathics.builtin.options import options_to_rules - -from mathics.core.atoms import ( - Integer, - Rational, - Real, -) +from mathics.core.atoms import Integer, Rational, Real +from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.exceptions import BoxExpressionError from mathics.core.expression import Expression +from mathics.core.formatter import lookup_method from mathics.core.list import ListExpression from mathics.core.symbols import ( Symbol, - symbol_set, - system_symbols_dict, SymbolList, SymbolNull, + symbol_set, + system_symbols_dict, ) from mathics.core.systemsymbols import ( SymbolEdgeForm, @@ -58,11 +50,7 @@ SymbolMakeBoxes, SymbolRule, ) - -from mathics.core.formatter import lookup_method - -from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED - +from mathics.eval.nevaluator import eval_N GRAPHICS_OPTIONS = { "AspectRatio": "Automatic", @@ -209,7 +197,7 @@ class Show(Builtin): options = GRAPHICS_OPTIONS summary_text = "display graphic objects" - def apply(self, graphics, evaluation, options): + def eval(self, graphics, evaluation, options): """Show[graphics_, OptionsPattern[%(name)s]]""" for option in options: @@ -285,7 +273,7 @@ class Graphics(Builtin): box_suffix = "Box" summary_text = "general two‐dimensional graphics" - def apply_makeboxes(self, content, evaluation, options): + def eval_makeboxes(self, content, evaluation, options): """MakeBoxes[%(name)s[content_, OptionsPattern[%(name)s]], StandardForm|TraditionalForm|OutputForm]""" @@ -315,7 +303,7 @@ def convert(content): if inset.get_head() is Symbol("System`Graphics"): opts = {} # opts = dict(opt._elements[0].name:opt_elements[1] for opt in inset._elements[1:]) - inset = self.apply_makeboxes( + inset = self.eval_makeboxes( inset._elements[0], evaluation, opts ) n_elements = [inset] + [ @@ -349,38 +337,6 @@ def convert(content): ) -class _Size(_GraphicsDirective): - def init(self, graphics, item=None, value=None): - super(_Size, self).init(graphics, item) - if item is not None: - self.value = item.elements[0].round_to_float() - elif value is not None: - self.value = value - else: - raise BoxExpressionError - if self.value < 0: - raise BoxExpressionError - - -class _Thickness(_Size): - pass - - -class AbsoluteThickness(_Thickness): - """ -
-
'AbsoluteThickness[$p$]' -
sets the line thickness for subsequent graphics primitives to $p$ points. -
- - >> Graphics[Table[{AbsoluteThickness[t], Line[{{20 t, 10}, {20 t, 80}}], Text[ToString[t]<>"pt", {20 t, 0}]}, {t, 0, 10}]] - = -Graphics- - """ - - def get_thickness(self): - return self.graphics.translate_absolute((self.value, 0))[0] - - class _Polyline(_GraphicsElementBox): def do_init(self, graphics, points): if not points.has_form("List", None): @@ -420,6 +376,40 @@ def extent(self) -> list: return result +class _Size(_GraphicsDirective): + def init(self, graphics, item=None, value=None): + super(_Size, self).init(graphics, item) + if item is not None: + self.value = item.elements[0].round_to_float() + elif value is not None: + self.value = value + else: + raise BoxExpressionError + if self.value < 0: + raise BoxExpressionError + + +class _Thickness(_Size): + pass + + +class AbsoluteThickness(_Thickness): + """ +
+
'AbsoluteThickness[$p$]' +
sets the line thickness for subsequent graphics primitives to $p$ points. +
+ + >> Graphics[Table[{AbsoluteThickness[t], Line[{{20 t, 10}, {20 t, 80}}], Text[ToString[t]<>"pt", {20 t, 0}]}, {t, 0, 10}]] + = -Graphics- + """ + + summary_text = "graphics directive be specifying absolute line thickness" + + def get_thickness(self): + return self.graphics.translate_absolute((self.value, 0))[0] + + class Point(Builtin): """
diff --git a/mathics/builtin/manipulate.py b/mathics/builtin/manipulate.py index 4eaad7f74..7fdd1c8bf 100755 --- a/mathics/builtin/manipulate.py +++ b/mathics/builtin/manipulate.py @@ -2,7 +2,6 @@ from mathics import settings - from mathics.builtin.base import Builtin from mathics.core.atoms import Integer, String from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED @@ -21,8 +20,8 @@ _jupyter = False try: - from ipywidgets import IntSlider, FloatSlider, ToggleButtons, Box, DOMWidget from IPython.core.formatters import IPythonDisplayFormatter + from ipywidgets import Box, DOMWidget, FloatSlider, IntSlider, ToggleButtons _ipywidgets = True except ImportError: @@ -88,6 +87,8 @@ class ManipulateParameter( "System`Private`ManipulateParameter[var_, {opt_List}] /; Length[opt] > 0": 'Join[{Type -> "Options", Options -> opt, Default -> Part[opt, 1]}, var]', } + summary_text = "interactive manipulation (not implemented yet)" + def _manipulate_label(x): # gets the label that is displayed for a symbol or name if isinstance(x, String): @@ -301,7 +302,7 @@ class Manipulate(Builtin): requires = ("ipywidgets",) summary_text = "interactively manipulate any expression, graphic, or other object" - def apply(self, expr, args, evaluation): + def eval(self, expr, args, evaluation): "Manipulate[expr_, args__]" if (not _jupyter) or (not Kernel.initialized()) or (Kernel.instance() is None): return evaluation.message("Manipulate", "jupyter") From d33ee2c8158e9cb480aa10e382879f1ab3219166 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 4 Dec 2022 11:53:33 -0500 Subject: [PATCH 006/121] Compute Symbol.__hash__() once Also note *why* we need Symbol.__hash__() --- mathics/core/symbols.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index f1a3a6efa..224698b7b 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -1,10 +1,11 @@ # cython: language_level=3 # -*- coding: utf-8 -*- -import sympy import time from typing import Any, FrozenSet, List, Optional, Tuple +import sympy + from mathics.core.element import ( BaseElement, EvalMixin, @@ -359,13 +360,14 @@ class Symbol(Atom, NumericOperators, EvalMixin): """ name: str + hash: str sympy_dummy: Any defined_symbols = {} class_head_name = "System`Symbol" # __new__ instead of __init__ is used here because we want # to return the same object for a given "name" value. - def __new__(cls, name, sympy_dummy=None, value=None): + def __new__(cls, name: str, sympy_dummy=None, value=None): """ Allocate an object ensuring that for a given `name` we get back the same object. """ @@ -374,6 +376,14 @@ def __new__(cls, name, sympy_dummy=None, value=None): if self is None: self = super(Symbol, cls).__new__(cls) self.name = name + + # Set a value for self.__hash__() once so that every time + # it is used this is fast. + # This tuple with "Symbol" is used to give a different hash + # than the hash that would be returned if just name were + # used. + self.hash = hash(("Symbol", name)) + # TODO: revise how we convert sympy.Dummy # symbols. # @@ -412,8 +422,11 @@ def __eq__(self, other) -> bool: def __getnewargs__(self): return (self.name, self.sympy_dummy) - def __hash__(self): - return hash(("Symbol", self.name)) # to distinguish from String + def __hash__(self) -> int: + """ + We need self.__hash__() so that we can use Symbols as keys in dictionaries. + """ + return self.hash def __ne__(self, other) -> bool: return self is not other From 41ea942e7739c744b84e7aa5e8dd77150c020b83 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 7 Dec 2022 14:25:06 -0500 Subject: [PATCH 007/121] Remove run-time summary-check code. It appears in test/consistency-and-style/test_summary_text.py --- mathics/builtin/__init__.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index 584d3998f..f4dbbf16b 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -219,33 +219,6 @@ def name_is_builtin_symbol(module, name: str) -> Optional[type]: # print("XXX3", submodule_names) import_builtins(submodule_names, subdir) -# FIXME: move this somewhere else... - -# Set this to True to print all the builtins that do not have -# a summary_text. In the future, we can set this to True -# and raise an error if a new builtin is added without -# this property or if it does not fulfill some other conditions. -RUN_SANITY_TEST = False - - -def sanity_check(cls, module): - if not RUN_SANITY_TEST: - return True - - if not hasattr(cls, "summary_text"): - print( - "In ", - module.__name__, - cls.__name__, - " does not have a summary_text.", - ) - return False - return True - - -# End FIXME - - for module in modules: builtins_by_module[module.__name__] = [] module_vars = dir(module) @@ -259,10 +232,6 @@ def sanity_check(cls, module): # This set the default context for symbols in mathics.builtins if not type(instance).context: type(instance).context = "System`" - assert sanity_check( - builtin_class, module - ), f"In {module.__name__} Builtin <<{builtin_class.__name__}>> did not pass the sanity check." - _builtins_list.append((instance.get_name(), instance)) builtins_by_module[module.__name__].append(instance) From 8793c2bf1e7b82b7e5fd9ee4563146c83859847d Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 7 Dec 2022 14:31:28 -0500 Subject: [PATCH 008/121] Miscelleneous lint found in files of #631 These changes have been split off from #631. That other PR will have to be rebased after this and #651 go in. --- admin-tools/build_and_check_manifest.py | 6 ++-- mathics/builtin/__init__.py | 6 ++-- mathics/core/definitions.py | 14 ++++----- mathics/core/pymathics.py | 3 +- mathics/doc/common_doc.py | 26 ++++++++-------- mathics/format/mathml.py | 41 ++++++++++++------------- 6 files changed, 47 insertions(+), 49 deletions(-) diff --git a/admin-tools/build_and_check_manifest.py b/admin-tools/build_and_check_manifest.py index 93db131a2..7b44e0728 100755 --- a/admin-tools/build_and_check_manifest.py +++ b/admin-tools/build_and_check_manifest.py @@ -4,7 +4,7 @@ import sys -def generate_avaliable_builtins_names(): +def generate_available_builtins_names(): msg = "" builtins_by_name = {} for module in modules: @@ -37,7 +37,7 @@ def generate_avaliable_builtins_names(): def build_builtin_manifest(): - builtins_by_name = generate_avaliable_builtins_names() + builtins_by_name = generate_available_builtins_names() with open("SYMBOLS_MANIFEST.txt", "w") as f_out: for key in sorted(key for key in builtins_by_name): f_out.write(key + "\n") @@ -45,7 +45,7 @@ def build_builtin_manifest(): def check_manifest(): status_OK = True - builtins_by_name = generate_avaliable_builtins_names() + builtins_by_name = generate_available_builtins_names() with open("SYMBOLS_MANIFEST.txt", "r") as f_in: manifest_symbols = {name[:-1]: "OK" for name in f_in.readlines()} # Check that all the Symbols in the manifest are available diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index 584d3998f..0ca63e9c6 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -16,7 +16,7 @@ A Mathics Builtin is implemented one of a particular kind of Python class. Within these classes class variables give properties of the -builtin class such as the Builtin's Attributes, it Information text, +builtin class such as the Builtin's Attributes, its Information text, among other things. """ @@ -168,9 +168,9 @@ def name_is_builtin_symbol(module, name: str) -> Optional[type]: # FIXME: redo using importlib since that is probably less fragile. -exclude_files = set(("codetables", "base")) +exclude_files = {"codetables", "base"} module_names = [ - f for f in __py_files__ if re.match("^[a-z0-9]+$", f) if f not in exclude_files + f for f in __py_files__ if re.match(r"^[a-z\d]+$", f) if f not in exclude_files ] modules = [] diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 65fb7f09e..731222ea3 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -42,7 +42,7 @@ def get_file_time(file) -> float: def valuesname(name) -> str: - "'NValues' -> 'n'" + """'NValues' -> 'n'""" assert name.startswith("System`"), name if name == "System`Messages": @@ -83,10 +83,11 @@ class Definitions: In the current implementation, the ``Definitions`` object stores ``Definition`` s in four dictionaries: - - builtins: stores the defintions of the ``Builtin`` symbols + - builtins: stores the definitions of the ``Builtin`` symbols - pymathics: stores the definitions of the ``Builtin`` symbols added from pymathics modules. - user: stores the definitions created during the runtime. - - definition_cache: keep definitions obtained by merging builtins, pymathics, and user definitions associated to the same symbol. + - definition_cache: keep definitions obtained by merging builtins, pymathics, and user definitions associated to the + same symbol. """ def __init__( @@ -176,7 +177,7 @@ def clear_cache(self, name=None): # the definitions cache (self.definitions_cache) caches (incomplete and complete) names -> Definition(), # e.g. "xy" -> d and "MyContext`xy" -> d. we need to clear this cache if a Definition() changes (which # would happen if a Definition is combined from a builtin and a user definition and some content in the - # user definition is updated) or if the lookup rules change and we could end up at a completely different + # user definition is updated) or if the lookup rules change, and we could end up at a completely different # Definition. # the lookup cache (self.lookup_cache) caches what lookup_name() does. we only need to update this if some @@ -273,7 +274,7 @@ def get_names(self): ) def get_accessible_contexts(self): - "Return the contexts reachable though $Context or $ContextPath." + """Return the contexts reachable though $Context or $ContextPath.""" accessible_ctxts = set(ctx for ctx in self.context_path) accessible_ctxts.add(self.current_context) return accessible_ctxts @@ -412,7 +413,6 @@ def get_definition(self, name, only_if_exists=False) -> "Definition": candidates = [user] if user else [] builtin_instance = None - is_numeric = False if pymathics: builtin_instance = pymathics @@ -447,7 +447,7 @@ def get_definition(self, name, only_if_exists=False) -> "Definition": # This behaviour for options is wrong: # because of this, ``Unprotect[Expand]; ClearAll[Expand]; Options[Expand]`` # returns the builtin options of ``Expand`` instead of an empty list, like - # in WMA. This suggest that this idea of keeping differnt dicts for builtin + # in WMA. This suggests that this idea of keeping different dicts for builtin # and user definitions is pointless. curr = its.pop() options.update(curr.options) diff --git a/mathics/core/pymathics.py b/mathics/core/pymathics.py index ca1615b8a..354404b10 100644 --- a/mathics/core/pymathics.py +++ b/mathics/core/pymathics.py @@ -9,8 +9,7 @@ from mathics.core.evaluation import Evaluation -# Tis dict -# Probably this do not belong here. +# This dict probably does not belong here. pymathics = {} diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py index 050c25277..442c61320 100644 --- a/mathics/doc/common_doc.py +++ b/mathics/doc/common_doc.py @@ -101,7 +101,7 @@ LATEX_INLINE_END_RE = re.compile(r"(?s)(?P\\lstinline'[^']*?'\}?[.,;:])") LATEX_CONSOLE_RE = re.compile(r"\\console\{(.*?)\}") -# These are all of the XML/HTML-like tags that documentation supports. +# These are all the XML/HTML-like tags that documentation supports. ALLOWED_TAGS = ( "dl", "dd", @@ -270,7 +270,7 @@ def _replace_all(text, pairs): def escape_latex_output(text): - "Escape Mathics output" + """Escape Mathics output""" text = _replace_all( text, @@ -289,7 +289,7 @@ def escape_latex_output(text): def escape_latex_code(text): - "Escape verbatim Mathics input" + """Escape verbatim Mathics input""" text = escape_latex_output(text) escape_char = get_latex_escape_char(text) @@ -297,7 +297,7 @@ def escape_latex_code(text): def escape_latex(text): - "Escape documentation text" + """Escape documentation text""" def repl_python(match): return ( @@ -587,7 +587,7 @@ def repl_out(match): return "\\begin{%s}%s\\end{%s}" % (tag, content, tag) def repl_inline_end(match): - "Prevent linebreaks between inline code and sentence delimeters" + """Prevent linebreaks between inline code and sentence delimeters""" code = match.group("all") if code[-2] == "}": @@ -614,7 +614,7 @@ def repl_nonasy(match): POST_SUBSTITUTION_TAG = "_POST_SUBSTITUTION%d_" -def pre_sub(re, text, repl_func): +def pre_sub(regexp, text, repl_func): post_substitutions = [] def repl_pre(match): @@ -623,7 +623,7 @@ def repl_pre(match): post_substitutions.append(repl) return POST_SUBSTITUTION_TAG % index - text = re.sub(repl_pre, text) + text = regexp.sub(repl_pre, text) return text, post_substitutions @@ -768,7 +768,7 @@ def skip_module_doc(module, modules_seen): def sorted_chapters(chapters: list) -> list: - "Return chapters sorted by title" + """Return chapters sorted by title""" return sorted(chapters, key=lambda chapter: chapter.title) @@ -883,7 +883,7 @@ def __init__(self, want_sorting=False): continue elif IS_PYPY and submodule.__name__ == "builtins": # PyPy seems to add this module on its own, - # but it is not something htat can be importable + # but it is not something that can be importable continue if submodule in modules_seen: @@ -1124,7 +1124,7 @@ def __init__(self, module=None): sections = SECTION_RE.findall(text) for pre_text, title, text in sections: if not chapter.doc: - chapter.doc = XMLDoc(pre_text) + chapter.doc = XMLDoc(pre_text, title) if title: section = DocSection(chapter, title, text) chapter.sections.append(section) @@ -1138,7 +1138,7 @@ def __init__(self, module=None): # Builds the automatic documentation builtin_part = DocPart(self, "Pymathics Modules", is_reference=True) title, text = get_module_doc(self.pymathicsmodule) - chapter = DocChapter(builtin_part, title, XMLDoc(text)) + chapter = DocChapter(builtin_part, title, XMLDoc(text, title)) for name in self.symbols: instance = self.symbols[name] installed = check_requires_list(getattr(instance, "requires", [])) @@ -1421,7 +1421,7 @@ def __init__( About some of the parameters... - Some of the subsections are contained in a grouping module and need special work to + Some subsections are contained in a grouping module and need special work to get the grouping module name correct. For example the Chapter "Colors" is a module so the docstring text for it is in @@ -1651,7 +1651,7 @@ def test_indices(self): return [test.index for test in self.tests] -# This string is used so we can indicate a trailing blank at the end of a line by +# This string is used, so we can indicate a trailing blank at the end of a line by # adding this string to the end of the line which gets stripped off. # Some editors and formatters like to strip off trailing blanks at the ends of lines. END_LINE_SENTINAL = "#<--#" diff --git a/mathics/format/mathml.py b/mathics/format/mathml.py index 5fb837e2b..e0281e8ec 100644 --- a/mathics/format/mathml.py +++ b/mathics/format/mathml.py @@ -41,26 +41,24 @@ def encode_mathml(text: str) -> str: return text -extra_operators = set( - ( - ",", - "(", - ")", - "[", - "]", - "{", - "}", - "\u301a", - "\u301b", - "\u00d7", - "\u2032", - "\u2032\u2032", - " ", - "\u2062", - "\u222b", - "\u2146", - ) -) +extra_operators = { + ",", + "(", + ")", + "[", + "]", + "{", + "}", + "\u301a", + "\u301b", + "\u00d7", + "\u2032", + "\u2032\u2032", + " ", + "\u2062", + "\u222b", + "\u2146", +} def string(self, **options) -> str: @@ -281,7 +279,8 @@ def graphicsbox(self, elements=None, **options) -> str: svg_body = self.boxes_to_svg(elements, **options) # mglyph, which is what we have been using, is bad because MathML standard changed. - # metext does not work because the way in which we produce the svg images is also based on this outdated mglyph behaviour. + # metext does not work because the way in which we produce the svg images is also based on this outdated mglyph + # behaviour. # template = '' template = ( '' From 6d0d3ec5ae181161685c01300cf11ecafd17a1c1 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 10 Dec 2022 21:02:29 -0500 Subject: [PATCH 009/121] Respect ENABLE_FILES_MODULE environment variable Basically we split out the "mainloop" module from `files_io` and add it to builtins. This way, everything in the `files_io` module can be ignored when desired. This basically follows the suggestion by alek3y in https://github.com/Mathics3/mathics-core/issues/653 Fixes #653 --- mathics/builtin/__init__.py | 6 ++---- mathics/builtin/files_io/__init__.py | 4 ++++ mathics/builtin/{files_io => }/mainloop.py | 0 3 files changed, 6 insertions(+), 4 deletions(-) rename mathics/builtin/{files_io => }/mainloop.py (100%) diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index e809af4b2..8a601e25e 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -179,9 +179,7 @@ def name_is_builtin_symbol(module, name: str) -> Optional[type]: _builtins_list = [] builtins_by_module = {} -disable_file_module_names = ( - [] if ENABLE_FILES_MODULE else ["files_io.files", "files_io.importexport"] -) +disable_file_module_names = [] if ENABLE_FILES_MODULE else ["files_io"] for subdir in ( "arithfns", @@ -208,7 +206,7 @@ def name_is_builtin_symbol(module, name: str) -> Optional[type]: ): import_name = f"{__name__}.{subdir}" - if import_name in disable_file_module_names: + if subdir in disable_file_module_names: continue builtin_module = importlib.import_module(import_name) diff --git a/mathics/builtin/files_io/__init__.py b/mathics/builtin/files_io/__init__.py index 681bd3c52..63c8ab92c 100644 --- a/mathics/builtin/files_io/__init__.py +++ b/mathics/builtin/files_io/__init__.py @@ -1,6 +1,10 @@ """ Input/Output, Files, and Filesystem + """ +# Note: everything in this module is not loaded if environment variable ENABLE_FILES_MODULE is False. +# Here we do not want to include any built-in commands that can write to the filesytesm. + # This tells documentation how to sort this module sort_order = "mathics.builtin.input-output-files-and-filesystem" diff --git a/mathics/builtin/files_io/mainloop.py b/mathics/builtin/mainloop.py similarity index 100% rename from mathics/builtin/files_io/mainloop.py rename to mathics/builtin/mainloop.py From 421f311130a29c0c78bd4761b5c477a3da3435c1 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 11 Dec 2022 10:57:15 -0500 Subject: [PATCH 010/121] mathics.builtin.mainloop up to current standards --- mathics/builtin/mainloop.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/mathics/builtin/mainloop.py b/mathics/builtin/mainloop.py index 3884c63dd..9cc7f6b50 100644 --- a/mathics/builtin/mainloop.py +++ b/mathics/builtin/mainloop.py @@ -24,15 +24,15 @@ sort_order = "mathics.builtin.the-main-loop" from mathics.builtin.base import Builtin - from mathics.core.attributes import A_LISTABLE, A_NO_ATTRIBUTES, A_PROTECTED class HistoryLength(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/$HistoryLength
-
'$HistoryLength' -
specifies the maximum number of 'In' and 'Out' entries. +
'$HistoryLength' +
specifies the maximum number of 'In' and 'Out' entries.
>> $HistoryLength = 100 @@ -60,10 +60,11 @@ class HistoryLength(Builtin): class In(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/In
-
'In[$k$]' +
'In[$k$]'
gives the $k$th line of input. -
+
>> x = 1 = 1 >> x = x + 1 @@ -99,6 +100,7 @@ class In(Builtin): class IOHookPreRead(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/$PreRead
$PreRead
is a global variable whose value, if set, is applied to the \ @@ -117,10 +119,10 @@ class IOHookPreRead(Builtin): class IOHookPre(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/$Pre
-
$Pre -
is a global variable whose value, if set, - is applied to every input expression. +
$Pre +
is a global variable whose value, if set, is applied to every input expression.
Set $Timing$ as the $Pre function, stores the enlapsed time in a variable, @@ -150,6 +152,7 @@ class IOHookPre(Builtin): class IOHookPost(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/$Post
$Post
is a global variable whose value, if set, is applied to every output expression. @@ -163,6 +166,7 @@ class IOHookPost(Builtin): class IOHookPrePrint(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/$PrePrint
$PrePrint
is a global variable whose value, if set, is applied to every output expression before it is printed. @@ -178,6 +182,7 @@ class IOHookPrePrint(Builtin): class IOHookSyntaxHandler(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/$SyntaxHandler
$SyntaxHandler
is a global variable whose value, if set, is applied to any input string that is found to contain a syntax error. @@ -193,6 +198,7 @@ class IOHookSyntaxHandler(Builtin): class Line(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/$Line
'$Line'
holds the current input line number. @@ -216,6 +222,7 @@ class Line(Builtin): class Out(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/$Out
'Out[$k$]'
'%$k$' @@ -263,4 +270,4 @@ class Out(Builtin): "MakeBoxes[Out[k_Integer?Positive]," " f:StandardForm|TraditionalForm|InputForm|OutputForm]": r'"%%" <> ToString[k]', } - summary_text = "result of the n-esim input line" + summary_text = "result of the Nth input line" From 3ddac4aca9190e914cadfe684cfecd7e0215460f Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 11 Dec 2022 11:07:24 -0500 Subject: [PATCH 011/121] Pycharm lint checks --- mathics/builtin/mainloop.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/mathics/builtin/mainloop.py b/mathics/builtin/mainloop.py index 9cc7f6b50..63d4ab110 100644 --- a/mathics/builtin/mainloop.py +++ b/mathics/builtin/mainloop.py @@ -13,19 +13,22 @@ As part of this loop, various global objects in this section are consulted. -There are a variety of "hooks" that allow you to insert functions to be applied to the expresssions at various stages in the main loop. +There are a variety of "hooks" that allow you to insert functions to be applied to the expressions at various stages \ +in the main loop. -If you assign a function to the global variable '$PreRead' it will be applied with the input that is read in the first step listed above. +If you assign a function to the global variable '$PreRead' it will be applied with the input that is read in the first \ +step listed above. -Similarly, if you assign a function to global variable '$Pre', it will be applied with the input before processing the input, the second step listed above. +Similarly, if you assign a function to global variable '$Pre', it will be applied with the input before processing the \ +input, the second step listed above. """ -# This tells documentation how to sort this module -sort_order = "mathics.builtin.the-main-loop" - from mathics.builtin.base import Builtin from mathics.core.attributes import A_LISTABLE, A_NO_ATTRIBUTES, A_PROTECTED +# This tells documentation how to sort this module +sort_order = "mathics.builtin.the-main-loop" + class HistoryLength(Builtin): """ @@ -95,7 +98,7 @@ class In(Builtin): rules = { "In[k_Integer?Negative]": "In[$Line + k]", } - summary_text = "i-esim input" + summary_text = "Kth input" class IOHookPreRead(Builtin): @@ -125,8 +128,8 @@ class IOHookPre(Builtin):
is a global variable whose value, if set, is applied to every input expression.
- Set $Timing$ as the $Pre function, stores the enlapsed time in a variable, - stores just the result in Out[$Line] and print a formated version showing the enlapsed time + Set $Timing$ as the $Pre function, stores the elapsed time in a variable, + stores just the result in Out[$Line] and print a formatted version showing the elapsed time >> $Pre := (Print["[Processing input...]"];#1)& >> $Post := (Print["[Storing result...]"]; #1)& | [Processing input...] @@ -139,7 +142,7 @@ class IOHookPre(Builtin): | [Storing result...] | The result is: = {..., 4} - >> $Pre = .; $Post = .; $PrePrint = .; $EnlapsedTime = .; + >> $Pre = .; $Post = .; $PrePrint = .; $ElapsedTime = .; | [Processing input...] >> 2 + 2 = 4 @@ -185,7 +188,8 @@ class IOHookSyntaxHandler(Builtin): :WMA: https://reference.wolfram.com/language/ref/$SyntaxHandler
$SyntaxHandler -
is a global variable whose value, if set, is applied to any input string that is found to contain a syntax error. +
is a global variable whose value, if set, is applied to any input string that is found to contain a syntax \ + error. (Not implemented yet)
@@ -270,4 +274,4 @@ class Out(Builtin): "MakeBoxes[Out[k_Integer?Positive]," " f:StandardForm|TraditionalForm|InputForm|OutputForm]": r'"%%" <> ToString[k]', } - summary_text = "result of the Nth input line" + summary_text = "result of the Kth input line" From 4d504ec577159089b7a73eeb14f3556d768d54cb Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 14 Dec 2022 19:35:41 -0500 Subject: [PATCH 012/121] Move eval function from _Plot[] to mathics.eval... Also clean up mathics.drawing.plot module --- mathics/builtin/drawing/plot.py | 506 +++++++------------------------- mathics/eval/plot.py | 385 ++++++++++++++++++++++++ 2 files changed, 493 insertions(+), 398 deletions(-) create mode 100644 mathics/eval/plot.py diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index e83e64bf9..e1898b930 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -10,32 +10,23 @@ sort_order = "mathics.builtin.plotting-data" -from math import sin, cos, pi, sqrt, isnan, isinf import itertools import numbers +from math import cos, pi, sin, sqrt + import palettable from mathics.builtin.base import Builtin from mathics.builtin.drawing.graphics3d import Graphics3D from mathics.builtin.graphics import Graphics -from mathics.builtin.numeric import chop from mathics.builtin.options import options_to_rules -from mathics.builtin.scoping import dynamic_scoping - -from mathics.core.atoms import ( - Real, - MachineReal, - String, - Integer, - Integer0, - Integer1, -) +from mathics.core.atoms import Integer, Integer0, Integer1, MachineReal, Real, String from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.convert.python import from_python from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import Symbol, SymbolList, SymbolN, SymbolPower, SymbolTrue +from mathics.core.symbols import Symbol, SymbolList, SymbolTrue from mathics.core.systemsymbols import ( SymbolBlend, SymbolColorData, @@ -44,37 +35,44 @@ SymbolGraphics, SymbolGraphics3D, SymbolGrid, - SymbolMessageName, + SymbolHue, + SymbolLine, SymbolMap, - SymbolQuiet, SymbolPoint, SymbolPolygon, + SymbolRGBColor, SymbolRow, SymbolRule, - SymbolRGBColor, SymbolSlot, SymbolStyle, ) - from mathics.eval.nevaluator import eval_N - -RealPoint6 = Real(0.6) -RealPoint2 = Real(0.2) +from mathics.eval.plot import ( + RealPoint2, + RealPoint6, + compile_quiet_function, + eval_Plot, + get_plot_range, +) SymbolColorDataFunction = Symbol("ColorDataFunction") SymbolDisk = Symbol("Disk") SymbolFaceForm = Symbol("FaceForm") -SymbolHue = Symbol("Hue") -SymbolLine = Symbol("Line") SymbolRectangle = Symbol("Rectangle") SymbolText = Symbol("Text") -try: - from mathics.compile import _compile, CompileArg, CompileError, real_type - - has_compile = True -except ImportError: - has_compile = False +# PlotRange Option +def check_plot_range(range, range_type) -> bool: + """ + Return True if `range` has two numbers, the first number less than the second number, + and that both numbers have type `range_type` + """ + if range in ("System`Automatic", "System`All"): + return True + if isinstance(range, list) and len(range) == 2: + if isinstance(range[0], range_type) and isinstance(range[1], range_type): + return True + return False def gradient_palette(color_function, n, evaluation): # always returns RGB values @@ -99,7 +97,7 @@ def gradient_palette(color_function, n, evaluation): # always returns RGB value if len(colors.elements) != n: return - from mathics.builtin.colors.color_directives import expression_to_color, ColorError + from mathics.builtin.colors.color_directives import ColorError, expression_to_color try: objects = [expression_to_color(x) for x in colors.elements] @@ -243,11 +241,11 @@ class ColorData(Builtin): "Moonrise1": _PalettableGradient(palettable.wesanderson.Moonrise1_5, False), } - def apply_directory(self, evaluation): + def eval_directory(self, evaluation): "ColorData[]" return ListExpression(String("Gradients")) - def apply(self, name, evaluation): + def eval(self, name, evaluation): "ColorData[name_String]" py_name = name.get_string_value() if py_name == "Gradients": @@ -267,122 +265,6 @@ def colors(name, evaluation): return palette.colors() -def extract_pyreal(value): - if isinstance(value, Real): - return chop(value).round_to_float() - return None - - -def zero_to_one(value): - if value == 0: - return 1 - return value - - -def compile_quiet_function(expr, arg_names, evaluation, expect_list): - """ - Given an expression return a quiet callable version. - Compiles the expression where possible. - """ - if has_compile and not expect_list: - try: - cfunc = _compile( - expr, [CompileArg(arg_name, real_type) for arg_name in arg_names] - ) - except CompileError: - pass - else: - - def quiet_f(*args): - try: - result = cfunc(*args) - if not (isnan(result) or isinf(result)): - return result - except Exception: - pass - return None - - return quiet_f - expr = Expression(SymbolN, expr).evaluate(evaluation) - quiet_expr = Expression( - SymbolQuiet, - expr, - ListExpression(Expression(SymbolMessageName, SymbolPower, String("infy"))), - ) - - def quiet_f(*args): - vars = {arg_name: Real(arg) for arg_name, arg in zip(arg_names, args)} - value = dynamic_scoping(quiet_expr.evaluate, vars, evaluation) - if expect_list: - if value.has_form("List", None): - value = [extract_pyreal(item) for item in value.elements] - if any(item is None for item in value): - return None - return value - else: - return None - else: - value = extract_pyreal(value) - if value is None or isinf(value) or isnan(value): - return None - return value - - return quiet_f - - -def automatic_plot_range(values): - """Calculates mean and standard deviation, throwing away all points - which are more than 'thresh' number of standard deviations away from - the mean. These are then used to find good vmin and vmax values. These - values can then be used to find Automatic Plotrange.""" - - if not values: - return 0, 1 - - thresh = 2.0 - values = sorted(values) - valavg = sum(values) / len(values) - valdev = sqrt( - sum([(x - valavg) ** 2 for x in values]) / zero_to_one(len(values) - 1) - ) - - n1, n2 = 0, len(values) - 1 - if valdev != 0: - for v in values: - if abs(v - valavg) / valdev < thresh: - break - n1 += 1 - for v in values[::-1]: - if abs(v - valavg) / valdev < thresh: - break - n2 -= 1 - - vrange = values[n2] - values[n1] - vmin = values[n1] - 0.05 * vrange # 5% extra looks nice - vmax = values[n2] + 0.05 * vrange - return vmin, vmax - - -def get_plot_range(values, all_values, option): - if option == "System`Automatic": - result = automatic_plot_range(values) - elif option == "System`All": - if not all_values: - result = [0, 1] - else: - result = min(all_values), max(all_values) - else: - result = option - if result[0] == result[1]: - value = result[0] - if value > 0: - return 0, value * 2 - if value < 0: - return value * 2, 0 - return -1, 1 - return result - - class _Plot(Builtin): attributes = A_HOLD_ALL | A_PROTECTED @@ -419,7 +301,7 @@ class _Plot(Builtin): } ) - def apply(self, functions, x, start, stop, evaluation, options): + def eval(self, functions, x, start, stop, evaluation, options): """%(name)s[functions_, {x_Symbol, start_, stop_}, OptionsPattern[%(name)s]]""" if isinstance(functions, Symbol) and functions.name is not x.get_name(): @@ -451,23 +333,13 @@ def apply(self, functions, x, start, stop, evaluation, options): return evaluation.message(self.get_name(), "plln", stop, expr) if py_start >= py_stop: return evaluation.message(self.get_name(), "plld", expr_limits) - start, stop = py_start, py_stop - - # PlotRange Option - def check_range(range): - if range in ("System`Automatic", "System`All"): - return True - if isinstance(range, list) and len(range) == 2: - if isinstance(range[0], numbers.Real) and isinstance( # noqa - range[1], numbers.Real - ): - return True - return False plotrange_option = self.get_option(options, "PlotRange", evaluation) plotrange = eval_N(plotrange_option, evaluation).to_python() - x_range, y_range = self.get_plotrange(plotrange, start, stop) - if not check_range(x_range) or not check_range(y_range): + x_range, y_range = self.get_plotrange(plotrange, py_start, py_stop) + if not check_plot_range(x_range, numbers.Real) or not check_plot_range( + y_range, numbers.Real + ): evaluation.message(self.get_name(), "prng", plotrange_option) x_range, y_range = [start, stop], "Automatic" @@ -552,198 +424,21 @@ def check_exclusion(excl): # exclusions is now either 'None' or a list of reals and 'Automatic' assert exclusions == "System`None" or isinstance(exclusions, list) - # constants to generate colors - hue = 0.67 - hue_pos = 0.236068 - hue_neg = -0.763932 - - def get_points_minmax(points): - xmin = xmax = ymin = ymax = None - for line in points: - for x, y in line: - if xmin is None or x < xmin: - xmin = x - if xmax is None or x > xmax: - xmax = x - if ymin is None or y < ymin: - ymin = y - if ymax is None or y > ymax: - ymax = y - return xmin, xmax, ymin, ymax - - def get_points_range(points): - xmin, xmax, ymin, ymax = get_points_minmax(points) - if xmin is None or xmax is None: - xmin, xmax = 0, 1 - if ymin is None or ymax is None: - ymin, ymax = 0, 1 - return zero_to_one(xmax - xmin), zero_to_one(ymax - ymin) - - function_hues = [] - base_plot_points = [] # list of points in base subdivision - plot_points = [] # list of all plotted points - mesh_points = [] - graphics = [] # list of resulting graphics primitives - for index, f in enumerate(functions): - points = [] - xvalues = [] # x value for each point in points - tmp_mesh_points = [] # For this function only - continuous = False - d = (stop - start) / (plotpoints - 1) - cf = compile_quiet_function(f, [x_name], evaluation, self.expect_list) - for i in range(plotpoints): - x_value = start + i * d - point = self.eval_f(cf, x_value) - if point is not None: - if continuous: - points[-1].append(point) - xvalues[-1].append(x_value) - else: - points.append([point]) - xvalues.append([x_value]) - continuous = True - else: - continuous = False - - base_points = [] - for line in points: - base_points.extend(line) - base_plot_points.extend(base_points) - - xmin, xmax = automatic_plot_range([xx for xx, yy in base_points]) - xscale = 1.0 / zero_to_one(xmax - xmin) - ymin, ymax = automatic_plot_range([yy for xx, yy in base_points]) - yscale = 1.0 / zero_to_one(ymax - ymin) - - if mesh == "System`Full": - for line in points: - tmp_mesh_points.extend(line) - - def find_excl(excl): - # Find which line the exclusion is in - for line in range(len(xvalues)): # TODO: Binary Search faster? - if xvalues[line][0] <= excl and xvalues[line][-1] >= excl: - break - if ( - xvalues[line][-1] <= excl - and xvalues[min(line + 1, len(xvalues) - 1)][0] - >= excl # nopep8 - ): - return min(line + 1, len(xvalues) - 1), 0, False - xi = 0 - for xi in range(len(xvalues[line]) - 1): - if xvalues[line][xi] <= excl and xvalues[line][xi + 1] >= excl: - return line, xi + 1, True - return line, xi + 1, False - - if exclusions != "System`None": - for excl in exclusions: - if excl != "System`Automatic": - l, xi, split_required = find_excl(excl) - if split_required: - xvalues.insert(l + 1, xvalues[l][xi:]) - xvalues[l] = xvalues[l][:xi] - points.insert(l + 1, points[l][xi:]) - points[l] = points[l][:xi] - # assert(xvalues[l][-1] <= excl <= xvalues[l+1][0]) - - # Adaptive Sampling - loop again and interpolate highly angled - # sections - - # Cos of the maximum angle between successive line segments - ang_thresh = cos(pi / 180) - - for line, line_xvalues in zip(points, xvalues): - recursion_count = 0 - smooth = False - while not smooth and recursion_count < maxrecursion: - recursion_count += 1 - smooth = True - i = 2 - while i < len(line): - vec1 = ( - xscale * (line[i - 1][0] - line[i - 2][0]), - yscale * (line[i - 1][1] - line[i - 2][1]), - ) - vec2 = ( - xscale * (line[i][0] - line[i - 1][0]), - yscale * (line[i][1] - line[i - 1][1]), - ) - try: - angle = (vec1[0] * vec2[0] + vec1[1] * vec2[1]) / sqrt( - (vec1[0] ** 2 + vec1[1] ** 2) - * (vec2[0] ** 2 + vec2[1] ** 2) - ) - except ZeroDivisionError: - angle = 0.0 - if abs(angle) < ang_thresh: - smooth = False - incr = 0 - - x_value = 0.5 * (line_xvalues[i - 1] + line_xvalues[i]) - - point = self.eval_f(cf, x_value) - if point is not None: - line.insert(i, point) - line_xvalues.insert(i, x_value) - incr += 1 - - x_value = 0.5 * (line_xvalues[i - 2] + line_xvalues[i - 1]) - point = self.eval_f(cf, x_value) - if point is not None: - line.insert(i - 1, point) - line_xvalues.insert(i - 1, x_value) - incr += 1 - - i += incr - i += 1 - - if exclusions == "System`None": # Join all the Lines - points = [[(xx, yy) for line in points for xx, yy in line]] - - graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) - graphics.append(Expression(SymbolLine, from_python(points))) - - for line in points: - plot_points.extend(line) - - if mesh == "System`All": - for line in points: - tmp_mesh_points.extend(line) - - if mesh != "System`None": - mesh_points.append(tmp_mesh_points) - - function_hues.append(hue) - - if index % 4 == 0: - hue += hue_pos - else: - hue += hue_neg - if hue > 1: - hue -= 1 - if hue < 0: - hue += 1 - - x_range = get_plot_range( - [xx for xx, yy in base_plot_points], [xx for xx, yy in plot_points], x_range - ) - y_range = get_plot_range( - [yy for xx, yy in base_plot_points], [yy for xx, yy in plot_points], y_range - ) - - options["System`PlotRange"] = from_python([x_range, y_range]) - - if mesh != "None": - for hue, points in zip(function_hues, mesh_points): - graphics.append( - Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6) - ) - meshpoints = [to_mathics_list(xx, yy) for xx, yy in points] - graphics.append(Expression(SymbolPoint, ListExpression(*meshpoints))) - - return Expression( - SymbolGraphics, ListExpression(*graphics), *options_to_rules(options) + return eval_Plot( + functions, + self._apply_fn, + x_name, + py_start, + py_stop, + x_range, + y_range, + plotpoints, + mesh, + self.expect_list, + exclusions, + maxrecursion, + options, + evaluation, ) @@ -764,7 +459,7 @@ class _Chart(Builtin): def _draw(self, data, color, evaluation, options): raise NotImplementedError() - def apply(self, points, evaluation, options): + def eval(self, points, evaluation, options): "%(name)s[points_, OptionsPattern[%(name)s]]" points = points.evaluate(evaluation) @@ -1241,7 +936,7 @@ class Histogram(Builtin): ) summary_text = "draw a histogram" - def apply(self, points, spec, evaluation, options): + def eval(self, points, spec, evaluation, options): "%(name)s[points_, spec___, OptionsPattern[%(name)s]]" points = points.evaluate(evaluation) @@ -1280,7 +975,8 @@ def to_numbers(li): span = maximum - minimum from math import ceil - from mpmath import floor as mpfloor, ceil as mpceil + + from mpmath import ceil as mpceil, floor as mpfloor class Distribution: def __init__(self, data, n_bins): @@ -1468,6 +1164,11 @@ def auto_bins(): class _ListPlot(Builtin): + """ + Computation part of ListPlot, ListLinePlot, and DiscretePlot + 2-Dimensional plot a list of points in some fashion. + """ + messages = { "prng": ( "Value of option PlotRange -> `1` is not All, Automatic or " @@ -1476,25 +1177,14 @@ class _ListPlot(Builtin): "joind": "Value of option Joined -> `1` is not True or False.", } - def apply(self, points, evaluation, options): + def eval(self, points, evaluation, options): "%(name)s[points_, OptionsPattern[%(name)s]]" plot_name = self.get_name() all_points = eval_N(points, evaluation).to_python() - # FIXME: arrange forself to have a .symbolname property or attribute + # FIXME: arrange for self to have a .symbolname property or attribute expr = Expression(Symbol(self.get_name()), points, *options_to_rules(options)) - # PlotRange Option - def check_range(range): - if range in ("System`Automatic", "System`All"): - return True - if isinstance(range, list) and len(range) == 2: - if isinstance(range[0], numbers.Real) and isinstance( # noqa - range[1], numbers.Real - ): - return True - return False - plotrange_option = self.get_option(options, "PlotRange", evaluation) plotrange = eval_N(plotrange_option, evaluation).to_python() if plotrange == "System`All": @@ -1506,7 +1196,7 @@ def check_range(range): elif isinstance(plotrange, list) and len(plotrange) == 2: if all(isinstance(pr, numbers.Real) for pr in plotrange): plotrange = ["System`All", plotrange] - elif all(check_range(pr) for pr in plotrange): + elif all(check_plot_range(pr, numbers.Real) for pr in plotrange): pass else: evaluation.message(self.get_name(), "prng", plotrange_option) @@ -1619,8 +1309,9 @@ def check_range(range): for indx, line in enumerate(all_points): graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) for segment in line: + mathics_segment = from_python(segment) if joined: - graphics.append(Expression(SymbolLine, from_python(segment))) + graphics.append(Expression(SymbolLine, mathics_segment)) if filling is not None: graphics.append( Expression( @@ -1633,6 +1324,18 @@ def check_range(range): graphics.append( Expression(SymbolPolygon, from_python(fill_area)) ) + elif discrete_plot: + graphics.append(Expression(SymbolPoint, mathics_segment)) + for mathics_point in mathics_segment: + graphics.append( + Expression( + SymbolLine, + ListExpression( + ListExpression(mathics_point[0], Integer0), + mathics_point, + ), + ) + ) else: graphics.append(Expression(SymbolPoint, from_python(segment))) if filling is not None: @@ -1681,7 +1384,7 @@ class _Plot3D(Builtin): ), } - def apply(self, functions, x, xstart, xstop, y, ystart, ystop, evaluation, options): + def eval(self, functions, x, xstart, xstop, y, ystart, ystop, evaluation, options): """%(name)s[functions_, {x_Symbol, xstart_, xstop_}, {y_Symbol, ystart_, ystop_}, OptionsPattern[%(name)s]]""" xexpr_limits = ListExpression(x, xstart, xstop) @@ -1772,15 +1475,16 @@ def check_plotpoints(steps): for indx, f in enumerate(functions): stored = {} - cf = compile_quiet_function( + compiled_fn = compile_quiet_function( f, [x.get_name(), y.get_name()], evaluation, False ) - def eval_f(x_value, y_value): + def _apply_fn(x_value, y_value): try: + # Try to used cached value first return stored[(x_value, y_value)] except KeyError: - value = cf(x_value, y_value) + value = compiled_fn(x_value, y_value) if value is not None: value = float(value) stored[(x_value, y_value)] = value @@ -1791,7 +1495,7 @@ def eval_f(x_value, y_value): 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) + v1, v2, v3 = _apply_fn(x1, y1), _apply_fn(x2, y2), _apply_fn(x3, y3) if (v1 is v2 is v3 is None) and (depth > max_depth // 2): # fast finish because the entire region is undefined but @@ -1868,10 +1572,10 @@ def triangle(x1, y1, x2, y2, x3, y3, depth=0): ) ) - v1 = eval_f(x1, y1) - v2 = eval_f(x2, y2) - v3 = eval_f(x3, y3) - v4 = eval_f(x4, y4) + v1 = _apply_fn(x1, y1) + v2 = _apply_fn(x2, y2) + v3 = _apply_fn(x3, y3) + v4 = _apply_fn(x4, y4) if v1 is None or v4 is None: triangle(x1, y1, x2, y2, x3, y3) @@ -2192,7 +1896,7 @@ def get_plotrange(self, plotrange, start, stop): x_range = [start, stop] return x_range, y_range - def eval_f(self, f, x_value): + def _apply_fn(self, f, x_value): value = f(x_value) if value is not None: return (x_value, value) @@ -2201,14 +1905,17 @@ def eval_f(self, f, x_value): class ParametricPlot(_Plot): """
-
'ParametricPlot[{$f_x$, $f_y$}, {$u$, $umin$, $umax$}]' -
plots a parametric function $f$ with the parameter $u$ ranging from $umin$ to $umax$. -
'ParametricPlot[{{$f_x$, $f_y$}, {$g_x$, $g_y$}, ...}, {$u$, $umin$, $umax$}]' -
plots several parametric functions $f$, $g$, ... -
'ParametricPlot[{$f_x$, $f_y$}, {$u$, $umin$, $umax$}, {$v$, $vmin$, $vmax$}]' -
plots a parametric area. -
'ParametricPlot[{{$f_x$, $f_y$}, {$g_x$, $g_y$}, ...}, {$u$, $umin$, $umax$}, {$v$, $vmin$, $vmax$}]' -
plots several parametric areas. +
'ParametricPlot[{$f_x$, $f_y$}, {$u$, $umin$, $umax$}]' +
plots a parametric function $f$ with the parameter $u$ ranging from $umin$ to $umax$. + +
'ParametricPlot[{{$f_x$, $f_y$}, {$g_x$, $g_y$}, ...}, {$u$, $umin$, $umax$}]' +
plots several parametric functions $f$, $g$, ... + +
'ParametricPlot[{$f_x$, $f_y$}, {$u$, $umin$, $umax$}, {$v$, $vmin$, $vmax$}]' +
plots a parametric area. + +
'ParametricPlot[{{$f_x$, $f_y$}, {$g_x$, $g_y$}, ...}, {$u$, $umin$, $umax$}, {$v$, $vmin$, $vmax$}]' +
plots several parametric areas.
>> ParametricPlot[{Sin[u], Cos[3 u]}, {u, 0, 2 Pi}] @@ -2254,7 +1961,7 @@ def get_plotrange(self, plotrange, start, stop): x_range, y_range = plotrange return x_range, y_range - def eval_f(self, f, x_value): + def _apply_fn(self, f, x_value): value = f(x_value) if value is not None and len(value) == 2: return value @@ -2326,7 +2033,7 @@ def get_plotrange(self, plotrange, start, stop): x_range, y_range = plotrange return x_range, y_range - def eval_f(self, f, x_value): + def _apply_fn(self, f, x_value): value = f(x_value) if value is not None: return (value * cos(x_value), value * sin(x_value)) @@ -2335,12 +2042,14 @@ def eval_f(self, f, x_value): class ListPlot(_ListPlot): """
-
'ListPlot[{$y_1$, $y_2$, ...}]' -
plots a list of y-values, assuming integer x-values 1, 2, 3, ... -
'ListPlot[{{$x_1$, $y_1$}, {$x_2$, $y_2$}, ...}]' -
plots a list of $x$, $y$ pairs. -
'ListPlot[{$list_1$, $list_2$, ...}]' -
plots several lists of points. +
'ListPlot[{$y_1$, $y_2$, ...}]' +
plots a list of y-values, assuming integer x-values 1, 2, 3, ... + +
'ListPlot[{{$x_1$, $y_1$}, {$x_2$, $y_2$}, ...}]' +
plots a list of $x$, $y$ pairs. + +
'ListPlot[{$list_1$, $list_2$, ...}]' +
plots several lists of points.
ListPlot accepts a superset of the Graphics options. @@ -2361,6 +2070,7 @@ class ListPlot(_ListPlot): "PlotPoints": "None", "Filling": "None", "Joined": "False", + "Discrete": "False", } ) summary_text = "plot lists of points" diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py new file mode 100644 index 000000000..2e671ef4d --- /dev/null +++ b/mathics/eval/plot.py @@ -0,0 +1,385 @@ +""" +Evaluation routines for 2D plotting. +""" +from math import cos, isinf, isnan, pi, sqrt +from typing import Callable, Iterable, List, Optional, Union + +from mathics.builtin.numeric import chop +from mathics.builtin.options import options_to_rules +from mathics.builtin.scoping import dynamic_scoping +from mathics.core.atoms import Real, String +from mathics.core.convert.expression import to_mathics_list +from mathics.core.convert.python import from_python +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.symbols import SymbolN, SymbolPower +from mathics.core.systemsymbols import ( + SymbolGraphics, + SymbolHue, + SymbolLine, + SymbolMessageName, + SymbolPoint, + SymbolQuiet, +) + +RealPoint6 = Real(0.6) +RealPoint2 = Real(0.2) + + +try: + from mathics.compile import CompileArg, CompileError, _compile, real_type + + has_compile = True +except ImportError: + has_compile = False + + +def automatic_plot_range(values): + """Calculates mean and standard deviation, throwing away all points + which are more than 'thresh' number of standard deviations away from + the mean. These are then used to find good vmin and vmax values. These + values can then be used to find Automatic Plotrange.""" + + if not values: + return 0, 1 + + thresh = 2.0 + values = sorted(values) + valavg = sum(values) / len(values) + valdev = sqrt( + sum([(x - valavg) ** 2 for x in values]) / zero_to_one(len(values) - 1) + ) + + n1, n2 = 0, len(values) - 1 + if valdev != 0: + for v in values: + if abs(v - valavg) / valdev < thresh: + break + n1 += 1 + for v in values[::-1]: + if abs(v - valavg) / valdev < thresh: + break + n2 -= 1 + + vrange = values[n2] - values[n1] + vmin = values[n1] - 0.05 * vrange # 5% extra looks nice + vmax = values[n2] + 0.05 * vrange + return vmin, vmax + + +def compile_quiet_function(expr, arg_names, evaluation, list_is_expected: bool): + """ + Given an expression return a quiet callable version. + Compiles the expression where possible. + """ + if has_compile and not list_is_expected: + try: + cfunc = _compile( + expr, [CompileArg(arg_name, real_type) for arg_name in arg_names] + ) + except CompileError: + pass + else: + + def quiet_f(*args): + try: + result = cfunc(*args) + if not (isnan(result) or isinf(result)): + return result + except Exception: + pass + return None + + return quiet_f + expr = Expression(SymbolN, expr).evaluate(evaluation) + quiet_expr = Expression( + SymbolQuiet, + expr, + ListExpression(Expression(SymbolMessageName, SymbolPower, String("infy"))), + ) + + def quiet_f(*args): + vars = {arg_name: Real(arg) for arg_name, arg in zip(arg_names, args)} + value = dynamic_scoping(quiet_expr.evaluate, vars, evaluation) + if list_is_expected: + if value.has_form("List", None): + value = [extract_pyreal(item) for item in value.elements] + if any(item is None for item in value): + return None + return value + else: + return None + else: + value = extract_pyreal(value) + if value is None or isinf(value) or isnan(value): + return None + return value + + return quiet_f + + +def eval_Plot( + functions: List[Expression], + apply_fn: Callable, + x_name: str, + start: int, + stop: int, + x_range: list, + y_range, + plotpoints: int, + mesh, + list_is_expected: bool, + exclusions: list, + max_recursion: int, + options: dict, + evaluation: Evaluation, +) -> Expression: + """ + Evaluation part of Plot[] + + Note: (?) indicates somewhat vague guesses. + + functions: is a list of Mathics M-Expressions to be evaluated + start: minimum x-axis value + stop: maximum t x-axis value + x_name; the name of the function parameter name used by ``functions`` + x_range: x-axis range of the form Automatic, All, or [min, max] + y_range: y-axis range of the form Automatic, All, or [min, max] + y_range: either Automatic, All, or of the form [min, max] + plotpoints: number of points to plot + list_is_expected: list is expected in evaluation (?) + max_recursion: maximum number of levels of recursion in evaluation (?) + options: Plot options + evaluation: Expression evaluation object typically needed in evaluation + """ + # constants to generate colors + hue = 0.67 + hue_pos = 0.236068 + hue_neg = -0.763932 + + def get_points_minmax(points): + xmin = xmax = ymin = ymax = None + for line in points: + for x, y in line: + if xmin is None or x < xmin: + xmin = x + if xmax is None or x > xmax: + xmax = x + if ymin is None or y < ymin: + ymin = y + if ymax is None or y > ymax: + ymax = y + return xmin, xmax, ymin, ymax + + def get_points_range(points): + xmin, xmax, ymin, ymax = get_points_minmax(points) + if xmin is None or xmax is None: + xmin, xmax = 0, 1 + if ymin is None or ymax is None: + ymin, ymax = 0, 1 + return zero_to_one(xmax - xmin), zero_to_one(ymax - ymin) + + function_hues = [] + base_plot_points = [] # list of points in base subdivision + plot_points = [] # list of all plotted points + mesh_points = [] + graphics = [] # list of resulting graphics primitives + for index, f in enumerate(functions): + points = [] + xvalues = [] # x value for each point in points + tmp_mesh_points = [] # For this function only + continuous = False + d = (stop - start) / (plotpoints - 1) + compiled_fn = compile_quiet_function(f, [x_name], evaluation, list_is_expected) + for i in range(plotpoints): + x_value = start + i * d + point = apply_fn(compiled_fn, x_value) + if point is not None: + if continuous: + points[-1].append(point) + xvalues[-1].append(x_value) + else: + points.append([point]) + xvalues.append([x_value]) + continuous = True + else: + continuous = False + + base_points = [] + for line in points: + base_points.extend(line) + base_plot_points.extend(base_points) + + xmin, xmax = automatic_plot_range([xx for xx, yy in base_points]) + xscale = 1.0 / zero_to_one(xmax - xmin) + ymin, ymax = automatic_plot_range([yy for xx, yy in base_points]) + yscale = 1.0 / zero_to_one(ymax - ymin) + + if mesh == "System`Full": + for line in points: + tmp_mesh_points.extend(line) + + def find_excl(excl): + # Find which line the exclusion is in + for line in range(len(xvalues)): # TODO: Binary Search faster? + if xvalues[line][0] <= excl and xvalues[line][-1] >= excl: + break + if ( + xvalues[line][-1] <= excl + and xvalues[min(line + 1, len(xvalues) - 1)][0] >= excl + ): + return min(line + 1, len(xvalues) - 1), 0, False + xi = 0 + for xi in range(len(xvalues[line]) - 1): + if xvalues[line][xi] <= excl and xvalues[line][xi + 1] >= excl: + return line, xi + 1, True + return line, xi + 1, False + + if exclusions != "System`None": + for excl in exclusions: + if excl != "System`Automatic": + l, xi, split_required = find_excl(excl) + if split_required: + xvalues.insert(l + 1, xvalues[l][xi:]) + xvalues[l] = xvalues[l][:xi] + points.insert(l + 1, points[l][xi:]) + points[l] = points[l][:xi] + # assert(xvalues[l][-1] <= excl <= xvalues[l+1][0]) + + # Adaptive Sampling - loop again and interpolate highly angled + # sections + + # Cos of the maximum angle between successive line segments + ang_thresh = cos(pi / 180) + + for line, line_xvalues in zip(points, xvalues): + recursion_count = 0 + smooth = False + while not smooth and recursion_count < max_recursion: + recursion_count += 1 + smooth = True + i = 2 + while i < len(line): + vec1 = ( + xscale * (line[i - 1][0] - line[i - 2][0]), + yscale * (line[i - 1][1] - line[i - 2][1]), + ) + vec2 = ( + xscale * (line[i][0] - line[i - 1][0]), + yscale * (line[i][1] - line[i - 1][1]), + ) + try: + angle = (vec1[0] * vec2[0] + vec1[1] * vec2[1]) / sqrt( + (vec1[0] ** 2 + vec1[1] ** 2) + * (vec2[0] ** 2 + vec2[1] ** 2) + ) + except ZeroDivisionError: + angle = 0.0 + if abs(angle) < ang_thresh: + smooth = False + incr = 0 + + x_value = 0.5 * (line_xvalues[i - 1] + line_xvalues[i]) + + point = apply_fn(compiled_fn, x_value) + if point is not None: + line.insert(i, point) + line_xvalues.insert(i, x_value) + incr += 1 + + x_value = 0.5 * (line_xvalues[i - 2] + line_xvalues[i - 1]) + point = apply_fn(compiled_fn, x_value) + if point is not None: + line.insert(i - 1, point) + line_xvalues.insert(i - 1, x_value) + incr += 1 + + i += incr + i += 1 + + if exclusions == "System`None": # Join all the Lines + points = [[(xx, yy) for line in points for xx, yy in line]] + + graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) + graphics.append(Expression(SymbolLine, from_python(points))) + + for line in points: + plot_points.extend(line) + + if mesh == "System`All": + for line in points: + tmp_mesh_points.extend(line) + + if mesh != "System`None": + mesh_points.append(tmp_mesh_points) + + function_hues.append(hue) + + if index % 4 == 0: + hue += hue_pos + else: + hue += hue_neg + if hue > 1: + hue -= 1 + if hue < 0: + hue += 1 + + x_range = get_plot_range( + [xx for xx, yy in base_plot_points], [xx for xx, yy in plot_points], x_range + ) + y_range = get_plot_range( + [yy for xx, yy in base_plot_points], [yy for xx, yy in plot_points], y_range + ) + + options["System`PlotRange"] = from_python([x_range, y_range]) + + if mesh != "None": + for hue, points in zip(function_hues, mesh_points): + graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) + meshpoints = [to_mathics_list(xx, yy) for xx, yy in points] + graphics.append(Expression(SymbolPoint, ListExpression(*meshpoints))) + + return Expression( + SymbolGraphics, ListExpression(*graphics), *options_to_rules(options) + ) + + +def extract_pyreal(value) -> Optional[float]: + if isinstance(value, Real): + return chop(value).round_to_float() + return None + + +def get_plot_range(values: Iterable, all_values: Iterable, option: str) -> tuple: + """ + Returns a tuple of the min and max values in values or + """ + if option == "System`Automatic": + result = automatic_plot_range(values) + elif option == "System`All": + if not all_values: + result = (0, 1) + else: + result = min(all_values), max(all_values) + else: + result = option + if result[0] == result[1]: + value = result[0] + if value > 0: + return 0, value * 2 + if value < 0: + return value * 2, 0 + return -1, 1 + return result + + +def zero_to_one(value: Union[float, int]) -> Union[float, int]: + """ + Return 1 only if ``value`` is zero, otherwise keep the value as is. + + This is useful in scaling when the value can be used as + a divisor or when determining the number of points to plot and we want to + assure there is at least one point plotted. + """ + return 1 if value == 0 else value From 2b5c208f76ead0e0e85f95246d53a48ef900fc68 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 14 Dec 2022 20:06:13 -0500 Subject: [PATCH 013/121] Add WMA and other links --- mathics/builtin/drawing/plot.py | 2693 ++++++++++++++++--------------- mathics/core/systemsymbols.py | 5 +- 2 files changed, 1354 insertions(+), 1344 deletions(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index e1898b930..1e463a673 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -108,16 +108,140 @@ def gradient_palette(color_function, n, evaluation): # always returns RGB value return -class ColorDataFunction(Builtin): - """ -
-
'ColorDataFunction[range, ...]' -
is a function that represents a color scheme. -
- """ +class _Chart(Builtin): + attributes = A_HOLD_ALL | A_PROTECTED + never_monochrome = False + options = Graphics.options.copy() + options.update( + { + "Mesh": "None", + "PlotRange": "Automatic", + "ChartLabels": "None", + "ChartLegends": "None", + "ChartStyle": "Automatic", + } + ) - summary_text = "color scheme object" - pass + def _draw(self, data, color, evaluation, options): + raise NotImplementedError() + + def eval(self, points, evaluation, options): + "%(name)s[points_, OptionsPattern[%(name)s]]" + + points = points.evaluate(evaluation) + + if points.get_head_name() != "System`List" or not points.elements: + return + + if points.elements[0].get_head_name() == "System`List": + if not all( + group.get_head_name() == "System`List" for group in points.elements + ): + return + multiple_colors = True + groups = points.elements + else: + multiple_colors = False + groups = [points] + + chart_legends = self.get_option(options, "ChartLegends", evaluation) + has_chart_legends = chart_legends.get_head_name() == "System`List" + if has_chart_legends: + multiple_colors = True + + def to_number(x): + if isinstance(x, Integer): + return float(x.get_int_value()) + return x.round_to_float(evaluation=evaluation) + + data = [[to_number(x) for x in group.elements] for group in groups] + + chart_style = self.get_option(options, "ChartStyle", evaluation) + if ( + isinstance(chart_style, Symbol) + and chart_style.get_name() == "System`Automatic" + ): + chart_style = String("Automatic") + + if chart_style.get_head_name() == "System`List": + colors = chart_style.elements + spread_colors = False + elif isinstance(chart_style, String): + if chart_style.get_string_value() == "Automatic": + mpl_colors = palettable.wesanderson.Moonrise1_5.mpl_colors + else: + mpl_colors = ColorData.colors(chart_style.get_string_value()) + if mpl_colors is None: + return + multiple_colors = True + + if not multiple_colors and not self.never_monochrome: + colors = [to_expression(SymbolRGBColor, *mpl_colors[0])] + else: + colors = [to_expression(SymbolRGBColor, *c) for c in mpl_colors] + spread_colors = True + else: + return + + def legends(names): + if not data: + return + + n = len(data[0]) + for d in data[1:]: + if len(d) != n: + return # data groups should have same size + + def box(color): + return Expression( + SymbolGraphics, + ListExpression( + Expression(SymbolFaceForm, color), Expression(SymbolRectangle) + ), + Expression( + SymbolRule, + Symbol("ImageSize"), + ListExpression(Integer(50), Integer(50)), + ), + ) + + rows_per_col = 5 + + n_cols = 1 + len(names) // rows_per_col + if len(names) % rows_per_col == 0: + n_cols -= 1 + + if n_cols == 1: + n_rows = len(names) + else: + n_rows = rows_per_col + + for i in range(n_rows): + items = [] + for j in range(n_cols): + k = 1 + i + j * rows_per_col + if k - 1 < len(names): + items.extend([box(color(k, n)), names[k - 1]]) + else: + items.extend([String(""), String("")]) + yield ListExpression(*items) + + def color(k, n): + if spread_colors and n < len(colors): + index = int(k * (len(colors) - 1)) // n + return colors[index] + else: + return colors[(k - 1) % len(colors)] + + chart = self._draw(data, color, evaluation, options) + + if has_chart_legends: + grid = Expression( + SymbolGrid, ListExpression(*list(legends(chart_legends.elements))) + ) + chart = Expression(SymbolRow, ListExpression(chart, grid)) + + return chart class _GradientColorScheme: @@ -143,126 +267,208 @@ def color_data_function(self, name): return Expression(SymbolColorDataFunction, *arguments) -class _PredefinedGradient(_GradientColorScheme): - def __init__(self, colors): - self._colors = colors - - def colors(self): - return self._colors - - -class _PalettableGradient(_GradientColorScheme): - def __init__(self, palette, reversed): - self.palette = palette - self.reversed = reversed - - def colors(self): - colors = self.palette.mpl_colors - if self.reversed: - colors = list(reversed(colors)) - return colors - - -class ColorData(Builtin): +class _ListPlot(Builtin): """ -
-
'ColorData["$name$"]' -
returns a color function with the given $name$. -
- - Define a user-defined color function: - >> Unprotect[ColorData]; ColorData["test"] := ColorDataFunction["test", "Gradients", {0, 1}, Blend[{Red, Green, Blue}, #1] &]; Protect[ColorData] - - Compare it to the default color function, 'LakeColors': - >> {DensityPlot[x + y, {x, -1, 1}, {y, -1, 1}], DensityPlot[x + y, {x, -1, 1}, {y, -1, 1}, ColorFunction->"test"]} - = {-Graphics-, -Graphics-} + Computation part of ListPlot, ListLinePlot, and DiscretePlot + 2-Dimensional plot a list of points in some fashion. """ - # rules = { - # 'ColorData["LakeColors"]': ( - # """ColorDataFunction["LakeColors", "Gradients", {0, 1}, - # Blend[{RGBColor[0.293416, 0.0574044, 0.529412], - # RGBColor[0.563821, 0.527565, 0.909499], - # RGBColor[0.762631, 0.846998, 0.914031], - # RGBColor[0.941176, 0.906538, 0.834043]}, #1] &]"""), - # } - summary_text = "named color gradients and collections" messages = { - "notent": "`1` is not a known color scheme. ColorData[] gives you lists of schemes.", - } - - palettes = { - "LakeColors": _PredefinedGradient( - [ - (0.293416, 0.0574044, 0.529412), - (0.563821, 0.527565, 0.909499), - (0.762631, 0.846998, 0.914031), - (0.941176, 0.906538, 0.834043), - ] - ), - "Pastel": _PalettableGradient( - palettable.colorbrewer.qualitative.Pastel1_9, False - ), - "Rainbow": _PalettableGradient( - palettable.colorbrewer.diverging.Spectral_9, True - ), - "RedBlueTones": _PalettableGradient( - palettable.colorbrewer.diverging.RdBu_11, False - ), - "GreenPinkTones": _PalettableGradient( - palettable.colorbrewer.diverging.PiYG_9, False - ), - "GrayTones": _PalettableGradient( - palettable.colorbrewer.sequential.Greys_9, False - ), - "SolarColors": _PalettableGradient( - palettable.colorbrewer.sequential.OrRd_9, True - ), - "CherryTones": _PalettableGradient( - palettable.colorbrewer.sequential.Reds_9, True - ), - "FuchsiaTones": _PalettableGradient( - palettable.colorbrewer.sequential.RdPu_9, True - ), - "SiennaTones": _PalettableGradient( - palettable.colorbrewer.sequential.Oranges_9, True - ), - # specific to Mathics - "Paired": _PalettableGradient( - palettable.colorbrewer.qualitative.Paired_9, False - ), - "Accent": _PalettableGradient( - palettable.colorbrewer.qualitative.Accent_8, False + "prng": ( + "Value of option PlotRange -> `1` is not All, Automatic or " + "an appropriate list of range specifications." ), - "Aquatic": _PalettableGradient(palettable.wesanderson.Aquatic1_5, False), - "Zissou": _PalettableGradient(palettable.wesanderson.Zissou_5, False), - "Tableau": _PalettableGradient(palettable.tableau.Tableau_20, False), - "TrafficLight": _PalettableGradient(palettable.tableau.TrafficLight_9, False), - "Moonrise1": _PalettableGradient(palettable.wesanderson.Moonrise1_5, False), + "joind": "Value of option Joined -> `1` is not True or False.", } - def eval_directory(self, evaluation): - "ColorData[]" - return ListExpression(String("Gradients")) + def eval(self, points, evaluation, options): + "%(name)s[points_, OptionsPattern[%(name)s]]" - def eval(self, name, evaluation): - "ColorData[name_String]" - py_name = name.get_string_value() - if py_name == "Gradients": - return ListExpression(*[String(name) for name in self.palettes.keys()]) - palette = ColorData.palettes.get(py_name, None) - if palette is None: - evaluation.message("ColorData", "notent", name) - return - return palette.color_data_function(py_name) + plot_name = self.get_name() + all_points = eval_N(points, evaluation).to_python() + # FIXME: arrange for self to have a .symbolname property or attribute + expr = Expression(Symbol(self.get_name()), points, *options_to_rules(options)) - @staticmethod - def colors(name, evaluation): - palette = ColorData.palettes.get(name, None) - if palette is None: - evaluation.message("ColorData", "notent", name) - return None - return palette.colors() + plotrange_option = self.get_option(options, "PlotRange", evaluation) + plotrange = eval_N(plotrange_option, evaluation).to_python() + if plotrange == "System`All": + plotrange = ["System`All", "System`All"] + elif plotrange == "System`Automatic": + plotrange = ["System`Automatic", "System`Automatic"] + elif isinstance(plotrange, numbers.Real): + plotrange = [[-plotrange, plotrange], [-plotrange, plotrange]] + elif isinstance(plotrange, list) and len(plotrange) == 2: + if all(isinstance(pr, numbers.Real) for pr in plotrange): + plotrange = ["System`All", plotrange] + elif all(check_plot_range(pr, numbers.Real) for pr in plotrange): + pass + else: + evaluation.message(self.get_name(), "prng", plotrange_option) + plotrange = ["System`Automatic", "System`Automatic"] + + x_range, y_range = plotrange[0], plotrange[1] + assert x_range in ("System`Automatic", "System`All") or isinstance( + x_range, list + ) + assert y_range in ("System`Automatic", "System`All") or isinstance( + y_range, list + ) + + # Filling option + # TODO: Fill between corresponding points in two datasets: + filling_option = self.get_option(options, "Filling", evaluation) + filling = eval_N(filling_option, evaluation).to_python() + if filling in [ + "System`Top", + "System`Bottom", + "System`Axis", + ] or isinstance( # noqa + filling, numbers.Real + ): + pass + else: + # Mathematica does not even check that filling is sane + filling = None + + # Joined Option + joined_option = self.get_option(options, "Joined", evaluation) + joined = joined_option.to_python() + if joined not in [True, False]: + evaluation.message(plot_name, "joind", joined_option, expr) + joined = False + + if isinstance(all_points, list) and len(all_points) != 0: + if all(not isinstance(point, list) for point in all_points): + # Only y values given + all_points = [ + [[float(i + 1), all_points[i]] for i in range(len(all_points))] + ] + elif all(isinstance(line, list) and len(line) == 2 for line in all_points): + # Single list of (x,y) pairs + all_points = [all_points] + elif all(isinstance(line, list) for line in all_points): + # List of lines + if all( + isinstance(point, list) and len(point) == 2 + for line in all_points + for point in line + ): + pass + elif all( + not isinstance(point, list) for line in all_points for point in line + ): + all_points = [ + [[float(i + 1), l] for i, l in enumerate(line)] + for line in all_points + ] + else: + return + else: + return + else: + return + + # Split into segments at missing data + all_points = [[line] for line in all_points] + for lidx, line in enumerate(all_points): + i = 0 + while i < len(all_points[lidx]): + seg = line[i] + for j, point in enumerate(seg): + if not ( + isinstance(point[0], (int, float)) + and isinstance(point[1], (int, float)) + ): + all_points[lidx].insert(i, seg[:j]) + all_points[lidx][i + 1] = seg[j + 1 :] + i -= 1 + break + + i += 1 + + y_range = get_plot_range( + [y for line in all_points for seg in line for x, y in seg], + [y for line in all_points for seg in line for x, y in seg], + y_range, + ) + x_range = get_plot_range( + [x for line in all_points for seg in line for x, y in seg], + [x for line in all_points for seg in line for x, y in seg], + x_range, + ) + + if filling == "System`Axis": + # TODO: Handle arbitary axis intercepts + filling = 0.0 + elif filling == "System`Bottom": + filling = y_range[0] + elif filling == "System`Top": + filling = y_range[1] + + hue = 0.67 + hue_pos = 0.236068 + hue_neg = -0.763932 + + graphics = [] + for indx, line in enumerate(all_points): + graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) + for segment in line: + mathics_segment = from_python(segment) + if joined: + graphics.append(Expression(SymbolLine, mathics_segment)) + if filling is not None: + graphics.append( + Expression( + SymbolHue, Real(hue), RealPoint6, RealPoint6, RealPoint2 + ) + ) + fill_area = list(segment) + fill_area.append([segment[-1][0], filling]) + fill_area.append([segment[0][0], filling]) + graphics.append( + Expression(SymbolPolygon, from_python(fill_area)) + ) + else: + graphics.append(Expression(SymbolPoint, from_python(segment))) + if filling is not None: + for point in segment: + graphics.append( + Expression( + SymbolLine, + from_python( + [[point[0], filling], [point[0], point[1]]] + ), + ) + ) + + if indx % 4 == 0: + hue += hue_pos + else: + hue += hue_neg + if hue > 1: + hue -= 1 + if hue < 0: + hue += 1 + + options["System`PlotRange"] = from_python([x_range, y_range]) + + return Expression( + SymbolGraphics, + ListExpression(*graphics), + *options_to_rules(options, Graphics.options) + ) + + +class _PalettableGradient(_GradientColorScheme): + def __init__(self, palette, reversed): + self.palette = palette + self.reversed = reversed + + def colors(self): + colors = self.palette.mpl_colors + if self.reversed: + colors = list(reversed(colors)) + return colors class _Plot(Builtin): @@ -442,332 +648,469 @@ def check_exclusion(excl): ) -class _Chart(Builtin): - attributes = A_HOLD_ALL | A_PROTECTED - never_monochrome = False - options = Graphics.options.copy() - options.update( - { - "Mesh": "None", - "PlotRange": "Automatic", - "ChartLabels": "None", - "ChartLegends": "None", - "ChartStyle": "Automatic", - } - ) +class _Plot3D(Builtin): + messages = { + "invmaxrec": ( + "MaxRecursion must be a non-negative integer; the recursion value " + "is limited to `2`. Using MaxRecursion -> `1`." + ), + "prng": ( + "Value of option PlotRange -> `1` is not All, Automatic or " + "an appropriate list of range specifications." + ), + "invmesh": "Mesh must be one of {None, Full, All}. Using Mesh->None.", + "invpltpts": ( + "Value of PlotPoints -> `1` is not a positive integer " + "or appropriate list of positive integers." + ), + } - def _draw(self, data, color, evaluation, options): - raise NotImplementedError() + def eval(self, functions, x, xstart, xstop, y, ystart, ystop, evaluation, options): + """%(name)s[functions_, {x_Symbol, xstart_, xstop_}, + {y_Symbol, ystart_, ystop_}, OptionsPattern[%(name)s]]""" + xexpr_limits = ListExpression(x, xstart, xstop) + yexpr_limits = ListExpression(y, ystart, ystop) + expr = Expression( + Symbol(self.get_name()), + functions, + xexpr_limits, + yexpr_limits, + *options_to_rules(options) + ) - def eval(self, points, evaluation, options): - "%(name)s[points_, OptionsPattern[%(name)s]]" + functions = self.get_functions_param(functions) + plot_name = self.get_name() - points = points.evaluate(evaluation) + def convert_limit(value, limits): + result = value.round_to_float(evaluation) + if result is None: + evaluation.message(plot_name, "plln", value, limits) + return result - if points.get_head_name() != "System`List" or not points.elements: + xstart = convert_limit(xstart, xexpr_limits) + xstop = convert_limit(xstop, xexpr_limits) + ystart = convert_limit(ystart, yexpr_limits) + ystop = convert_limit(ystop, yexpr_limits) + if None in (xstart, xstop, ystart, ystop): return - if points.elements[0].get_head_name() == "System`List": - if not all( - group.get_head_name() == "System`List" for group in points.elements - ): - return - multiple_colors = True - groups = points.elements - else: - multiple_colors = False - groups = [points] - - chart_legends = self.get_option(options, "ChartLegends", evaluation) - has_chart_legends = chart_legends.get_head_name() == "System`List" - if has_chart_legends: - multiple_colors = True - - def to_number(x): - if isinstance(x, Integer): - return float(x.get_int_value()) - return x.round_to_float(evaluation=evaluation) - - data = [[to_number(x) for x in group.elements] for group in groups] - - chart_style = self.get_option(options, "ChartStyle", evaluation) - if ( - isinstance(chart_style, Symbol) - and chart_style.get_name() == "System`Automatic" - ): - chart_style = String("Automatic") - - if chart_style.get_head_name() == "System`List": - colors = chart_style.elements - spread_colors = False - elif isinstance(chart_style, String): - if chart_style.get_string_value() == "Automatic": - mpl_colors = palettable.wesanderson.Moonrise1_5.mpl_colors - else: - mpl_colors = ColorData.colors(chart_style.get_string_value()) - if mpl_colors is None: - return - multiple_colors = True - - if not multiple_colors and not self.never_monochrome: - colors = [to_expression(SymbolRGBColor, *mpl_colors[0])] - else: - colors = [to_expression(SymbolRGBColor, *c) for c in mpl_colors] - spread_colors = True - else: + if ystart >= ystop: + evaluation.message(plot_name, "plln", ystop, expr) return - def legends(names): - if not data: - return - - n = len(data[0]) - for d in data[1:]: - if len(d) != n: - return # data groups should have same size + if xstart >= xstop: + evaluation.message(plot_name, "plln", xstop, expr) + return - def box(color): - return Expression( - SymbolGraphics, - ListExpression( - Expression(SymbolFaceForm, color), Expression(SymbolRectangle) - ), - Expression( - SymbolRule, - Symbol("ImageSize"), - ListExpression(Integer(50), Integer(50)), - ), - ) + # Mesh Option + mesh_option = self.get_option(options, "Mesh", evaluation) + mesh = mesh_option.to_python() + if mesh not in ["System`None", "System`Full", "System`All"]: + evaluation.message("Mesh", "ilevels", mesh_option) + mesh = "System`Full" - rows_per_col = 5 + # PlotPoints Option + plotpoints_option = self.get_option(options, "PlotPoints", evaluation) + plotpoints = plotpoints_option.to_python() - n_cols = 1 + len(names) // rows_per_col - if len(names) % rows_per_col == 0: - n_cols -= 1 + def check_plotpoints(steps): + if isinstance(steps, int) and steps > 0: + return True + return False - if n_cols == 1: - n_rows = len(names) - else: - n_rows = rows_per_col + if plotpoints == "System`None": + plotpoints = [7, 7] + elif check_plotpoints(plotpoints): + plotpoints = [plotpoints, plotpoints] - for i in range(n_rows): - items = [] - for j in range(n_cols): - k = 1 + i + j * rows_per_col - if k - 1 < len(names): - items.extend([box(color(k, n)), names[k - 1]]) - else: - items.extend([String(""), String("")]) - yield ListExpression(*items) + if not ( + isinstance(plotpoints, list) + and len(plotpoints) == 2 + and check_plotpoints(plotpoints[0]) + and check_plotpoints(plotpoints[1]) + ): + evaluation.message(self.get_name(), "invpltpts", plotpoints) + plotpoints = [7, 7] - def color(k, n): - if spread_colors and n < len(colors): - index = int(k * (len(colors) - 1)) // n - return colors[index] + # MaxRecursion Option + maxrec_option = self.get_option(options, "MaxRecursion", evaluation) + max_depth = maxrec_option.to_python() + if isinstance(max_depth, int): + if max_depth < 0: + max_depth = 0 + evaluation.message(self.get_name(), "invmaxrec", max_depth, 15) + elif max_depth > 15: + max_depth = 15 + evaluation.message(self.get_name(), "invmaxrec", max_depth, 15) else: - return colors[(k - 1) % len(colors)] + pass # valid + elif max_depth == float("inf"): + max_depth = 15 + evaluation.message(self.get_name(), "invmaxrec", max_depth, 15) + else: + max_depth = 0 + evaluation.message(self.get_name(), "invmaxrec", max_depth, 15) - chart = self._draw(data, color, evaluation, options) + # Plot the functions + graphics = [] + for indx, f in enumerate(functions): + stored = {} - if has_chart_legends: - grid = Expression( - SymbolGrid, ListExpression(*list(legends(chart_legends.elements))) + compiled_fn = compile_quiet_function( + f, [x.get_name(), y.get_name()], evaluation, False ) - chart = Expression(SymbolRow, ListExpression(chart, grid)) - - return chart + def _apply_fn(x_value, y_value): + try: + # Try to used cached value first + return stored[(x_value, y_value)] + except KeyError: + value = compiled_fn(x_value, y_value) + if value is not None: + value = float(value) + stored[(x_value, y_value)] = value + return value -class PieChart(_Chart): - """ -
-
'PieChart[{$a1$, $a2$ ...}]' -
draws a pie chart with sector angles proportional to $a1$, $a2$, .... -
+ triangles = [] - Drawing options include - - Charting: -
    -
  • Mesh -
  • PlotRange -
  • ChartLabels -
  • ChartLegends -
  • ChartStyle -
+ split_edges = set() # subdivided edges - PieChart specific: -
    -
  • Axes (default: False, False) -
  • AspectRatio (default 1) -
  • SectorOrigin: (default {Automatic, 0}) -
  • SectorSpacing" (default Automatic) -
+ def triangle(x1, y1, x2, y2, x3, y3, depth=0): + v1, v2, v3 = _apply_fn(x1, y1), _apply_fn(x2, y2), _apply_fn(x3, y3) - A hypothetical comparsion between types of pets owned: - >> PieChart[{30, 20, 10}, ChartLabels -> {Dogs, Cats, Fish}] - = -Graphics- + if (v1 is v2 is v3 is None) and (depth > max_depth // 2): + # fast finish because the entire region is undefined but + # recurse 'a little' to avoid missing well defined regions + return + elif v1 is None or v2 is None or v3 is None: + # 'triforce' pattern recursion to find the edge of defined region + # 1 + # /\ + # 4 /__\ 6 + # /\ /\ + # /__\/__\ + # 2 5 3 + if depth < max_depth: + x4, y4 = 0.5 * (x1 + x2), 0.5 * (y1 + y2) + x5, y5 = 0.5 * (x2 + x3), 0.5 * (y2 + y3) + x6, y6 = 0.5 * (x1 + x3), 0.5 * (y1 + y3) + split_edges.add( + ((x1, y1), (x2, y2)) + if (x2, y2) > (x1, y1) + else ((x2, y2), (x1, y1)) + ) + split_edges.add( + ((x2, y2), (x3, y3)) + if (x3, y3) > (x2, y2) + else ((x3, y3), (x2, y2)) + ) + split_edges.add( + ((x1, y1), (x3, y3)) + if (x3, y3) > (x1, y1) + else ((x3, y3), (x1, y1)) + ) + triangle(x1, y1, x4, y4, x6, y6, depth + 1) + triangle(x4, y4, x2, y2, x5, y5, depth + 1) + triangle(x6, y6, x5, y5, x3, y3, depth + 1) + triangle(x4, y4, x5, y5, x6, y6, depth + 1) + return + triangles.append(sorted(((x1, y1, v1), (x2, y2, v2), (x3, y3, v3)))) - A doughnut chart for a list of values: - >> PieChart[{8, 16, 2}, SectorOrigin -> {Automatic, 1.5}] - = -Graphics- + # linear (grid) sampling + numx = plotpoints[0] * 1.0 + numy = plotpoints[1] * 1.0 + for xi in range(plotpoints[0]): + for yi in range(plotpoints[1]): + # Decide which way to break the square grid into triangles + # by looking at diagonal lengths. + # + # 3___4 3___4 + # |\ | | /| + # | \ | versus | / | + # |__\| |/__| + # 1 2 1 2 + # + # Approaching the boundary of the well defined region is + # important too. Use first stategy if 1 or 4 are undefined + # and stategy 2 if either 2 or 3 are undefined. + # + (x1, x2, x3, x4) = ( + xstart + value * (xstop - xstart) + for value in ( + xi / numx, + (xi + 1) / numx, + xi / numx, + (xi + 1) / numx, + ) + ) + (y1, y2, y3, y4) = ( + ystart + value * (ystop - ystart) + for value in ( + yi / numy, + yi / numy, + (yi + 1) / numy, + (yi + 1) / numy, + ) + ) - A Pie chart with multple datasets: - >> PieChart[{{10, 20, 30}, {15, 22, 30}}] - = -Graphics- - - Same as the above, but without gaps between the groups of data: - >> PieChart[{{10, 20, 30}, {15, 22, 30}}, SectorSpacing -> None] - = -Graphics- - - The doughnut chart above with labels on each of the 3 pieces: - >> PieChart[{{10, 20, 30}, {15, 22, 30}}, ChartLabels -> {A, B, C}] - = -Graphics- - - Negative values are removed, the data below is the same as {1, 3}: - >> PieChart[{1, -1, 3}] - = -Graphics- - """ - - never_monochrome = True - options = _Chart.options.copy() - options.update( - { - "Axes": "{False, False}", - "AspectRatio": "1", - "SectorOrigin": "{Automatic, 0}", - "SectorSpacing": "Automatic", - } - ) - - summary_text = "draw a pie chart" - - def _draw(self, data, color, evaluation, options): - data = [[max(0.0, x) for x in group] for group in data] - - sector_origin = self.get_option(options, "SectorOrigin", evaluation) - if not sector_origin.has_form("List", 2): - return - sector_origin = eval_N(sector_origin, evaluation) - - orientation = sector_origin.elements[0] - if ( - isinstance(orientation, Symbol) - and orientation.get_name() == "System`Automatic" - ): - sector_phi = pi - sector_sign = -1.0 - elif orientation.has_form("List", 2) and isinstance( - orientation.elements[1], String - ): - sector_phi = orientation.elements[0].round_to_float() - clock_name = orientation.elements[1].get_string_value() - if clock_name == "Clockwise": - sector_sign = -1.0 - elif clock_name == "Counterclockwise": - sector_sign = 1.0 - else: - return - else: - return - - sector_spacing = self.get_option(options, "SectorSpacing", evaluation) - if isinstance(sector_spacing, Symbol): - if sector_spacing.get_name() == "System`Automatic": - sector_spacing = ListExpression(Integer0, Real(0.2)) - elif sector_spacing.get_name() == "System`None": - sector_spacing = ListExpression(Integer0, Integer0) - else: - return - if not sector_spacing.has_form("List", 2): - return - segment_spacing = 0.0 # not yet implemented; needs real arc graphics - radius_spacing = max(0.0, min(1.0, sector_spacing.elements[1].round_to_float())) - - def vector2(x, y) -> ListExpression: - return ListExpression(Real(x), Real(y)) - - def radii(): - outer = 2.0 - inner = sector_origin.elements[1].round_to_float() - n = len(data) - - d = (outer - inner) / n + v1 = _apply_fn(x1, y1) + v2 = _apply_fn(x2, y2) + v3 = _apply_fn(x3, y3) + v4 = _apply_fn(x4, y4) - r0 = outer - for i in range(n): - r1 = r0 - d - if i > 0: - r0 -= radius_spacing * d - yield (r0, r1) - r0 = r1 + if v1 is None or v4 is None: + triangle(x1, y1, x2, y2, x3, y3) + triangle(x4, y4, x3, y3, x2, y2) + elif v2 is None or v3 is None: + triangle(x2, y2, x1, y1, x4, y4) + triangle(x3, y3, x4, y4, x1, y1) + else: + if abs(v3 - v2) > abs(v4 - v1): + triangle(x2, y2, x1, y1, x4, y4) + triangle(x3, y3, x4, y4, x1, y1) + else: + triangle(x1, y1, x2, y2, x3, y3) + triangle(x4, y4, x3, y3, x2, y2) - def phis(values): - s = sum(values) + # adaptive resampling + # TODO: optimise this + # 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() + lent = len(triangles) # number of initial triangles + for i1 in range(lent): + for i2 in range(lent): + # find all edge pairings + if i1 == i2: + continue + t1 = triangles[i1] + t2 = triangles[i2] - t = 0.0 - pi2 = pi * 2.0 - phi0 = pi - spacing = sector_sign * segment_spacing / 2.0 + edge_pairing = ( + (t1[0], t1[1]) == (t2[0], t2[1]) + or (t1[0], t1[1]) == (t2[1], t2[2]) + or (t1[0], t1[1]) == (t2[0], t2[2]) + or (t1[1], t1[2]) == (t2[0], t2[1]) + or (t1[1], t1[2]) == (t2[1], t2[2]) + or (t1[1], t1[2]) == (t2[0], t2[2]) + or (t1[0], t1[2]) == (t2[0], t2[1]) + or (t1[0], t1[2]) == (t2[1], t2[2]) + or (t1[0], t1[2]) == (t2[0], t2[2]) + ) + if not edge_pairing: + continue + v1 = [t1[1][i] - t1[0][i] for i in range(3)] + w1 = [t1[2][i] - t1[0][i] for i in range(3)] + v2 = [t2[1][i] - t2[0][i] for i in range(3)] + w2 = [t2[2][i] - t2[0][i] for i in range(3)] + n1 = ( # surface normal for t1 + (v1[1] * w1[2]) - (v1[2] * w1[1]), + (v1[2] * w1[0]) - (v1[0] * w1[2]), + (v1[0] * w1[1]) - (v1[1] * w1[0]), + ) + n2 = ( # surface normal for t2 + (v2[1] * w2[2]) - (v2[2] * w2[1]), + (v2[2] * w2[0]) - (v2[0] * w2[2]), + (v2[0] * w2[1]) - (v2[1] * w2[0]), + ) + try: + angle = ( + n1[0] * n2[0] + n1[1] * n2[1] + n1[2] * n2[2] + ) / sqrt( + (n1[0] ** 2 + n1[1] ** 2 + n1[2] ** 2) + * (n2[0] ** 2 + n2[1] ** 2 + n2[2] ** 2) + ) + except ZeroDivisionError: + angle = 0.0 + if abs(angle) < ang_thresh: + for i, t in ((i1, t1), (i2, t2)): + # subdivide + x1, y1 = t[0][0], t[0][1] + x2, y2 = t[1][0], t[1][1] + x3, y3 = t[2][0], t[2][1] + x4, y4 = 0.5 * (x1 + x2), 0.5 * (y1 + y2) + x5, y5 = 0.5 * (x2 + x3), 0.5 * (y2 + y3) + x6, y6 = 0.5 * (x1 + x3), 0.5 * (y1 + y3) + needs_removal.add(i) + split_edges.add( + ((x1, y1), (x2, y2)) + if (x2, y2) > (x1, y1) + else ((x2, y2), (x1, y1)) + ) + split_edges.add( + ((x2, y2), (x3, y3)) + if (x3, y3) > (x2, y2) + else ((x3, y3), (x2, y2)) + ) + split_edges.add( + ((x1, y1), (x3, y3)) + if (x3, y3) > (x1, y1) + else ((x3, y3), (x1, y1)) + ) + triangle(x1, y1, x4, y4, x6, y6, depth=depth) + triangle(x2, y2, x4, y4, x5, y5, depth=depth) + triangle(x3, y3, x5, y5, x6, y6, depth=depth) + triangle(x4, y4, x5, y5, x6, y6, depth=depth) + # remove subdivided triangles which have been divided + triangles = [ + t for i, t in enumerate(triangles) if i not in needs_removal + ] - for k, value in enumerate(values): - t += value - phi1 = sector_phi + sector_sign * (t / s) * pi2 + # fix up subdivided edges + # + # look at every triangle and see if its edges need updating. + # depending on how many edges require subdivision we proceede with + # one of two subdivision strategies + # + # TODO possible optimisation: don't look at every triangle again + made_changes = True + while made_changes: + made_changes = False + new_triangles = [] + for i, t in enumerate(triangles): + new_points = [] + if ((t[0][0], t[0][1]), (t[1][0], t[1][1])) in split_edges: + new_points.append([0, 1]) + if ((t[1][0], t[1][1]), (t[2][0], t[2][1])) in split_edges: + new_points.append([1, 2]) + if ((t[0][0], t[0][1]), (t[2][0], t[2][1])) in split_edges: + new_points.append([0, 2]) - yield (phi0 + spacing, phi1 - spacing) - phi0 = phi1 + if len(new_points) == 0: + continue + made_changes = True + # 'triforce' subdivision + # 1 + # /\ + # 4 /__\ 6 + # /\ /\ + # /__\/__\ + # 2 5 3 + # if less than three edges require subdivision bisect them + # anyway but fake their values by averaging + x4 = 0.5 * (t[0][0] + t[1][0]) + y4 = 0.5 * (t[0][1] + t[1][1]) + v4 = stored.get((x4, y4), 0.5 * (t[0][2] + t[1][2])) - def segments(): - yield Expression(SymbolEdgeForm, Symbol("Black")) + x5 = 0.5 * (t[1][0] + t[2][0]) + y5 = 0.5 * (t[1][1] + t[2][1]) + v5 = stored.get((x5, y5), 0.5 * (t[1][2] + t[2][2])) - origin = vector2(0.0, 0.0) + x6 = 0.5 * (t[0][0] + t[2][0]) + y6 = 0.5 * (t[0][1] + t[2][1]) + v6 = stored.get((x6, y6), 0.5 * (t[0][2] + t[2][2])) - for values, (r0, r1) in zip(data, radii()): - radius = vector2(r0, r0) + if not (v4 is None or v6 is None): + new_triangles.append(sorted((t[0], (x4, y4, v4), (x6, y6, v6)))) + if not (v4 is None or v5 is None): + new_triangles.append(sorted((t[1], (x4, y4, v4), (x5, y5, v5)))) + if not (v5 is None or v6 is None): + new_triangles.append(sorted((t[2], (x5, y5, v5), (x6, y6, v6)))) + if not (v4 is None or v5 is None or v6 is None): + new_triangles.append( + sorted(((x4, y4, v4), (x5, y5, v5), (x6, y6, v6))) + ) + triangles[i] = None - n = len(values) + triangles.extend(new_triangles) + triangles = [t for t in triangles if t is not None] - for k, (phi0, phi1) in enumerate(phis(values)): - yield Expression( - SymbolStyle, - Expression(SymbolDisk, origin, radius, vector2(phi0, phi1)), - color(k + 1, n), - ) + # add the mesh + mesh_points = [] + if mesh == "System`Full": + for xi in range(plotpoints[0] + 1): + xval = xstart + xi / numx * (xstop - xstart) + mesh_row = [] + for yi in range(plotpoints[1] + 1): + yval = ystart + yi / numy * (ystop - ystart) + z = stored[(xval, yval)] + mesh_row.append((xval, yval, z)) + mesh_points.append(mesh_row) - if r1 > 0.0: - yield Expression( - SymbolStyle, - Expression(SymbolDisk, origin, vector2(r1, r1)), - Symbol("White"), - ) + for yi in range(plotpoints[1] + 1): + yval = ystart + yi / numy * (ystop - ystart) + mesh_col = [] + for xi in range(plotpoints[0] + 1): + xval = xstart + xi / numx * (xstop - xstart) + z = stored[(xval, yval)] + mesh_col.append((xval, yval, z)) + mesh_points.append(mesh_col) - def labels(names): - yield Expression(SymbolFaceForm, Symbol("Black")) + # handle edge subdivisions + made_changes = True + while made_changes: + made_changes = False + for mesh_line in mesh_points: + i = 0 + while i < len(mesh_line) - 1: + x1, y1, v1 = mesh_line[i] + x2, y2, v2 = mesh_line[i + 1] + key = ( + ((x1, y1), (x2, y2)) + if (x2, y2) > (x1, y1) + else ((x2, y2), (x1, y1)) + ) + if key in split_edges: + x3 = 0.5 * (x1 + x2) + y3 = 0.5 * (y1 + y2) + v3 = stored[(x3, y3)] + mesh_line.insert(i + 1, (x3, y3, v3)) + made_changes = True + i += 1 + i += 1 - for values, (r0, r1) in zip(data, radii()): - for name, (phi0, phi1) in zip(names, phis(values)): - r = (r0 + r1) / 2.0 - phi = (phi0 + phi1) / 2.0 - yield Expression( - SymbolText, name, vector2(r * cos(phi), r * sin(phi)) + # handle missing regions + old_meshpoints, mesh_points = mesh_points, [] + for mesh_line in old_meshpoints: + mesh_points.extend( + [ + sorted(g) + for k, g in itertools.groupby( + mesh_line, lambda x: x[2] is None + ) + ] ) + mesh_points = [ + mesh_line + for mesh_line in mesh_points + if not any(x[2] is None for x in mesh_line) + ] + elif mesh == "System`All": + 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])) + mesh_points.add((t[0], t[2]) if t[2] > t[0] else (t[2], t[0])) + mesh_points = list(mesh_points) - graphics = list(segments()) + # find the max and min height + v_min = v_max = None + for t in triangles: + for tx, ty, v in t: + if v_min is None or v < v_min: + v_min = v + if v_max is None or v > v_max: + v_max = v + graphics.extend( + self.construct_graphics( + triangles, mesh_points, v_min, v_max, options, evaluation + ) + ) + return self.final_graphics(graphics, options) - chart_labels = self.get_option(options, "ChartLabels", evaluation) - if chart_labels.get_head_name() == "System`List": - graphics.extend(list(labels(chart_labels.elements))) - options["System`PlotRange"] = ListExpression( - vector2(-2.0, 2.0), vector2(-2.0, 2.0) - ) +class _PredefinedGradient(_GradientColorScheme): + def __init__(self, colors): + self._colors = colors - return Expression( - SymbolGraphics, ListExpression(*graphics), *options_to_rules(options) - ) + def colors(self): + return self._colors class BarChart(_Chart): """ + :WMA link: https://reference.wolfram.com/language/ref/BarChart.html
'BarChart[{$b1$, $b2$ ...}]'
makes a bar chart with lengths $b1$, $b2$, .... @@ -885,32 +1228,303 @@ def labels(names): graphics = list(rectangles()) + list(axes()) - x_range = "System`All" - y_range = "System`All" + x_range = "System`All" + y_range = "System`All" + + x_range = list(get_plot_range(x_coords, x_coords, x_range)) + y_range = list(get_plot_range(y_coords, y_coords, y_range)) + + chart_labels = self.get_option(options, "ChartLabels", evaluation) + if chart_labels.get_head_name() == "System`List": + graphics.extend(list(labels(chart_labels.elements))) + y_range[0] = -0.4 # room for labels at the bottom + + # always specify -.1 as the minimum x plot range, as this will make the y axis apppear + # at origin (0,0); otherwise it will be shifted right; see GraphicsBox.axis_ticks(). + x_range[0] = -0.1 + + options["System`PlotRange"] = ListExpression( + vector2(*x_range), vector2(*y_range) + ) + + return Expression( + SymbolGraphics, ListExpression(*graphics), *options_to_rules(options) + ) + + +class DensityPlot(_Plot3D): + """ + :WMA link: https://reference.wolfram.com/language/ref/DensityPlot.html +
+
'DensityPlot[$f$, {$x$, $xmin$, $xmax$}, {$y$, $ymin$, $ymax$}]' +
plots a density plot of $f$ with $x$ ranging from $xmin$ to $xmax$ and $y$ ranging from $ymin$ to $ymax$. +
+ + >> DensityPlot[x ^ 2 + 1 / y, {x, -1, 1}, {y, 1, 4}] + = -Graphics- + + >> DensityPlot[1 / x, {x, 0, 1}, {y, 0, 1}] + = -Graphics- + + >> DensityPlot[Sqrt[x * y], {x, -1, 1}, {y, -1, 1}] + = -Graphics- + + >> DensityPlot[1/(x^2 + y^2 + 1), {x, -1, 1}, {y, -2,2}, Mesh->Full] + = -Graphics- + + >> DensityPlot[x^2 y, {x, -1, 1}, {y, -1, 1}, Mesh->All] + = -Graphics- + """ + + attributes = A_HOLD_ALL | A_PROTECTED + + options = Graphics.options.copy() + options.update( + { + "Axes": "False", + "AspectRatio": "1", + "Mesh": "None", + "Frame": "True", + "ColorFunction": "Automatic", + "ColorFunctionScaling": "True", + "PlotPoints": "None", + "MaxRecursion": "0", + # 'MaxRecursion': '2', # FIXME causes bugs in svg output see #303 + } + ) + summary_text = "density plot for a function" + + def get_functions_param(self, functions): + return [functions] + + def construct_graphics( + self, triangles, mesh_points, v_min, v_max, options, evaluation + ): + color_function = self.get_option(options, "ColorFunction", evaluation, pop=True) + color_function_scaling = self.get_option( + options, "ColorFunctionScaling", evaluation, pop=True + ) + + color_function_min = color_function_max = None + if color_function.get_name() == "System`Automatic": + color_function = String("LakeColors") + if color_function.get_string_value(): + func = Expression( + SymbolColorData, String(color_function.get_string_value()) + ).evaluate(evaluation) + if func.has_form("ColorDataFunction", 4): + color_function_min = func.elements[2].elements[0].round_to_float() + color_function_max = func.elements[2].elements[1].round_to_float() + color_function = Expression( + SymbolFunction, + Expression(func.elements[3], Expression(SymbolSlot, Integer1)), + ) + else: + evaluation.message("DensityPlot", "color", func) + return + if color_function.has_form("ColorDataFunction", 4): + color_function_min = color_function.elements[2].elements[0].round_to_float() + color_function_max = color_function.elements[2].elements[1].round_to_float() + + color_function_scaling = color_function_scaling is SymbolTrue + v_range = v_max - v_min + + if v_range == 0: + v_range = 1 + + if color_function.has_form("ColorDataFunction", 4): + color_func = color_function.elements[3] + else: + color_func = color_function + if ( + color_function_scaling + and color_function_min is not None # noqa + and color_function_max is not None + ): + color_function_range = color_function_max - color_function_min + + colors = {} + + def eval_color(x, y, v): + v_scaled = (v - v_min) / v_range + if ( + color_function_scaling + and color_function_min is not None # noqa + and color_function_max is not None + ): + v_color_scaled = color_function_min + v_scaled * color_function_range + else: + v_color_scaled = v + + # Calculate and store 100 different shades max. + v_lookup = int(v_scaled * 100 + 0.5) + + value = colors.get(v_lookup) + if value is None: + value = Expression(color_func, Real(v_color_scaled)) + value = value.evaluate(evaluation) + colors[v_lookup] = value + return value + + points = [] + vertex_colors = [] + graphics = [] + for p in triangles: + points.append(ListExpression(*(to_mathics_list(*x[:2]) for x in p))) + vertex_colors.append(ListExpression(*(eval_color(*x) for x in p))) + + graphics.append( + Expression( + SymbolPolygon, + ListExpression(*points), + Expression( + SymbolRule, + Symbol("VertexColors"), + ListExpression(*vertex_colors), + ), + ) + ) + + # add mesh + for xi in range(len(mesh_points)): + line = [] + for yi in range(len(mesh_points[xi])): + line.append( + to_mathics_list(mesh_points[xi][yi][0], mesh_points[xi][yi][1]) + ) + graphics.append(Expression(SymbolLine, ListExpression(*line))) + + return graphics + + def final_graphics(self, graphics, options): + return Expression( + SymbolGraphics, + ListExpression(*graphics), + *options_to_rules(options, Graphics.options) + ) + + +class ColorData(Builtin): + """ + :WMA link: https://reference.wolfram.com/language/ref/ColorData.html +
+
'ColorData["$name$"]' +
returns a color function with the given $name$. +
+ + Define a user-defined color function: + >> Unprotect[ColorData]; ColorData["test"] := ColorDataFunction["test", "Gradients", {0, 1}, Blend[{Red, Green, Blue}, #1] &]; Protect[ColorData] + + Compare it to the default color function, 'LakeColors': + >> {DensityPlot[x + y, {x, -1, 1}, {y, -1, 1}], DensityPlot[x + y, {x, -1, 1}, {y, -1, 1}, ColorFunction->"test"]} + = {-Graphics-, -Graphics-} + """ + + # rules = { + # 'ColorData["LakeColors"]': ( + # """ColorDataFunction["LakeColors", "Gradients", {0, 1}, + # Blend[{RGBColor[0.293416, 0.0574044, 0.529412], + # RGBColor[0.563821, 0.527565, 0.909499], + # RGBColor[0.762631, 0.846998, 0.914031], + # RGBColor[0.941176, 0.906538, 0.834043]}, #1] &]"""), + # } + summary_text = "named color gradients and collections" + messages = { + "notent": "`1` is not a known color scheme. ColorData[] gives you lists of schemes.", + } + + palettes = { + "LakeColors": _PredefinedGradient( + [ + (0.293416, 0.0574044, 0.529412), + (0.563821, 0.527565, 0.909499), + (0.762631, 0.846998, 0.914031), + (0.941176, 0.906538, 0.834043), + ] + ), + "Pastel": _PalettableGradient( + palettable.colorbrewer.qualitative.Pastel1_9, False + ), + "Rainbow": _PalettableGradient( + palettable.colorbrewer.diverging.Spectral_9, True + ), + "RedBlueTones": _PalettableGradient( + palettable.colorbrewer.diverging.RdBu_11, False + ), + "GreenPinkTones": _PalettableGradient( + palettable.colorbrewer.diverging.PiYG_9, False + ), + "GrayTones": _PalettableGradient( + palettable.colorbrewer.sequential.Greys_9, False + ), + "SolarColors": _PalettableGradient( + palettable.colorbrewer.sequential.OrRd_9, True + ), + "CherryTones": _PalettableGradient( + palettable.colorbrewer.sequential.Reds_9, True + ), + "FuchsiaTones": _PalettableGradient( + palettable.colorbrewer.sequential.RdPu_9, True + ), + "SiennaTones": _PalettableGradient( + palettable.colorbrewer.sequential.Oranges_9, True + ), + # specific to Mathics + "Paired": _PalettableGradient( + palettable.colorbrewer.qualitative.Paired_9, False + ), + "Accent": _PalettableGradient( + palettable.colorbrewer.qualitative.Accent_8, False + ), + "Aquatic": _PalettableGradient(palettable.wesanderson.Aquatic1_5, False), + "Zissou": _PalettableGradient(palettable.wesanderson.Zissou_5, False), + "Tableau": _PalettableGradient(palettable.tableau.Tableau_20, False), + "TrafficLight": _PalettableGradient(palettable.tableau.TrafficLight_9, False), + "Moonrise1": _PalettableGradient(palettable.wesanderson.Moonrise1_5, False), + } + + def eval_directory(self, evaluation): + "ColorData[]" + return ListExpression(String("Gradients")) - x_range = list(get_plot_range(x_coords, x_coords, x_range)) - y_range = list(get_plot_range(y_coords, y_coords, y_range)) + def eval(self, name, evaluation): + "ColorData[name_String]" + py_name = name.get_string_value() + if py_name == "Gradients": + return ListExpression(*[String(name) for name in self.palettes.keys()]) + palette = ColorData.palettes.get(py_name, None) + if palette is None: + evaluation.message("ColorData", "notent", name) + return + return palette.color_data_function(py_name) - chart_labels = self.get_option(options, "ChartLabels", evaluation) - if chart_labels.get_head_name() == "System`List": - graphics.extend(list(labels(chart_labels.elements))) - y_range[0] = -0.4 # room for labels at the bottom + @staticmethod + def colors(name, evaluation): + palette = ColorData.palettes.get(name, None) + if palette is None: + evaluation.message("ColorData", "notent", name) + return None + return palette.colors() - # always specify -.1 as the minimum x plot range, as this will make the y axis apppear - # at origin (0,0); otherwise it will be shifted right; see GraphicsBox.axis_ticks(). - x_range[0] = -0.1 - options["System`PlotRange"] = ListExpression( - vector2(*x_range), vector2(*y_range) - ) +class ColorDataFunction(Builtin): + """ + :WMA link: https://reference.wolfram.com/language/ref/ColorDataFunction.html +
+
'ColorDataFunction[range, ...]' +
is a function that represents a color scheme. +
+ """ - return Expression( - SymbolGraphics, ListExpression(*graphics), *options_to_rules(options) - ) + summary_text = "color scheme object" + pass class Histogram(Builtin): """ + :Histogram: https://en.wikipedia.org/wiki/Histogram \ + (:WMA link: https://reference.wolfram.com/language/ref/ColorDataFunction.html) +
'Histogram[{$x1$, $x2$ ...}]'
plots a histogram using the values $x1$, $x2$, .... @@ -1066,300 +1680,96 @@ def best_distributions(n_bins, dir, cost0, distributions0): while True: new_n_bins = n_bins + dir * step_size if new_n_bins < 1 or new_n_bins > max_bins: - good = False - else: - cost, distributions = compute_cost(new_n_bins) - good = cost < cost0 - - if not good: - if step_size == 1: - break - step_size = max(step_size // 2, 1) - else: - n_bins = new_n_bins - cost0 = cost - distributions0 = distributions - - return cost0, distributions0 - - def graphics(distributions): - palette = palettable.wesanderson.FantasticFox1_5 - colors = list(reversed(palette.mpl_colors)) - - from itertools import chain - - n_bins = distributions[0].n_bins() - x_coords = [minimum + (i * span) / n_bins for i in range(n_bins + 1)] - y_coords = [0] + list( - chain(*[distribution.bins for distribution in distributions]) - ) - - graphics = [] - for i, distribution in enumerate(distributions): - color = colors[i % len(colors)] - graphics.extend(list(chain(*[distribution.graphics(color)]))) - - x_range = "System`All" - y_range = "System`All" - - x_range = list(get_plot_range(x_coords, x_coords, x_range)) - y_range = list(get_plot_range(y_coords, y_coords, y_range)) - - # always specify -.1 as the minimum x plot range, as this will make the y axis apppear - # at origin (0,0); otherwise it will be shifted right; see GraphicsBox.axis_ticks(). - x_range[0] = -0.1 - - options["System`PlotRange"] = from_python([x_range, y_range]) - - return Expression( - SymbolGraphics, ListExpression(*graphics), *options_to_rules(options) - ) - - def manual_bins(bspec, hspec): - if isinstance(bspec, Integer): - distributions = [ - Distribution(data, bspec.get_int_value()) for data in matrix - ] - return graphics(distributions) - elif bspec.get_head_name() == "System`List" and len(bspec.elements) == 1: - bin_width = bspec[0].to_mpmath() - distributions = [ - Distribution(data, int(mpceil(span / bin_width))) for data in matrix - ] - return graphics(distributions) - - def auto_bins(): - # start with Rice's rule, see https://en.wikipedia.org/wiki/Histogram - n_bins = int(ceil(2 * (max_bins ** (1.0 / 3.0)))) - - # now optimize the bin size by going into both directions and looking - # for local minima. - - cost0, distributions0 = compute_cost(n_bins) - cost_r, distributions_r = best_distributions( - n_bins, 1, cost0, distributions0 - ) - cost_l, distributions_l = best_distributions( - n_bins, -1, cost0, distributions0 - ) - - if cost_r < cost_l: - distributions = distributions_r - else: - distributions = distributions_l - - return graphics(distributions) - - if not spec: - return auto_bins() - else: - if len(spec) < 2: - spec.append(None) - return manual_bins(*spec) - return Expression( - SymbolGraphics, - ListExpression(*graphics), - *options_to_rules(options, Graphics.options) - ) - - -class _ListPlot(Builtin): - """ - Computation part of ListPlot, ListLinePlot, and DiscretePlot - 2-Dimensional plot a list of points in some fashion. - """ - - messages = { - "prng": ( - "Value of option PlotRange -> `1` is not All, Automatic or " - "an appropriate list of range specifications." - ), - "joind": "Value of option Joined -> `1` is not True or False.", - } - - def eval(self, points, evaluation, options): - "%(name)s[points_, OptionsPattern[%(name)s]]" - - plot_name = self.get_name() - all_points = eval_N(points, evaluation).to_python() - # FIXME: arrange for self to have a .symbolname property or attribute - expr = Expression(Symbol(self.get_name()), points, *options_to_rules(options)) - - plotrange_option = self.get_option(options, "PlotRange", evaluation) - plotrange = eval_N(plotrange_option, evaluation).to_python() - if plotrange == "System`All": - plotrange = ["System`All", "System`All"] - elif plotrange == "System`Automatic": - plotrange = ["System`Automatic", "System`Automatic"] - elif isinstance(plotrange, numbers.Real): - plotrange = [[-plotrange, plotrange], [-plotrange, plotrange]] - elif isinstance(plotrange, list) and len(plotrange) == 2: - if all(isinstance(pr, numbers.Real) for pr in plotrange): - plotrange = ["System`All", plotrange] - elif all(check_plot_range(pr, numbers.Real) for pr in plotrange): - pass - else: - evaluation.message(self.get_name(), "prng", plotrange_option) - plotrange = ["System`Automatic", "System`Automatic"] - - x_range, y_range = plotrange[0], plotrange[1] - assert x_range in ("System`Automatic", "System`All") or isinstance( - x_range, list - ) - assert y_range in ("System`Automatic", "System`All") or isinstance( - y_range, list - ) - - # Filling option - # TODO: Fill between corresponding points in two datasets: - filling_option = self.get_option(options, "Filling", evaluation) - filling = eval_N(filling_option, evaluation).to_python() - if filling in [ - "System`Top", - "System`Bottom", - "System`Axis", - ] or isinstance( # noqa - filling, numbers.Real - ): - pass - else: - # Mathematica does not even check that filling is sane - filling = None - - # Joined Option - joined_option = self.get_option(options, "Joined", evaluation) - joined = joined_option.to_python() - if joined not in [True, False]: - evaluation.message(plot_name, "joind", joined_option, expr) - joined = False - - if isinstance(all_points, list) and len(all_points) != 0: - if all(not isinstance(point, list) for point in all_points): - # Only y values given - all_points = [ - [[float(i + 1), all_points[i]] for i in range(len(all_points))] - ] - elif all(isinstance(line, list) and len(line) == 2 for line in all_points): - # Single list of (x,y) pairs - all_points = [all_points] - elif all(isinstance(line, list) for line in all_points): - # List of lines - if all( - isinstance(point, list) and len(point) == 2 - for line in all_points - for point in line - ): - pass - elif all( - not isinstance(point, list) for line in all_points for point in line - ): - all_points = [ - [[float(i + 1), l] for i, l in enumerate(line)] - for line in all_points - ] - else: - return - else: - return - else: - return + good = False + else: + cost, distributions = compute_cost(new_n_bins) + good = cost < cost0 - # Split into segments at missing data - all_points = [[line] for line in all_points] - for lidx, line in enumerate(all_points): - i = 0 - while i < len(all_points[lidx]): - seg = line[i] - for j, point in enumerate(seg): - if not ( - isinstance(point[0], (int, float)) - and isinstance(point[1], (int, float)) - ): - all_points[lidx].insert(i, seg[:j]) - all_points[lidx][i + 1] = seg[j + 1 :] - i -= 1 + if not good: + if step_size == 1: break + step_size = max(step_size // 2, 1) + else: + n_bins = new_n_bins + cost0 = cost + distributions0 = distributions - i += 1 + return cost0, distributions0 - y_range = get_plot_range( - [y for line in all_points for seg in line for x, y in seg], - [y for line in all_points for seg in line for x, y in seg], - y_range, - ) - x_range = get_plot_range( - [x for line in all_points for seg in line for x, y in seg], - [x for line in all_points for seg in line for x, y in seg], - x_range, - ) + def graphics(distributions): + palette = palettable.wesanderson.FantasticFox1_5 + colors = list(reversed(palette.mpl_colors)) - if filling == "System`Axis": - # TODO: Handle arbitary axis intercepts - filling = 0.0 - elif filling == "System`Bottom": - filling = y_range[0] - elif filling == "System`Top": - filling = y_range[1] + from itertools import chain - hue = 0.67 - hue_pos = 0.236068 - hue_neg = -0.763932 + n_bins = distributions[0].n_bins() + x_coords = [minimum + (i * span) / n_bins for i in range(n_bins + 1)] + y_coords = [0] + list( + chain(*[distribution.bins for distribution in distributions]) + ) - graphics = [] - for indx, line in enumerate(all_points): - graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) - for segment in line: - mathics_segment = from_python(segment) - if joined: - graphics.append(Expression(SymbolLine, mathics_segment)) - if filling is not None: - graphics.append( - Expression( - SymbolHue, Real(hue), RealPoint6, RealPoint6, RealPoint2 - ) - ) - fill_area = list(segment) - fill_area.append([segment[-1][0], filling]) - fill_area.append([segment[0][0], filling]) - graphics.append( - Expression(SymbolPolygon, from_python(fill_area)) - ) - elif discrete_plot: - graphics.append(Expression(SymbolPoint, mathics_segment)) - for mathics_point in mathics_segment: - graphics.append( - Expression( - SymbolLine, - ListExpression( - ListExpression(mathics_point[0], Integer0), - mathics_point, - ), - ) - ) - else: - graphics.append(Expression(SymbolPoint, from_python(segment))) - if filling is not None: - for point in segment: - graphics.append( - Expression( - SymbolLine, - from_python( - [[point[0], filling], [point[0], point[1]]] - ), - ) - ) + graphics = [] + for i, distribution in enumerate(distributions): + color = colors[i % len(colors)] + graphics.extend(list(chain(*[distribution.graphics(color)]))) - if indx % 4 == 0: - hue += hue_pos + x_range = "System`All" + y_range = "System`All" + + x_range = list(get_plot_range(x_coords, x_coords, x_range)) + y_range = list(get_plot_range(y_coords, y_coords, y_range)) + + # always specify -.1 as the minimum x plot range, as this will make the y axis apppear + # at origin (0,0); otherwise it will be shifted right; see GraphicsBox.axis_ticks(). + x_range[0] = -0.1 + + options["System`PlotRange"] = from_python([x_range, y_range]) + + return Expression( + SymbolGraphics, ListExpression(*graphics), *options_to_rules(options) + ) + + def manual_bins(bspec, hspec): + if isinstance(bspec, Integer): + distributions = [ + Distribution(data, bspec.get_int_value()) for data in matrix + ] + return graphics(distributions) + elif bspec.get_head_name() == "System`List" and len(bspec.elements) == 1: + bin_width = bspec[0].to_mpmath() + distributions = [ + Distribution(data, int(mpceil(span / bin_width))) for data in matrix + ] + return graphics(distributions) + + def auto_bins(): + # start with Rice's rule, see https://en.wikipedia.org/wiki/Histogram + n_bins = int(ceil(2 * (max_bins ** (1.0 / 3.0)))) + + # now optimize the bin size by going into both directions and looking + # for local minima. + + cost0, distributions0 = compute_cost(n_bins) + cost_r, distributions_r = best_distributions( + n_bins, 1, cost0, distributions0 + ) + cost_l, distributions_l = best_distributions( + n_bins, -1, cost0, distributions0 + ) + + if cost_r < cost_l: + distributions = distributions_r else: - hue += hue_neg - if hue > 1: - hue -= 1 - if hue < 0: - hue += 1 + distributions = distributions_l - options["System`PlotRange"] = from_python([x_range, y_range]) + return graphics(distributions) + if not spec: + return auto_bins() + else: + if len(spec) < 2: + spec.append(None) + return manual_bins(*spec) return Expression( SymbolGraphics, ListExpression(*graphics), @@ -1367,460 +1777,277 @@ def eval(self, points, evaluation, options): ) -class _Plot3D(Builtin): - messages = { - "invmaxrec": ( - "MaxRecursion must be a non-negative integer; the recursion value " - "is limited to `2`. Using MaxRecursion -> `1`." - ), - "prng": ( - "Value of option PlotRange -> `1` is not All, Automatic or " - "an appropriate list of range specifications." - ), - "invmesh": "Mesh must be one of {None, Full, All}. Using Mesh->None.", - "invpltpts": ( - "Value of PlotPoints -> `1` is not a positive integer " - "or appropriate list of positive integers." - ), - } +class ListPlot(_ListPlot): + """ + :WMA link: https://reference.wolfram.com/language/ref/ListPlot.html +
+
'ListPlot[{$y_1$, $y_2$, ...}]' +
plots a list of y-values, assuming integer x-values 1, 2, 3, ... - def eval(self, functions, x, xstart, xstop, y, ystart, ystop, evaluation, options): - """%(name)s[functions_, {x_Symbol, xstart_, xstop_}, - {y_Symbol, ystart_, ystop_}, OptionsPattern[%(name)s]]""" - xexpr_limits = ListExpression(x, xstart, xstop) - yexpr_limits = ListExpression(y, ystart, ystop) - expr = Expression( - Symbol(self.get_name()), - functions, - xexpr_limits, - yexpr_limits, - *options_to_rules(options) - ) +
'ListPlot[{{$x_1$, $y_1$}, {$x_2$, $y_2$}, ...}]' +
plots a list of $x$, $y$ pairs. - functions = self.get_functions_param(functions) - plot_name = self.get_name() +
'ListPlot[{$list_1$, $list_2$, ...}]' +
plots several lists of points. +
- def convert_limit(value, limits): - result = value.round_to_float(evaluation) - if result is None: - evaluation.message(plot_name, "plln", value, limits) - return result + ListPlot accepts a superset of the Graphics options. - xstart = convert_limit(xstart, xexpr_limits) - xstop = convert_limit(xstop, xexpr_limits) - ystart = convert_limit(ystart, yexpr_limits) - ystop = convert_limit(ystop, yexpr_limits) - if None in (xstart, xstop, ystart, ystop): - return + >> ListPlot[Table[n ^ 2, {n, 10}]] + = -Graphics- + """ - if ystart >= ystop: - evaluation.message(plot_name, "plln", ystop, expr) - return + attributes = A_HOLD_ALL | A_PROTECTED + + options = Graphics.options.copy() + options.update( + { + "Axes": "True", + "AspectRatio": "1 / GoldenRatio", + "Mesh": "None", + "PlotRange": "Automatic", + "PlotPoints": "None", + "Filling": "None", + "Joined": "False", + "Discrete": "False", + } + ) + summary_text = "plot lists of points" + + +class ListLinePlot(_ListPlot): + """ + :WMA link: https://reference.wolfram.com/language/ref/ListLinePlot.html +
+
'ListLinePlot[{$y_1$, $y_2$, ...}]' +
plots a line through a list of $y$-values, assuming integer $x$-values 1, 2, 3, ... + +
'ListLinePlot[{{$x_1$, $y_1$}, {$x_2$, $y_2$}, ...}]' +
plots a line through a list of $x$, $y$ pairs. + +
'ListLinePlot[{$list_1$, $list_2$, ...}]' +
plots several lines. +
+ + ListPlot accepts a superset of the Graphics options. + + >> ListLinePlot[Table[{n, n ^ 0.5}, {n, 10}]] + = -Graphics- + + >> ListLinePlot[{{-2, -1}, {-1, -1}}] + = -Graphics- + """ + + attributes = A_HOLD_ALL | A_PROTECTED - if xstart >= xstop: - evaluation.message(plot_name, "plln", xstop, expr) - return + options = Graphics.options.copy() + options.update( + { + "Axes": "True", + "AspectRatio": "1 / GoldenRatio", + "Mesh": "None", + "PlotRange": "Automatic", + "PlotPoints": "None", + "Filling": "None", + "Joined": "True", + } + ) + summary_text = "plot lines through lists of points" - # Mesh Option - mesh_option = self.get_option(options, "Mesh", evaluation) - mesh = mesh_option.to_python() - if mesh not in ["System`None", "System`Full", "System`All"]: - evaluation.message("Mesh", "ilevels", mesh_option) - mesh = "System`Full" - # PlotPoints Option - plotpoints_option = self.get_option(options, "PlotPoints", evaluation) - plotpoints = plotpoints_option.to_python() +class PieChart(_Chart): + """ + :Pie Chart: https://en.wikipedia.org/wiki/Pie_chart \ + (:WMA link: https://reference.wolfram.com/language/ref/PieChart.html) +
+
'PieChart[{$a1$, $a2$ ...}]' +
draws a pie chart with sector angles proportional to $a1$, $a2$, .... +
- def check_plotpoints(steps): - if isinstance(steps, int) and steps > 0: - return True - return False + Drawing options include - + Charting: +
    +
  • Mesh +
  • PlotRange +
  • ChartLabels +
  • ChartLegends +
  • ChartStyle +
- if plotpoints == "System`None": - plotpoints = [7, 7] - elif check_plotpoints(plotpoints): - plotpoints = [plotpoints, plotpoints] + PieChart specific: +
    +
  • Axes (default: False, False) +
  • AspectRatio (default 1) +
  • SectorOrigin: (default {Automatic, 0}) +
  • SectorSpacing" (default Automatic) +
- if not ( - isinstance(plotpoints, list) - and len(plotpoints) == 2 - and check_plotpoints(plotpoints[0]) - and check_plotpoints(plotpoints[1]) - ): - evaluation.message(self.get_name(), "invpltpts", plotpoints) - plotpoints = [7, 7] + A hypothetical comparsion between types of pets owned: + >> PieChart[{30, 20, 10}, ChartLabels -> {Dogs, Cats, Fish}] + = -Graphics- - # MaxRecursion Option - maxrec_option = self.get_option(options, "MaxRecursion", evaluation) - max_depth = maxrec_option.to_python() - if isinstance(max_depth, int): - if max_depth < 0: - max_depth = 0 - evaluation.message(self.get_name(), "invmaxrec", max_depth, 15) - elif max_depth > 15: - max_depth = 15 - evaluation.message(self.get_name(), "invmaxrec", max_depth, 15) - else: - pass # valid - elif max_depth == float("inf"): - max_depth = 15 - evaluation.message(self.get_name(), "invmaxrec", max_depth, 15) - else: - max_depth = 0 - evaluation.message(self.get_name(), "invmaxrec", max_depth, 15) + A doughnut chart for a list of values: + >> PieChart[{8, 16, 2}, SectorOrigin -> {Automatic, 1.5}] + = -Graphics- - # Plot the functions - graphics = [] - for indx, f in enumerate(functions): - stored = {} + A Pie chart with multple datasets: + >> PieChart[{{10, 20, 30}, {15, 22, 30}}] + = -Graphics- - compiled_fn = compile_quiet_function( - f, [x.get_name(), y.get_name()], evaluation, False - ) + Same as the above, but without gaps between the groups of data: + >> PieChart[{{10, 20, 30}, {15, 22, 30}}, SectorSpacing -> None] + = -Graphics- - def _apply_fn(x_value, y_value): - try: - # Try to used cached value first - return stored[(x_value, y_value)] - except KeyError: - value = compiled_fn(x_value, y_value) - if value is not None: - value = float(value) - stored[(x_value, y_value)] = value - return value + The doughnut chart above with labels on each of the 3 pieces: + >> PieChart[{{10, 20, 30}, {15, 22, 30}}, ChartLabels -> {A, B, C}] + = -Graphics- - triangles = [] + Negative values are removed, the data below is the same as {1, 3}: + >> PieChart[{1, -1, 3}] + = -Graphics- + """ - split_edges = set() # subdivided edges + never_monochrome = True + options = _Chart.options.copy() + options.update( + { + "Axes": "{False, False}", + "AspectRatio": "1", + "SectorOrigin": "{Automatic, 0}", + "SectorSpacing": "Automatic", + } + ) - def triangle(x1, y1, x2, y2, x3, y3, depth=0): - v1, v2, v3 = _apply_fn(x1, y1), _apply_fn(x2, y2), _apply_fn(x3, y3) + summary_text = "draw a pie chart" - if (v1 is v2 is v3 is None) and (depth > max_depth // 2): - # fast finish because the entire region is undefined but - # recurse 'a little' to avoid missing well defined regions - return - elif v1 is None or v2 is None or v3 is None: - # 'triforce' pattern recursion to find the edge of defined region - # 1 - # /\ - # 4 /__\ 6 - # /\ /\ - # /__\/__\ - # 2 5 3 - if depth < max_depth: - x4, y4 = 0.5 * (x1 + x2), 0.5 * (y1 + y2) - x5, y5 = 0.5 * (x2 + x3), 0.5 * (y2 + y3) - x6, y6 = 0.5 * (x1 + x3), 0.5 * (y1 + y3) - split_edges.add( - ((x1, y1), (x2, y2)) - if (x2, y2) > (x1, y1) - else ((x2, y2), (x1, y1)) - ) - split_edges.add( - ((x2, y2), (x3, y3)) - if (x3, y3) > (x2, y2) - else ((x3, y3), (x2, y2)) - ) - split_edges.add( - ((x1, y1), (x3, y3)) - if (x3, y3) > (x1, y1) - else ((x3, y3), (x1, y1)) - ) - triangle(x1, y1, x4, y4, x6, y6, depth + 1) - triangle(x4, y4, x2, y2, x5, y5, depth + 1) - triangle(x6, y6, x5, y5, x3, y3, depth + 1) - triangle(x4, y4, x5, y5, x6, y6, depth + 1) - return - triangles.append(sorted(((x1, y1, v1), (x2, y2, v2), (x3, y3, v3)))) + def _draw(self, data, color, evaluation, options): + data = [[max(0.0, x) for x in group] for group in data] - # linear (grid) sampling - numx = plotpoints[0] * 1.0 - numy = plotpoints[1] * 1.0 - for xi in range(plotpoints[0]): - for yi in range(plotpoints[1]): - # Decide which way to break the square grid into triangles - # by looking at diagonal lengths. - # - # 3___4 3___4 - # |\ | | /| - # | \ | versus | / | - # |__\| |/__| - # 1 2 1 2 - # - # Approaching the boundary of the well defined region is - # important too. Use first stategy if 1 or 4 are undefined - # and stategy 2 if either 2 or 3 are undefined. - # - (x1, x2, x3, x4) = ( - xstart + value * (xstop - xstart) - for value in ( - xi / numx, - (xi + 1) / numx, - xi / numx, - (xi + 1) / numx, - ) - ) - (y1, y2, y3, y4) = ( - ystart + value * (ystop - ystart) - for value in ( - yi / numy, - yi / numy, - (yi + 1) / numy, - (yi + 1) / numy, - ) - ) + sector_origin = self.get_option(options, "SectorOrigin", evaluation) + if not sector_origin.has_form("List", 2): + return + sector_origin = eval_N(sector_origin, evaluation) - v1 = _apply_fn(x1, y1) - v2 = _apply_fn(x2, y2) - v3 = _apply_fn(x3, y3) - v4 = _apply_fn(x4, y4) + orientation = sector_origin.elements[0] + if ( + isinstance(orientation, Symbol) + and orientation.get_name() == "System`Automatic" + ): + sector_phi = pi + sector_sign = -1.0 + elif orientation.has_form("List", 2) and isinstance( + orientation.elements[1], String + ): + sector_phi = orientation.elements[0].round_to_float() + clock_name = orientation.elements[1].get_string_value() + if clock_name == "Clockwise": + sector_sign = -1.0 + elif clock_name == "Counterclockwise": + sector_sign = 1.0 + else: + return + else: + return - if v1 is None or v4 is None: - triangle(x1, y1, x2, y2, x3, y3) - triangle(x4, y4, x3, y3, x2, y2) - elif v2 is None or v3 is None: - triangle(x2, y2, x1, y1, x4, y4) - triangle(x3, y3, x4, y4, x1, y1) - else: - if abs(v3 - v2) > abs(v4 - v1): - triangle(x2, y2, x1, y1, x4, y4) - triangle(x3, y3, x4, y4, x1, y1) - else: - triangle(x1, y1, x2, y2, x3, y3) - triangle(x4, y4, x3, y3, x2, y2) + sector_spacing = self.get_option(options, "SectorSpacing", evaluation) + if isinstance(sector_spacing, Symbol): + if sector_spacing.get_name() == "System`Automatic": + sector_spacing = ListExpression(Integer0, Real(0.2)) + elif sector_spacing.get_name() == "System`None": + sector_spacing = ListExpression(Integer0, Integer0) + else: + return + if not sector_spacing.has_form("List", 2): + return + segment_spacing = 0.0 # not yet implemented; needs real arc graphics + radius_spacing = max(0.0, min(1.0, sector_spacing.elements[1].round_to_float())) - # adaptive resampling - # TODO: optimise this - # 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() - lent = len(triangles) # number of initial triangles - for i1 in range(lent): - for i2 in range(lent): - # find all edge pairings - if i1 == i2: - continue - t1 = triangles[i1] - t2 = triangles[i2] + def vector2(x, y) -> ListExpression: + return ListExpression(Real(x), Real(y)) - edge_pairing = ( - (t1[0], t1[1]) == (t2[0], t2[1]) - or (t1[0], t1[1]) == (t2[1], t2[2]) - or (t1[0], t1[1]) == (t2[0], t2[2]) - or (t1[1], t1[2]) == (t2[0], t2[1]) - or (t1[1], t1[2]) == (t2[1], t2[2]) - or (t1[1], t1[2]) == (t2[0], t2[2]) - or (t1[0], t1[2]) == (t2[0], t2[1]) - or (t1[0], t1[2]) == (t2[1], t2[2]) - or (t1[0], t1[2]) == (t2[0], t2[2]) - ) - if not edge_pairing: - continue - v1 = [t1[1][i] - t1[0][i] for i in range(3)] - w1 = [t1[2][i] - t1[0][i] for i in range(3)] - v2 = [t2[1][i] - t2[0][i] for i in range(3)] - w2 = [t2[2][i] - t2[0][i] for i in range(3)] - n1 = ( # surface normal for t1 - (v1[1] * w1[2]) - (v1[2] * w1[1]), - (v1[2] * w1[0]) - (v1[0] * w1[2]), - (v1[0] * w1[1]) - (v1[1] * w1[0]), - ) - n2 = ( # surface normal for t2 - (v2[1] * w2[2]) - (v2[2] * w2[1]), - (v2[2] * w2[0]) - (v2[0] * w2[2]), - (v2[0] * w2[1]) - (v2[1] * w2[0]), - ) - try: - angle = ( - n1[0] * n2[0] + n1[1] * n2[1] + n1[2] * n2[2] - ) / sqrt( - (n1[0] ** 2 + n1[1] ** 2 + n1[2] ** 2) - * (n2[0] ** 2 + n2[1] ** 2 + n2[2] ** 2) - ) - except ZeroDivisionError: - angle = 0.0 - if abs(angle) < ang_thresh: - for i, t in ((i1, t1), (i2, t2)): - # subdivide - x1, y1 = t[0][0], t[0][1] - x2, y2 = t[1][0], t[1][1] - x3, y3 = t[2][0], t[2][1] - x4, y4 = 0.5 * (x1 + x2), 0.5 * (y1 + y2) - x5, y5 = 0.5 * (x2 + x3), 0.5 * (y2 + y3) - x6, y6 = 0.5 * (x1 + x3), 0.5 * (y1 + y3) - needs_removal.add(i) - split_edges.add( - ((x1, y1), (x2, y2)) - if (x2, y2) > (x1, y1) - else ((x2, y2), (x1, y1)) - ) - split_edges.add( - ((x2, y2), (x3, y3)) - if (x3, y3) > (x2, y2) - else ((x3, y3), (x2, y2)) - ) - split_edges.add( - ((x1, y1), (x3, y3)) - if (x3, y3) > (x1, y1) - else ((x3, y3), (x1, y1)) - ) - triangle(x1, y1, x4, y4, x6, y6, depth=depth) - triangle(x2, y2, x4, y4, x5, y5, depth=depth) - triangle(x3, y3, x5, y5, x6, y6, depth=depth) - triangle(x4, y4, x5, y5, x6, y6, depth=depth) - # remove subdivided triangles which have been divided - triangles = [ - t for i, t in enumerate(triangles) if i not in needs_removal - ] + def radii(): + outer = 2.0 + inner = sector_origin.elements[1].round_to_float() + n = len(data) - # fix up subdivided edges - # - # look at every triangle and see if its edges need updating. - # depending on how many edges require subdivision we proceede with - # one of two subdivision strategies - # - # TODO possible optimisation: don't look at every triangle again - made_changes = True - while made_changes: - made_changes = False - new_triangles = [] - for i, t in enumerate(triangles): - new_points = [] - if ((t[0][0], t[0][1]), (t[1][0], t[1][1])) in split_edges: - new_points.append([0, 1]) - if ((t[1][0], t[1][1]), (t[2][0], t[2][1])) in split_edges: - new_points.append([1, 2]) - if ((t[0][0], t[0][1]), (t[2][0], t[2][1])) in split_edges: - new_points.append([0, 2]) + d = (outer - inner) / n - if len(new_points) == 0: - continue - made_changes = True - # 'triforce' subdivision - # 1 - # /\ - # 4 /__\ 6 - # /\ /\ - # /__\/__\ - # 2 5 3 - # if less than three edges require subdivision bisect them - # anyway but fake their values by averaging - x4 = 0.5 * (t[0][0] + t[1][0]) - y4 = 0.5 * (t[0][1] + t[1][1]) - v4 = stored.get((x4, y4), 0.5 * (t[0][2] + t[1][2])) + r0 = outer + for i in range(n): + r1 = r0 - d + if i > 0: + r0 -= radius_spacing * d + yield (r0, r1) + r0 = r1 - x5 = 0.5 * (t[1][0] + t[2][0]) - y5 = 0.5 * (t[1][1] + t[2][1]) - v5 = stored.get((x5, y5), 0.5 * (t[1][2] + t[2][2])) + def phis(values): + s = sum(values) - x6 = 0.5 * (t[0][0] + t[2][0]) - y6 = 0.5 * (t[0][1] + t[2][1]) - v6 = stored.get((x6, y6), 0.5 * (t[0][2] + t[2][2])) + t = 0.0 + pi2 = pi * 2.0 + phi0 = pi + spacing = sector_sign * segment_spacing / 2.0 - if not (v4 is None or v6 is None): - new_triangles.append(sorted((t[0], (x4, y4, v4), (x6, y6, v6)))) - if not (v4 is None or v5 is None): - new_triangles.append(sorted((t[1], (x4, y4, v4), (x5, y5, v5)))) - if not (v5 is None or v6 is None): - new_triangles.append(sorted((t[2], (x5, y5, v5), (x6, y6, v6)))) - if not (v4 is None or v5 is None or v6 is None): - new_triangles.append( - sorted(((x4, y4, v4), (x5, y5, v5), (x6, y6, v6))) - ) - triangles[i] = None + for k, value in enumerate(values): + t += value + phi1 = sector_phi + sector_sign * (t / s) * pi2 - triangles.extend(new_triangles) - triangles = [t for t in triangles if t is not None] + yield (phi0 + spacing, phi1 - spacing) + phi0 = phi1 - # add the mesh - mesh_points = [] - if mesh == "System`Full": - for xi in range(plotpoints[0] + 1): - xval = xstart + xi / numx * (xstop - xstart) - mesh_row = [] - for yi in range(plotpoints[1] + 1): - yval = ystart + yi / numy * (ystop - ystart) - z = stored[(xval, yval)] - mesh_row.append((xval, yval, z)) - mesh_points.append(mesh_row) + def segments(): + yield Expression(SymbolEdgeForm, Symbol("Black")) - for yi in range(plotpoints[1] + 1): - yval = ystart + yi / numy * (ystop - ystart) - mesh_col = [] - for xi in range(plotpoints[0] + 1): - xval = xstart + xi / numx * (xstop - xstart) - z = stored[(xval, yval)] - mesh_col.append((xval, yval, z)) - mesh_points.append(mesh_col) + origin = vector2(0.0, 0.0) - # handle edge subdivisions - made_changes = True - while made_changes: - made_changes = False - for mesh_line in mesh_points: - i = 0 - while i < len(mesh_line) - 1: - x1, y1, v1 = mesh_line[i] - x2, y2, v2 = mesh_line[i + 1] - key = ( - ((x1, y1), (x2, y2)) - if (x2, y2) > (x1, y1) - else ((x2, y2), (x1, y1)) - ) - if key in split_edges: - x3 = 0.5 * (x1 + x2) - y3 = 0.5 * (y1 + y2) - v3 = stored[(x3, y3)] - mesh_line.insert(i + 1, (x3, y3, v3)) - made_changes = True - i += 1 - i += 1 + for values, (r0, r1) in zip(data, radii()): + radius = vector2(r0, r0) - # handle missing regions - old_meshpoints, mesh_points = mesh_points, [] - for mesh_line in old_meshpoints: - mesh_points.extend( - [ - sorted(g) - for k, g in itertools.groupby( - mesh_line, lambda x: x[2] is None - ) - ] + n = len(values) + + for k, (phi0, phi1) in enumerate(phis(values)): + yield Expression( + SymbolStyle, + Expression(SymbolDisk, origin, radius, vector2(phi0, phi1)), + color(k + 1, n), + ) + + if r1 > 0.0: + yield Expression( + SymbolStyle, + Expression(SymbolDisk, origin, vector2(r1, r1)), + Symbol("White"), + ) + + def labels(names): + yield Expression(SymbolFaceForm, Symbol("Black")) + + for values, (r0, r1) in zip(data, radii()): + for name, (phi0, phi1) in zip(names, phis(values)): + r = (r0 + r1) / 2.0 + phi = (phi0 + phi1) / 2.0 + yield Expression( + SymbolText, name, vector2(r * cos(phi), r * sin(phi)) ) - mesh_points = [ - mesh_line - for mesh_line in mesh_points - if not any(x[2] is None for x in mesh_line) - ] - elif mesh == "System`All": - 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])) - mesh_points.add((t[0], t[2]) if t[2] > t[0] else (t[2], t[0])) - mesh_points = list(mesh_points) - # find the max and min height - v_min = v_max = None - for t in triangles: - for tx, ty, v in t: - if v_min is None or v < v_min: - v_min = v - if v_max is None or v > v_max: - v_max = v - graphics.extend( - self.construct_graphics( - triangles, mesh_points, v_min, v_max, options, evaluation - ) - ) - return self.final_graphics(graphics, options) + graphics = list(segments()) + + chart_labels = self.get_option(options, "ChartLabels", evaluation) + if chart_labels.get_head_name() == "System`List": + graphics.extend(list(labels(chart_labels.elements))) + + options["System`PlotRange"] = ListExpression( + vector2(-2.0, 2.0), vector2(-2.0, 2.0) + ) + + return Expression( + SymbolGraphics, ListExpression(*graphics), *options_to_rules(options) + ) class Plot(_Plot): """ + :WMA link: https://reference.wolfram.com/language/ref/Plot.html
'Plot[$f$, {$x$, $xmin$, $xmax$}]'
plots $f$ with $x$ ranging from $xmin$ to $xmax$. @@ -1904,6 +2131,7 @@ def _apply_fn(self, f, x_value): class ParametricPlot(_Plot): """ + :WMA link: https://reference.wolfram.com/language/ref/ParametricPlot.html
'ParametricPlot[{$f_x$, $f_y$}, {$u$, $umin$, $umax$}]'
plots a parametric function $f$ with the parameter $u$ ranging from $umin$ to $umax$. @@ -1969,14 +2197,19 @@ def _apply_fn(self, f, x_value): class PolarPlot(_Plot): """ + :WMA link: https://reference.wolfram.com/language/ref/PolarPlot.html
'PolarPlot[$r$, {$t$, $t_min$, $t_max$}]' -
creates a polar plot of curve with radius $r$ as a function of angle $t$ ranging from $t_min$ to $t_max$. +
creates a polar plot of curve with radius $r$ as a function of angle $t$ \ + ranging from $t_min$ to $t_max$.
- In a Polar Plot, a polar coordinate system is used. + In a Polar Plot, a :polar coordinate system: + https://en.wikipedia.org/wiki/Polar_coordinate_system is used. - A polar coordinate system is a two-dimensional coordinate system in which each point on a plane is determined by a distance from a reference point and an angle from a reference direction. + A polar coordinate system is a two-dimensional coordinate system in which \ + each point on a plane is determined by a distance from a reference point \ + and an angle from a reference direction. Here is a 5-blade propeller, or maybe a flower, using 'PolarPlot': @@ -2039,84 +2272,9 @@ def _apply_fn(self, f, x_value): return (value * cos(x_value), value * sin(x_value)) -class ListPlot(_ListPlot): - """ -
-
'ListPlot[{$y_1$, $y_2$, ...}]' -
plots a list of y-values, assuming integer x-values 1, 2, 3, ... - -
'ListPlot[{{$x_1$, $y_1$}, {$x_2$, $y_2$}, ...}]' -
plots a list of $x$, $y$ pairs. - -
'ListPlot[{$list_1$, $list_2$, ...}]' -
plots several lists of points. -
- - ListPlot accepts a superset of the Graphics options. - - >> ListPlot[Table[n ^ 2, {n, 10}]] - = -Graphics- - """ - - attributes = A_HOLD_ALL | A_PROTECTED - - options = Graphics.options.copy() - options.update( - { - "Axes": "True", - "AspectRatio": "1 / GoldenRatio", - "Mesh": "None", - "PlotRange": "Automatic", - "PlotPoints": "None", - "Filling": "None", - "Joined": "False", - "Discrete": "False", - } - ) - summary_text = "plot lists of points" - - -class ListLinePlot(_ListPlot): - """ -
-
'ListLinePlot[{$y_1$, $y_2$, ...}]' -
plots a line through a list of $y$-values, assuming integer $x$-values 1, 2, 3, ... - -
'ListLinePlot[{{$x_1$, $y_1$}, {$x_2$, $y_2$}, ...}]' -
plots a line through a list of $x$, $y$ pairs. - -
'ListLinePlot[{$list_1$, $list_2$, ...}]' -
plots several lines. -
- - ListPlot accepts a superset of the Graphics options. - - >> ListLinePlot[Table[{n, n ^ 0.5}, {n, 10}]] - = -Graphics- - - >> ListLinePlot[{{-2, -1}, {-1, -1}}] - = -Graphics- - """ - - attributes = A_HOLD_ALL | A_PROTECTED - - options = Graphics.options.copy() - options.update( - { - "Axes": "True", - "AspectRatio": "1 / GoldenRatio", - "Mesh": "None", - "PlotRange": "Automatic", - "PlotPoints": "None", - "Filling": "None", - "Joined": "True", - } - ) - summary_text = "plot lines through lists of points" - - class Plot3D(_Plot3D): """ + :WMA link: https://reference.wolfram.com/language/ref/Plot3D.html
'Plot3D[$f$, {$x$, $xmin$, $xmax$}, {$y$, $ymin$, $ymax$}]'
creates a three-dimensional plot of $f$ with $x$ ranging from $xmin$ to $xmax$ and $y$ ranging from $ymin$ to $ymax$. @@ -2232,154 +2390,3 @@ def final_graphics(self, graphics, options): ListExpression(*graphics), *options_to_rules(options, Graphics3D.options) ) - - -class DensityPlot(_Plot3D): - """ -
-
'DensityPlot[$f$, {$x$, $xmin$, $xmax$}, {$y$, $ymin$, $ymax$}]' -
plots a density plot of $f$ with $x$ ranging from $xmin$ to $xmax$ and $y$ ranging from $ymin$ to $ymax$. -
- - >> DensityPlot[x ^ 2 + 1 / y, {x, -1, 1}, {y, 1, 4}] - = -Graphics- - - >> DensityPlot[1 / x, {x, 0, 1}, {y, 0, 1}] - = -Graphics- - - >> DensityPlot[Sqrt[x * y], {x, -1, 1}, {y, -1, 1}] - = -Graphics- - - >> DensityPlot[1/(x^2 + y^2 + 1), {x, -1, 1}, {y, -2,2}, Mesh->Full] - = -Graphics- - - >> DensityPlot[x^2 y, {x, -1, 1}, {y, -1, 1}, Mesh->All] - = -Graphics- - """ - - attributes = A_HOLD_ALL | A_PROTECTED - - options = Graphics.options.copy() - options.update( - { - "Axes": "False", - "AspectRatio": "1", - "Mesh": "None", - "Frame": "True", - "ColorFunction": "Automatic", - "ColorFunctionScaling": "True", - "PlotPoints": "None", - "MaxRecursion": "0", - # 'MaxRecursion': '2', # FIXME causes bugs in svg output see #303 - } - ) - summary_text = "density plot for a function" - - def get_functions_param(self, functions): - return [functions] - - def construct_graphics( - self, triangles, mesh_points, v_min, v_max, options, evaluation - ): - color_function = self.get_option(options, "ColorFunction", evaluation, pop=True) - color_function_scaling = self.get_option( - options, "ColorFunctionScaling", evaluation, pop=True - ) - - color_function_min = color_function_max = None - if color_function.get_name() == "System`Automatic": - color_function = String("LakeColors") - if color_function.get_string_value(): - func = Expression( - SymbolColorData, String(color_function.get_string_value()) - ).evaluate(evaluation) - if func.has_form("ColorDataFunction", 4): - color_function_min = func.elements[2].elements[0].round_to_float() - color_function_max = func.elements[2].elements[1].round_to_float() - color_function = Expression( - SymbolFunction, - Expression(func.elements[3], Expression(SymbolSlot, Integer1)), - ) - else: - evaluation.message("DensityPlot", "color", func) - return - if color_function.has_form("ColorDataFunction", 4): - color_function_min = color_function.elements[2].elements[0].round_to_float() - color_function_max = color_function.elements[2].elements[1].round_to_float() - - color_function_scaling = color_function_scaling is SymbolTrue - v_range = v_max - v_min - - if v_range == 0: - v_range = 1 - - if color_function.has_form("ColorDataFunction", 4): - color_func = color_function.elements[3] - else: - color_func = color_function - if ( - color_function_scaling - and color_function_min is not None # noqa - and color_function_max is not None - ): - color_function_range = color_function_max - color_function_min - - colors = {} - - def eval_color(x, y, v): - v_scaled = (v - v_min) / v_range - if ( - color_function_scaling - and color_function_min is not None # noqa - and color_function_max is not None - ): - v_color_scaled = color_function_min + v_scaled * color_function_range - else: - v_color_scaled = v - - # Calculate and store 100 different shades max. - v_lookup = int(v_scaled * 100 + 0.5) - - value = colors.get(v_lookup) - if value is None: - value = Expression(color_func, Real(v_color_scaled)) - value = value.evaluate(evaluation) - colors[v_lookup] = value - return value - - points = [] - vertex_colors = [] - graphics = [] - for p in triangles: - points.append(ListExpression(*(to_mathics_list(*x[:2]) for x in p))) - vertex_colors.append(ListExpression(*(eval_color(*x) for x in p))) - - graphics.append( - Expression( - SymbolPolygon, - ListExpression(*points), - Expression( - SymbolRule, - Symbol("VertexColors"), - ListExpression(*vertex_colors), - ), - ) - ) - - # add mesh - for xi in range(len(mesh_points)): - line = [] - for yi in range(len(mesh_points[xi])): - line.append( - to_mathics_list(mesh_points[xi][yi][0], mesh_points[xi][yi][1]) - ) - graphics.append(Expression(SymbolLine, ListExpression(*line))) - - return graphics - - def final_graphics(self, graphics, options): - return Expression( - SymbolGraphics, - ListExpression(*graphics), - *options_to_rules(options, Graphics.options) - ) diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index a49c30a5d..3592eb616 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -89,6 +89,7 @@ SymbolHold = Symbol("System`Hold") SymbolHoldForm = Symbol("System`HoldForm") SymbolHoldPattern = Symbol("System`HoldPattern") +SymbolHue = Symbol("System`Hue") SymbolIf = Symbol("System`If") SymbolIm = Symbol("System`Im") SymbolImplies = Symbol("System`Implies") @@ -104,6 +105,7 @@ SymbolLength = Symbol("System`Length") SymbolLess = Symbol("System`Less") SymbolLessEqual = Symbol("System`LessEqual") +SymbolLine = Symbol("System`Line") SymbolLog = Symbol("System`Log") SymbolMachinePrecision = Symbol("System`MachinePrecision") SymbolMakeBoxes = Symbol("System`MakeBoxes") @@ -143,10 +145,11 @@ SymbolPart = Symbol("System`Part") SymbolPattern = Symbol("System`Pattern") SymbolPatternTest = Symbol("System`PatternTest") -SymbolPower = Symbol("System`Power") SymbolPi = Symbol("System`Pi") SymbolPiecewise = Symbol("System`Piecewise") +SymbolPlot = Symbol("System`Plot") SymbolPoint = Symbol("System`Point") +SymbolPower = Symbol("System`Power") SymbolPolygon = Symbol("System`Polygon") SymbolPossibleZeroQ = Symbol("System`PossibleZeroQ") SymbolPrecision = Symbol("System`Precision") From fb335cdfc7c021a97a31399d76f958d3e892da2b Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 14 Dec 2022 20:33:09 -0500 Subject: [PATCH 014/121] Lint from pycharm --- mathics/builtin/drawing/plot.py | 11 +++++------ mathics/eval/plot.py | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 1e463a673..82392381d 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -5,11 +5,6 @@ Plotting functions take a function as a parameter and data, often a range of points, as another parameter, and plot or show the function applied to the data. """ -# This tells documentation how to sort this module -# Here we are also hiding "drawing" since this erroneously appears at the top level. -sort_order = "mathics.builtin.plotting-data" - - import itertools import numbers from math import cos, pi, sin, sqrt @@ -55,12 +50,17 @@ get_plot_range, ) +# This tells documentation how to sort this module +# Here we are also hiding "drawing" since this erroneously appears at the top level. +sort_order = "mathics.builtin.plotting-data" + SymbolColorDataFunction = Symbol("ColorDataFunction") SymbolDisk = Symbol("Disk") SymbolFaceForm = Symbol("FaceForm") SymbolRectangle = Symbol("Rectangle") SymbolText = Symbol("Text") + # PlotRange Option def check_plot_range(range, range_type) -> bool: """ @@ -472,7 +472,6 @@ def colors(self): class _Plot(Builtin): - attributes = A_HOLD_ALL | A_PROTECTED expect_list = False diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index 2e671ef4d..dbb0b079f 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -2,7 +2,7 @@ Evaluation routines for 2D plotting. """ from math import cos, isinf, isnan, pi, sqrt -from typing import Callable, Iterable, List, Optional, Union +from typing import Callable, Iterable, List, Optional, Union, Type from mathics.builtin.numeric import chop from mathics.builtin.options import options_to_rules @@ -10,6 +10,7 @@ from mathics.core.atoms import Real, String from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_python +from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression @@ -92,7 +93,7 @@ def quiet_f(*args): return None return quiet_f - expr = Expression(SymbolN, expr).evaluate(evaluation) + expr: Optional[Type[BaseElement]] = Expression(SymbolN, expr).evaluate(evaluation) quiet_expr = Expression( SymbolQuiet, expr, @@ -127,7 +128,7 @@ def eval_Plot( stop: int, x_range: list, y_range, - plotpoints: int, + num_plot_points: int, mesh, list_is_expected: bool, exclusions: list, @@ -147,7 +148,7 @@ def eval_Plot( x_range: x-axis range of the form Automatic, All, or [min, max] y_range: y-axis range of the form Automatic, All, or [min, max] y_range: either Automatic, All, or of the form [min, max] - plotpoints: number of points to plot + num_plot_points: number of points to plot list_is_expected: list is expected in evaluation (?) max_recursion: maximum number of levels of recursion in evaluation (?) options: Plot options @@ -190,9 +191,9 @@ def get_points_range(points): xvalues = [] # x value for each point in points tmp_mesh_points = [] # For this function only continuous = False - d = (stop - start) / (plotpoints - 1) + d = (stop - start) / (num_plot_points - 1) compiled_fn = compile_quiet_function(f, [x_name], evaluation, list_is_expected) - for i in range(plotpoints): + for i in range(num_plot_points): x_value = start + i * d point = apply_fn(compiled_fn, x_value) if point is not None: @@ -337,8 +338,8 @@ def find_excl(excl): if mesh != "None": for hue, points in zip(function_hues, mesh_points): graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) - meshpoints = [to_mathics_list(xx, yy) for xx, yy in points] - graphics.append(Expression(SymbolPoint, ListExpression(*meshpoints))) + mesh_points = [to_mathics_list(xx, yy) for xx, yy in points] + graphics.append(Expression(SymbolPoint, ListExpression(*mesh_points))) return Expression( SymbolGraphics, ListExpression(*graphics), *options_to_rules(options) @@ -379,7 +380,7 @@ def zero_to_one(value: Union[float, int]) -> Union[float, int]: Return 1 only if ``value`` is zero, otherwise keep the value as is. This is useful in scaling when the value can be used as - a divisor or when determining the number of points to plot and we want to + a divisor or when determining the number of points to plot, and we want to assure there is at least one point plotted. """ return 1 if value == 0 else value From 28d6480fa57d68ffd5ec925e06930a3ae4604213 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 15 May 2022 12:25:05 -0400 Subject: [PATCH 015/121] WIP Start DiscretePlot via Plot. -- Not working. But can use ListPlot[ ... Discrete->True] --- mathics/builtin/drawing/discrete_plot.py | 154 +++++++++++++++++++++++ mathics/builtin/drawing/plot.py | 16 +++ 2 files changed, 170 insertions(+) create mode 100644 mathics/builtin/drawing/discrete_plot.py diff --git a/mathics/builtin/drawing/discrete_plot.py b/mathics/builtin/drawing/discrete_plot.py new file mode 100644 index 000000000..ab117928c --- /dev/null +++ b/mathics/builtin/drawing/discrete_plot.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +import numbers + + +from mathics.builtin.drawing.plot import ( + Plot, + check_plot_range, + compile_quiet_function, + get_plot_range, +) +from mathics.builtin.graphics import Graphics +from mathics.builtin.options import options_to_rules + +from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED +from mathics.core.convert.python import from_python +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol, SymbolList +from mathics.core.systemsymbols import SymbolPlot + + +class _DiscretePlot(Plot): + + attributes = A_HOLD_ALL | A_PROTECTED + + options = Graphics.options.copy() + options.update( + { + "Axes": "True", + "AspectRatio": "1 / GoldenRatio", + "PlotRange": "Automatic", + "$OptionSyntax": "Strict", + } + ) + + messages = { + "prng": ( + "Value of option PlotRange -> `1` is not All, Automatic or " + "an appropriate list of range specifications." + ), + "invexcl": ( + "Value of Exclusions -> `1` is not None, Automatic or an " + "appropriate list of constraints." + ), + } + + expect_list = False + + def eval(self, function, x, start, stop, evaluation, options): + """%(name)s[function_, {x_Symbol, start_Integer, stop_Integer}, + OptionsPattern[%(name)s]]""" + if isinstance(function, Symbol) and function.name is not x.get_name(): + rules = evaluation.definitions.get_ownvalues(function.name) + for rule in rules: + function = rule.apply(function, evaluation, fully=True) + + expr_limits = Expression(SymbolList, x, start, stop) + expr = Expression( + self.get_name(), function, expr_limits, *options_to_rules(options) + ) + x_name = x.get_name() + + py_start = start.value + py_stop = stop.value + if py_start is None or py_stop is None: + return evaluation.message(self.get_name(), "plln", stop, expr) + if py_start >= py_stop: + return evaluation.message(self.get_name(), "plld", expr_limits) + + plotrange_option = self.get_option(options, "PlotRange", evaluation) + + plotrange = plotrange_option.to_python(n_evaluation=evaluation) + x_range, y_range = self.get_plotrange(plotrange, py_start, py_stop) + if not check_plot_range(x_range, numbers.Real) or not check_plot_range( + y_range, numbers.Real + ): + evaluation.message(self.get_name(), "prng", plotrange_option) + x_range, y_range = [py_start, py_stop], "Automatic" + + # x_range and y_range are now either Automatic, All, or of the form [min, max] + assert x_range in ("System`Automatic", "System`All") or isinstance( + x_range, list + ) + assert y_range in ("System`Automatic", "System`All") or isinstance( + y_range, list + ) + + # PlotPoints Option + # constants to generate colors + + def get_points_minmax(points): + xmin = xmax = ymin = ymax = None + for line in points: + for x, y in line: + if xmin is None or x < xmin: + xmin = x + if xmax is None or x > xmax: + xmax = x + if ymin is None or y < ymin: + ymin = y + if ymax is None or y > ymax: + ymax = y + return xmin, xmax, ymin, ymax + + base_plot_points = [] # list of points in base subdivision + plot_points = [] # list of all plotted points + graphics = [] # list of resulting graphics primitives + + points = [] + xvalues = [] # x value for each point in points + cf = compile_quiet_function(function, [x_name], evaluation, self.expect_list) + for x_value in range(py_start, py_stop): + point = self.eval_f(cf, x_value) + points.append(point) + xvalues.append(x_value) + + x_range = get_plot_range( + [xx for xx, yy in base_plot_points], [xx for xx, yy in plot_points], x_range + ) + y_range = get_plot_range( + [yy for xx, yy in base_plot_points], [yy for xx, yy in plot_points], y_range + ) + + graphics = from_python(points) + + options["System`PlotRange"] = from_python([x_range, y_range]) + options["System`Discrete"] = True + + return Expression( + SymbolPlot, + Expression(SymbolList, *graphics), + # *options_to_rules(options) + ) + + +class DiscretePlot(_DiscretePlot): + """ +
+
'DiscretePlot[$f$, {$x$, $xmin$, $xmax$}]' +
plots $f$ with $x$ ranging from $xmin$ to $xmax$. + +
'DiscretePlot[{$f1$, $f2$, ...}, {$x$, $xmin$, $xmax$}]' +
plots several functions $f1$, $f2$, ... + +
+ + >> DiscretePlot[PrimePi[k], {k, 1, 50}] + = -Graphics- + + >> DiscretePlot[Sin[x], {x, 0, 4 Pi}, DiscretePlotRange->{{0, 4 Pi}, {0, 1.5}}] + = -Graphics- + + >> DiscretePlot[Tan[x], {x, -6, 6}, Mesh->Full] + = -Graphics- + """ diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 82392381d..55c6b1632 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -337,6 +337,10 @@ def eval(self, points, evaluation, options): evaluation.message(plot_name, "joind", joined_option, expr) joined = False + # Hack until we separate DiscretePlot + discrete_plot_option = self.get_option(options, "Discrete", evaluation) + discrete_plot = discrete_plot_option.to_python() + if isinstance(all_points, list) and len(all_points) != 0: if all(not isinstance(point, list) for point in all_points): # Only y values given @@ -428,6 +432,18 @@ def eval(self, points, evaluation, options): graphics.append( Expression(SymbolPolygon, from_python(fill_area)) ) + elif discrete_plot: + graphics.append(Expression(SymbolPoint, mathics_segment)) + for mathics_point in mathics_segment: + graphics.append( + Expression( + SymbolLine, + ListExpression( + ListExpression(mathics_point[0], Integer0), + mathics_point, + ), + ) + ) else: graphics.append(Expression(SymbolPoint, from_python(segment))) if filling is not None: From afa0c607a9b96024de69a134a422fa1d7c67ebd2 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 08:41:38 -0500 Subject: [PATCH 016/121] First working DiscretePlot --- mathics/builtin/drawing/discrete_plot.py | 83 +++++----- mathics/builtin/drawing/plot.py | 187 ++++------------------- mathics/eval/plot.py | 186 +++++++++++++++++++--- 3 files changed, 248 insertions(+), 208 deletions(-) diff --git a/mathics/builtin/drawing/discrete_plot.py b/mathics/builtin/drawing/discrete_plot.py index ab117928c..91e4f4d94 100644 --- a/mathics/builtin/drawing/discrete_plot.py +++ b/mathics/builtin/drawing/discrete_plot.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- import numbers +from functools import lru_cache +from typing import Optional from mathics.builtin.drawing.plot import ( Plot, @@ -14,8 +16,10 @@ from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED from mathics.core.convert.python import from_python from mathics.core.expression import Expression +from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolList -from mathics.core.systemsymbols import SymbolPlot +from mathics.eval.nevaluator import eval_N +from mathics.eval.plot import eval_ListPlot class _DiscretePlot(Plot): @@ -51,12 +55,24 @@ def eval(self, function, x, start, stop, evaluation, options): if isinstance(function, Symbol) and function.name is not x.get_name(): rules = evaluation.definitions.get_ownvalues(function.name) for rule in rules: - function = rule.apply(function, evaluation, fully=True) - - expr_limits = Expression(SymbolList, x, start, stop) + functions = rule.apply(function, evaluation, fully=True) + + if function.get_head_name() == "List": + functions_param = self.get_functions_param(functions) + for index, f in enumerate(functions_param): + if isinstance(f, Symbol) and f.name is not x.get_name(): + rules = evaluation.definitions.get_ownvalues(f.name) + for rule in rules: + f = rule.apply(f, evaluation, fully=True) + functions_param[index] = f + functions = functions.flatten_with_respect_to_head(SymbolList) + + expr_limits = ListExpression(x, start, stop) + # FIXME: arrange for self to have a .symbolname property or attribute expr = Expression( - self.get_name(), function, expr_limits, *options_to_rules(options) + Symbol(self.get_name()), function, expr_limits, *options_to_rules(options) ) + function = self.get_functions_param(function) x_name = x.get_name() py_start = start.value @@ -67,9 +83,9 @@ def eval(self, function, x, start, stop, evaluation, options): return evaluation.message(self.get_name(), "plld", expr_limits) plotrange_option = self.get_option(options, "PlotRange", evaluation) + plot_range = eval_N(plotrange_option, evaluation).to_python() - plotrange = plotrange_option.to_python(n_evaluation=evaluation) - x_range, y_range = self.get_plotrange(plotrange, py_start, py_stop) + x_range, y_range = self.get_plotrange(plot_range, py_start, py_stop) if not check_plot_range(x_range, numbers.Real) or not check_plot_range( y_range, numbers.Real ): @@ -87,48 +103,41 @@ def eval(self, function, x, start, stop, evaluation, options): # PlotPoints Option # constants to generate colors - def get_points_minmax(points): - xmin = xmax = ymin = ymax = None - for line in points: - for x, y in line: - if xmin is None or x < xmin: - xmin = x - if xmax is None or x > xmax: - xmax = x - if ymin is None or y < ymin: - ymin = y - if ymax is None or y > ymax: - ymax = y - return xmin, xmax, ymin, ymax - base_plot_points = [] # list of points in base subdivision plot_points = [] # list of all plotted points - graphics = [] # list of resulting graphics primitives - points = [] - xvalues = [] # x value for each point in points - cf = compile_quiet_function(function, [x_name], evaluation, self.expect_list) + compiled_fn = compile_quiet_function( + function[0], [x_name], evaluation, self.expect_list + ) + + @lru_cache + def apply_fn(fn, x_value: int) -> Optional[float]: + value = fn(x_value) + if value is not None: + value = float(value) + return value + for x_value in range(py_start, py_stop): - point = self.eval_f(cf, x_value) - points.append(point) - xvalues.append(x_value) + point = apply_fn(compiled_fn, x_value) + plot_points.append((x_value, point)) x_range = get_plot_range( [xx for xx, yy in base_plot_points], [xx for xx, yy in plot_points], x_range ) - y_range = get_plot_range( - [yy for xx, yy in base_plot_points], [yy for xx, yy in plot_points], y_range - ) - - graphics = from_python(points) + y_values = [yy for xx, yy in plot_points] + y_range = get_plot_range(y_values, y_values, option="System`All") options["System`PlotRange"] = from_python([x_range, y_range]) options["System`Discrete"] = True - return Expression( - SymbolPlot, - Expression(SymbolList, *graphics), - # *options_to_rules(options) + return eval_ListPlot( + plot_points, + x_range, + y_range, + is_discrete_plot=True, + is_joined_plot=False, + filling=False, + options=options, ) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 55c6b1632..8fa01b56f 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -2,7 +2,8 @@ """ Plotting Data -Plotting functions take a function as a parameter and data, often a range of points, as another parameter, and plot or show the function applied to the data. +Plotting functions take a function as a parameter and data, often a range of \ +points, as another parameter, and plot or show the function applied to the data. """ import itertools @@ -11,6 +12,8 @@ import palettable +from functools import lru_cache + from mathics.builtin.base import Builtin from mathics.builtin.drawing.graphics3d import Graphics3D from mathics.builtin.graphics import Graphics @@ -30,10 +33,8 @@ SymbolGraphics, SymbolGraphics3D, SymbolGrid, - SymbolHue, SymbolLine, SymbolMap, - SymbolPoint, SymbolPolygon, SymbolRGBColor, SymbolRow, @@ -43,9 +44,8 @@ ) from mathics.eval.nevaluator import eval_N from mathics.eval.plot import ( - RealPoint2, - RealPoint6, compile_quiet_function, + eval_ListPlot, eval_Plot, get_plot_range, ) @@ -332,146 +332,19 @@ def eval(self, points, evaluation, options): # Joined Option joined_option = self.get_option(options, "Joined", evaluation) - joined = joined_option.to_python() - if joined not in [True, False]: + is_joined_plot = joined_option.to_python() + if is_joined_plot not in [True, False]: evaluation.message(plot_name, "joind", joined_option, expr) - joined = False - - # Hack until we separate DiscretePlot - discrete_plot_option = self.get_option(options, "Discrete", evaluation) - discrete_plot = discrete_plot_option.to_python() - - if isinstance(all_points, list) and len(all_points) != 0: - if all(not isinstance(point, list) for point in all_points): - # Only y values given - all_points = [ - [[float(i + 1), all_points[i]] for i in range(len(all_points))] - ] - elif all(isinstance(line, list) and len(line) == 2 for line in all_points): - # Single list of (x,y) pairs - all_points = [all_points] - elif all(isinstance(line, list) for line in all_points): - # List of lines - if all( - isinstance(point, list) and len(point) == 2 - for line in all_points - for point in line - ): - pass - elif all( - not isinstance(point, list) for line in all_points for point in line - ): - all_points = [ - [[float(i + 1), l] for i, l in enumerate(line)] - for line in all_points - ] - else: - return - else: - return - else: - return - - # Split into segments at missing data - all_points = [[line] for line in all_points] - for lidx, line in enumerate(all_points): - i = 0 - while i < len(all_points[lidx]): - seg = line[i] - for j, point in enumerate(seg): - if not ( - isinstance(point[0], (int, float)) - and isinstance(point[1], (int, float)) - ): - all_points[lidx].insert(i, seg[:j]) - all_points[lidx][i + 1] = seg[j + 1 :] - i -= 1 - break - - i += 1 + is_joined_plot = False - y_range = get_plot_range( - [y for line in all_points for seg in line for x, y in seg], - [y for line in all_points for seg in line for x, y in seg], - y_range, - ) - x_range = get_plot_range( - [x for line in all_points for seg in line for x, y in seg], - [x for line in all_points for seg in line for x, y in seg], + return eval_ListPlot( + all_points, x_range, - ) - - if filling == "System`Axis": - # TODO: Handle arbitary axis intercepts - filling = 0.0 - elif filling == "System`Bottom": - filling = y_range[0] - elif filling == "System`Top": - filling = y_range[1] - - hue = 0.67 - hue_pos = 0.236068 - hue_neg = -0.763932 - - graphics = [] - for indx, line in enumerate(all_points): - graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) - for segment in line: - mathics_segment = from_python(segment) - if joined: - graphics.append(Expression(SymbolLine, mathics_segment)) - if filling is not None: - graphics.append( - Expression( - SymbolHue, Real(hue), RealPoint6, RealPoint6, RealPoint2 - ) - ) - fill_area = list(segment) - fill_area.append([segment[-1][0], filling]) - fill_area.append([segment[0][0], filling]) - graphics.append( - Expression(SymbolPolygon, from_python(fill_area)) - ) - elif discrete_plot: - graphics.append(Expression(SymbolPoint, mathics_segment)) - for mathics_point in mathics_segment: - graphics.append( - Expression( - SymbolLine, - ListExpression( - ListExpression(mathics_point[0], Integer0), - mathics_point, - ), - ) - ) - else: - graphics.append(Expression(SymbolPoint, from_python(segment))) - if filling is not None: - for point in segment: - graphics.append( - Expression( - SymbolLine, - from_python( - [[point[0], filling], [point[0], point[1]]] - ), - ) - ) - - if indx % 4 == 0: - hue += hue_pos - else: - hue += hue_neg - if hue > 1: - hue -= 1 - if hue < 0: - hue += 1 - - options["System`PlotRange"] = from_python([x_range, y_range]) - - return Expression( - SymbolGraphics, - ListExpression(*graphics), - *options_to_rules(options, Graphics.options) + y_range, + is_discrete_plot=False, + is_joined_plot=is_joined_plot, + filling=filling, + options=options, ) @@ -541,7 +414,7 @@ def eval(self, functions, x, start, stop, evaluation, options): functions = functions.flatten_with_respect_to_head(SymbolList) expr_limits = ListExpression(x, start, stop) - # FIXME: arrange forself to have a .symbolname property or attribute + # FIXME: arrange for self to have a .symbolname property or attribute expr = Expression( Symbol(self.get_name()), functions, expr_limits, *options_to_rules(options) ) @@ -556,8 +429,8 @@ def eval(self, functions, x, start, stop, evaluation, options): return evaluation.message(self.get_name(), "plld", expr_limits) plotrange_option = self.get_option(options, "PlotRange", evaluation) - plotrange = eval_N(plotrange_option, evaluation).to_python() - x_range, y_range = self.get_plotrange(plotrange, py_start, py_stop) + plot_range = eval_N(plotrange_option, evaluation).to_python() + x_range, y_range = self.get_plotrange(plot_range, py_start, py_stop) if not check_plot_range(x_range, numbers.Real) or not check_plot_range( y_range, numbers.Real ): @@ -775,7 +648,7 @@ def check_plotpoints(steps): f, [x.get_name(), y.get_name()], evaluation, False ) - def _apply_fn(x_value, y_value): + def apply_fn(compiled_fn, x_value, y_value): try: # Try to used cached value first return stored[(x_value, y_value)] @@ -791,7 +664,11 @@ def _apply_fn(x_value, y_value): split_edges = set() # subdivided edges def triangle(x1, y1, x2, y2, x3, y3, depth=0): - v1, v2, v3 = _apply_fn(x1, y1), _apply_fn(x2, y2), _apply_fn(x3, y3) + v1, v2, v3 = ( + apply_fn(compiled_fn, x1, y1), + apply_fn(compiled_fn, x2, y2), + apply_fn(compiled_fn, x3, y3), + ) if (v1 is v2 is v3 is None) and (depth > max_depth // 2): # fast finish because the entire region is undefined but @@ -868,10 +745,10 @@ def triangle(x1, y1, x2, y2, x3, y3, depth=0): ) ) - v1 = _apply_fn(x1, y1) - v2 = _apply_fn(x2, y2) - v3 = _apply_fn(x3, y3) - v4 = _apply_fn(x4, y4) + v1 = apply_fn(compiled_fn, x1, y1) + v2 = apply_fn(compiled_fn, x2, y2) + v3 = apply_fn(compiled_fn, x3, y3) + v4 = apply_fn(compiled_fn, x4, y4) if v1 is None or v4 is None: triangle(x1, y1, x2, y2, x3, y3) @@ -1824,7 +1701,6 @@ class ListPlot(_ListPlot): "PlotPoints": "None", "Filling": "None", "Joined": "False", - "Discrete": "False", } ) summary_text = "plot lists of points" @@ -2138,6 +2014,7 @@ def get_plotrange(self, plotrange, start, stop): x_range = [start, stop] return x_range, y_range + @lru_cache def _apply_fn(self, f, x_value): value = f(x_value) if value is not None: @@ -2204,8 +2081,9 @@ def get_plotrange(self, plotrange, start, stop): x_range, y_range = plotrange return x_range, y_range - def _apply_fn(self, f, x_value): - value = f(x_value) + @lru_cache + def _apply_fn(self, fn, x_value): + value = fn(x_value) if value is not None and len(value) == 2: return value @@ -2281,6 +2159,7 @@ def get_plotrange(self, plotrange, start, stop): x_range, y_range = plotrange return x_range, y_range + @lru_cache def _apply_fn(self, f, x_value): value = f(x_value) if value is not None: diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index dbb0b079f..46391abd3 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -1,13 +1,16 @@ """ Evaluation routines for 2D plotting. + +Note this is distinct from formatting/rendering, e.g. to SVG.. """ from math import cos, isinf, isnan, pi, sqrt from typing import Callable, Iterable, List, Optional, Union, Type +from mathics.builtin.graphics import Graphics from mathics.builtin.numeric import chop from mathics.builtin.options import options_to_rules from mathics.builtin.scoping import dynamic_scoping -from mathics.core.atoms import Real, String +from mathics.core.atoms import Integer, Integer0, Real, String from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_python from mathics.core.element import BaseElement @@ -21,6 +24,7 @@ SymbolLine, SymbolMessageName, SymbolPoint, + SymbolPolygon, SymbolQuiet, ) @@ -120,6 +124,149 @@ def quiet_f(*args): return quiet_f +def eval_ListPlot( + plot_points: list, + x_range: int, + y_range: int, + is_discrete_plot: bool, + is_joined_plot: bool, + filling, + options: dict, +): + if isinstance(plot_points, list) and len(plot_points) != 0: + if all(not isinstance(point, (list, tuple)) for point in plot_points): + # Only y values given + plot_points = [ + [[float(i + 1), plot_points[i]] for i in range(len(plot_points))] + ] + elif all( + isinstance(line, (list, tuple)) and len(line) == 2 for line in plot_points + ): + # Single list of (x,y) pairs + plot_points = [plot_points] + elif all(isinstance(line, list) for line in plot_points): + # List of lines + if all( + isinstance(point, list) and len(point) == 2 + for line in plot_points + for point in line + ): + pass + elif all( + not isinstance(point, list) for line in plot_points for point in line + ): + plot_points = [ + [[float(i + 1), l] for i, l in enumerate(line)] + for line in plot_points + ] + else: + return + else: + return + else: + return + + # Split into segments at missing data + plot_points = [[line] for line in plot_points] + for lidx, line in enumerate(plot_points): + i = 0 + while i < len(plot_points[lidx]): + seg = line[i] + for j, point in enumerate(seg): + if not ( + isinstance(point[0], (int, float)) + and isinstance(point[1], (int, float)) + ): + plot_points[lidx].insert(i, seg[:j]) + plot_points[lidx][i + 1] = seg[j + 1 :] + i -= 1 + break + + i += 1 + + y_range = get_plot_range( + [y for line in plot_points for seg in line for x, y in seg], + [y for line in plot_points for seg in line for x, y in seg], + y_range, + ) + x_range = get_plot_range( + [x for line in plot_points for seg in line for x, y in seg], + [x for line in plot_points for seg in line for x, y in seg], + x_range, + ) + + if filling == "System`Axis": + # TODO: Handle arbitary axis intercepts + filling = 0.0 + elif filling == "System`Bottom": + filling = y_range[0] + elif filling == "System`Top": + filling = y_range[1] + + hue = 0.67 + hue_pos = 0.236068 + hue_neg = -0.763932 + + graphics = [] + for indx, line in enumerate(plot_points): + graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) + for segment in line: + mathics_segment = from_python(segment) + if is_joined_plot: + graphics.append(Expression(SymbolLine, mathics_segment)) + if filling is not None: + graphics.append( + Expression( + SymbolHue, Real(hue), RealPoint6, RealPoint6, RealPoint2 + ) + ) + fill_area = list(segment) + fill_area.append([segment[-1][0], filling]) + fill_area.append([segment[0][0], filling]) + graphics.append(Expression(SymbolPolygon, from_python(fill_area))) + elif is_discrete_plot: + graphics.append(Expression(SymbolPoint, mathics_segment)) + for mathics_point in mathics_segment: + graphics.append( + Expression( + SymbolLine, + ListExpression( + ListExpression(mathics_point[0], Integer0), + mathics_point, + ), + ) + ) + else: + graphics.append(Expression(SymbolPoint, from_python(segment))) + if filling is not None: + for point in segment: + graphics.append( + Expression( + SymbolLine, + from_python( + [[point[0], filling], [point[0], point[1]]] + ), + ) + ) + + if indx % 4 == 0: + hue += hue_pos + else: + hue += hue_neg + if hue > 1: + hue -= 1 + if hue < 0: + hue += 1 + + options["System`PlotRange"] = from_python([x_range, y_range]) + + return Expression( + SymbolGraphics, + ListExpression(*graphics), + *options_to_rules(options, Graphics.options) + ) + + def eval_Plot( functions: List[Expression], apply_fn: Callable, @@ -159,20 +306,6 @@ def eval_Plot( hue_pos = 0.236068 hue_neg = -0.763932 - def get_points_minmax(points): - xmin = xmax = ymin = ymax = None - for line in points: - for x, y in line: - if xmin is None or x < xmin: - xmin = x - if xmax is None or x > xmax: - xmax = x - if ymin is None or y < ymin: - ymin = y - if ymax is None or y > ymax: - ymax = y - return xmin, xmax, ymin, ymax - def get_points_range(points): xmin, xmax, ymin, ymax = get_points_minmax(points) if xmin is None or xmax is None: @@ -347,14 +480,14 @@ def find_excl(excl): def extract_pyreal(value) -> Optional[float]: - if isinstance(value, Real): + if isinstance(value, (Real, Integer)): return chop(value).round_to_float() return None def get_plot_range(values: Iterable, all_values: Iterable, option: str) -> tuple: """ - Returns a tuple of the min and max values in values or + Returns a tuple of the min and max values in values. """ if option == "System`Automatic": result = automatic_plot_range(values) @@ -375,6 +508,25 @@ def get_plot_range(values: Iterable, all_values: Iterable, option: str) -> tuple return result +def get_points_minmax(points: Iterable) -> tuple: + """ + Return the minimum and maximum x and y values + in a list of points. + """ + xmin = xmax = ymin = ymax = None + for line in points: + for x, y in line: + if xmin is None or x < xmin: + xmin = x + if xmax is None or x > xmax: + xmax = x + if ymin is None or y < ymin: + ymin = y + if ymax is None or y > ymax: + ymax = y + return xmin, xmax, ymin, ymax + + def zero_to_one(value: Union[float, int]) -> Union[float, int]: """ Return 1 only if ``value`` is zero, otherwise keep the value as is. From fc4d6b8a5d5a41d5574dea4478979d41970a9789 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 08:50:18 -0500 Subject: [PATCH 017/121] Note DiscretePlot in CHANGES.rst --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index d763134ae..8cf7d7a22 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,7 @@ New Builtins #. ``Accuracy`` #. ``ClebschGordan`` #. ``Curl`` (2-D and 3-D vector forms only) +#. ``DiscretePlot`` #. ``Kurtosis`` #. ``PauliMatrix`` #. ``Remove`` From b1882920c49ffa076d6ae676a4fbc16deeb3db31 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 09:18:39 -0500 Subject: [PATCH 018/121] Add 1- and 2-arg forms --- mathics/builtin/drawing/discrete_plot.py | 70 +++++++++++++++--------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/mathics/builtin/drawing/discrete_plot.py b/mathics/builtin/drawing/discrete_plot.py index 91e4f4d94..991f0e666 100644 --- a/mathics/builtin/drawing/discrete_plot.py +++ b/mathics/builtin/drawing/discrete_plot.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +""" +Discrete Plot +""" import numbers from functools import lru_cache @@ -26,15 +29,7 @@ class _DiscretePlot(Plot): attributes = A_HOLD_ALL | A_PROTECTED - options = Graphics.options.copy() - options.update( - { - "Axes": "True", - "AspectRatio": "1 / GoldenRatio", - "PlotRange": "Automatic", - "$OptionSyntax": "Strict", - } - ) + expect_list = False messages = { "prng": ( @@ -47,11 +42,26 @@ class _DiscretePlot(Plot): ), } - expect_list = False + options = Graphics.options.copy() + options.update( + { + "Axes": "True", + "AspectRatio": "1 / GoldenRatio", + "PlotRange": "Automatic", + "$OptionSyntax": "Strict", + } + ) + + rules = { + # One argument-form of DiscretePlot + "DiscretePlot[expr_, {var_Symbol, nmax_Integer}]": "DiscretePlot[expr, {var, 1, nmax, 1}]", + # One argument-form of DiscretePlot + "DiscretePlot[expr_, {var_Symbol, nmin_Integer, nmax_Integer}]": "DiscretePlot[expr, {var, nmin, nmax, 1}]", + } - def eval(self, function, x, start, stop, evaluation, options): - """%(name)s[function_, {x_Symbol, start_Integer, stop_Integer}, - OptionsPattern[%(name)s]]""" + def eval(self, function, x, start, nmax, step, evaluation, options): + """DiscretePlot[function_, {x_Symbol, start_Integer, nmax_Integer, step_Integer}, + OptionsPattern[DiscretePlot]]""" if isinstance(function, Symbol) and function.name is not x.get_name(): rules = evaluation.definitions.get_ownvalues(function.name) for rule in rules: @@ -67,7 +77,7 @@ def eval(self, function, x, start, stop, evaluation, options): functions_param[index] = f functions = functions.flatten_with_respect_to_head(SymbolList) - expr_limits = ListExpression(x, start, stop) + expr_limits = ListExpression(x, start, nmax) # FIXME: arrange for self to have a .symbolname property or attribute expr = Expression( Symbol(self.get_name()), function, expr_limits, *options_to_rules(options) @@ -76,21 +86,22 @@ def eval(self, function, x, start, stop, evaluation, options): x_name = x.get_name() py_start = start.value - py_stop = stop.value - if py_start is None or py_stop is None: - return evaluation.message(self.get_name(), "plln", stop, expr) - if py_start >= py_stop: + py_nmax = nmax.value + py_step = step.value + if py_start is None or py_nmax is None: + return evaluation.message(self.get_name(), "plln", nmax, expr) + if py_start >= py_nmax: return evaluation.message(self.get_name(), "plld", expr_limits) plotrange_option = self.get_option(options, "PlotRange", evaluation) plot_range = eval_N(plotrange_option, evaluation).to_python() - x_range, y_range = self.get_plotrange(plot_range, py_start, py_stop) + x_range, y_range = self.get_plotrange(plot_range, py_start, py_nmax) if not check_plot_range(x_range, numbers.Real) or not check_plot_range( y_range, numbers.Real ): evaluation.message(self.get_name(), "prng", plotrange_option) - x_range, y_range = [py_start, py_stop], "Automatic" + x_range, y_range = [py_start, py_nmax], "Automatic" # x_range and y_range are now either Automatic, All, or of the form [min, max] assert x_range in ("System`Automatic", "System`All") or isinstance( @@ -117,7 +128,7 @@ def apply_fn(fn, x_value: int) -> Optional[float]: value = float(value) return value - for x_value in range(py_start, py_stop): + for x_value in range(py_start, py_nmax, py_step): point = apply_fn(compiled_fn, x_value) plot_points.append((x_value, point)) @@ -144,20 +155,25 @@ def apply_fn(fn, x_value: int) -> Optional[float]: class DiscretePlot(_DiscretePlot): """
-
'DiscretePlot[$f$, {$x$, $xmin$, $xmax$}]' -
plots $f$ with $x$ ranging from $xmin$ to $xmax$. +
'DiscretePlot[$expr$, {$x$, $n_max$}]' +
plots $expr$ with $x$ ranging from 1 to $n_max$. + +
'DiscretePlot[$expr$, {$x$, $n_min$, $n_max$}]' +
plots $expr$ with $x$ ranging from $n_min$ to $n_max$. -
'DiscretePlot[{$f1$, $f2$, ...}, {$x$, $xmin$, $xmax$}]' -
plots several functions $f1$, $f2$, ... +
'DiscretePlot[$f$, {$x$, $n_min$, $n_max$, $dn$}]' +
plots $f$ with $x$ ranging from $n_min$ to $n_max$ usings steps $dn$.
- >> DiscretePlot[PrimePi[k], {k, 1, 50}] + >> DiscretePlot[PrimePi[k], {k, 50}] = -Graphics- >> DiscretePlot[Sin[x], {x, 0, 4 Pi}, DiscretePlotRange->{{0, 4 Pi}, {0, 1.5}}] = -Graphics- - >> DiscretePlot[Tan[x], {x, -6, 6}, Mesh->Full] + >> DiscretePlot[Tan[x], {x, -6, 6}] = -Graphics- """ + + summary_text = "discrete plot of a one-paremeter function" From da058183e2a456198e4552e8e2445192ff6f8614 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 13:46:25 -0500 Subject: [PATCH 019/121] Symbols used a little more... and a comment corrected. --- mathics/builtin/drawing/discrete_plot.py | 4 ++-- mathics/builtin/drawing/plot.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/drawing/discrete_plot.py b/mathics/builtin/drawing/discrete_plot.py index 991f0e666..2508c51ec 100644 --- a/mathics/builtin/drawing/discrete_plot.py +++ b/mathics/builtin/drawing/discrete_plot.py @@ -55,7 +55,7 @@ class _DiscretePlot(Plot): rules = { # One argument-form of DiscretePlot "DiscretePlot[expr_, {var_Symbol, nmax_Integer}]": "DiscretePlot[expr, {var, 1, nmax, 1}]", - # One argument-form of DiscretePlot + # Two argument-form of DiscretePlot "DiscretePlot[expr_, {var_Symbol, nmin_Integer, nmax_Integer}]": "DiscretePlot[expr, {var, nmin, nmax, 1}]", } @@ -67,7 +67,7 @@ def eval(self, function, x, start, nmax, step, evaluation, options): for rule in rules: functions = rule.apply(function, evaluation, fully=True) - if function.get_head_name() == "List": + if function.head is SymbolList: functions_param = self.get_functions_param(functions) for index, f in enumerate(functions_param): if isinstance(f, Symbol) and f.name is not x.get_name(): diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 8fa01b56f..35bbd85c9 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -403,7 +403,7 @@ def eval(self, functions, x, start, stop, evaluation, options): for rule in rules: functions = rule.apply(functions, evaluation, fully=True) - if functions.get_head_name() == "List": + if functions.head is SymbolList: functions_param = self.get_functions_param(functions) for index, f in enumerate(functions_param): if isinstance(f, Symbol) and f.name is not x.get_name(): From e81fbf43ec0356a069e5baf9954d4d606d0b67bc Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 14:21:37 -0500 Subject: [PATCH 020/121] Add head() to Symbol. --- mathics/builtin/drawing/discrete_plot.py | 7 +++- mathics/core/symbols.py | 4 ++ mathics/eval/plot.py | 49 +++++++++++------------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/mathics/builtin/drawing/discrete_plot.py b/mathics/builtin/drawing/discrete_plot.py index 2508c51ec..981ed84e0 100644 --- a/mathics/builtin/drawing/discrete_plot.py +++ b/mathics/builtin/drawing/discrete_plot.py @@ -166,7 +166,12 @@ class DiscretePlot(_DiscretePlot):
- >> DiscretePlot[PrimePi[k], {k, 50}] + The number of primes for a number $k$: + >> DiscretePlot[PrimePi[k], {k, 100}] + = -Graphics- + + is about the same as 'Sqrt[k] * 3': + >> DiscretePlot[3 Sqrt[k], {k, 100}] = -Graphics- >> DiscretePlot[Sin[x], {x, 0, 4 Pi}, DiscretePlotRange->{{0, 4 Pi}, {0, 1.5}}] diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 224698b7b..45abc80e7 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -294,6 +294,10 @@ def has_form(self, heads, *element_counts) -> bool: else: return heads == name + @property + def head(self) -> str: + Symbol(self.class_head_name) + @property def is_literal(self) -> bool: """True if the value can't change and has a Python representation, diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index 46391abd3..4cd06ced3 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -133,34 +133,31 @@ def eval_ListPlot( filling, options: dict, ): - if isinstance(plot_points, list) and len(plot_points) != 0: - if all(not isinstance(point, (list, tuple)) for point in plot_points): - # Only y values given + if not isinstance(plot_points, list) or len(plot_points) == 0: + return + + if all(not isinstance(point, (list, tuple)) for point in plot_points): + # Only y values given + plot_points = [ + [[float(i + 1), plot_points[i]] for i in range(len(plot_points))] + ] + elif all( + isinstance(line, (list, tuple)) and len(line) == 2 for line in plot_points + ): + # Single list of (x,y) pairs + plot_points = [plot_points] + elif all(isinstance(line, list) for line in plot_points): + # List of lines + if all( + isinstance(point, list) and len(point) == 2 + for line in plot_points + for point in line + ): + pass + elif all(not isinstance(point, list) for line in plot_points for point in line): plot_points = [ - [[float(i + 1), plot_points[i]] for i in range(len(plot_points))] + [[float(i + 1), l] for i, l in enumerate(line)] for line in plot_points ] - elif all( - isinstance(line, (list, tuple)) and len(line) == 2 for line in plot_points - ): - # Single list of (x,y) pairs - plot_points = [plot_points] - elif all(isinstance(line, list) for line in plot_points): - # List of lines - if all( - isinstance(point, list) and len(point) == 2 - for line in plot_points - for point in line - ): - pass - elif all( - not isinstance(point, list) for line in plot_points for point in line - ): - plot_points = [ - [[float(i + 1), l] for i, l in enumerate(line)] - for line in plot_points - ] - else: - return else: return else: From fc877a4d012d624530b3fd18da13074475c69fdb Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 17:14:55 -0500 Subject: [PATCH 021/121] Improve tick axes placement ... and some other small tweaks --- mathics/builtin/drawing/discrete_plot.py | 8 +++++++ mathics/builtin/drawing/plot.py | 1 + mathics/eval/plot.py | 29 ++++++++++++++++-------- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/mathics/builtin/drawing/discrete_plot.py b/mathics/builtin/drawing/discrete_plot.py index 981ed84e0..bf9bf6d3d 100644 --- a/mathics/builtin/drawing/discrete_plot.py +++ b/mathics/builtin/drawing/discrete_plot.py @@ -138,6 +138,14 @@ def apply_fn(fn, x_value: int) -> Optional[float]: y_values = [yy for xx, yy in plot_points] y_range = get_plot_range(y_values, y_values, option="System`All") + # FIXME: For now we are going to specify that the min points are (-.1, -.1) + # or pretty close to (0, 0) for positive plots, so that the tick axes are set to zero. + # See GraphicsBox.axis_ticks(). + if x_range[0] > 0: + x_range = (-0.1, x_range[1]) + if y_range[0] > 0: + y_range = (-0.1, y_range[1]) + options["System`PlotRange"] = from_python([x_range, y_range]) options["System`Discrete"] = True diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 35bbd85c9..a9c3b980d 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -1131,6 +1131,7 @@ def labels(names): graphics.extend(list(labels(chart_labels.elements))) y_range[0] = -0.4 # room for labels at the bottom + # FIXME: this can't be right... # always specify -.1 as the minimum x plot range, as this will make the y axis apppear # at origin (0,0); otherwise it will be shifted right; see GraphicsBox.axis_ticks(). x_range[0] = -0.1 diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index 4cd06ced3..09b29cc0d 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -1,7 +1,8 @@ """ Evaluation routines for 2D plotting. -Note this is distinct from formatting/rendering, e.g. to SVG.. +Note this is distinct from boxing and formatting/rendering, e.g. to SVG. +That is done as another pass after M-expression evaluation finishes. """ from math import cos, isinf, isnan, pi, sqrt from typing import Callable, Iterable, List, Optional, Union, Type @@ -136,18 +137,23 @@ def eval_ListPlot( if not isinstance(plot_points, list) or len(plot_points) == 0: return + # Classify the kind of data that "point" is, and + # canonicalize this into a list of lines. if all(not isinstance(point, (list, tuple)) for point in plot_points): - # Only y values given + # He have only y values given plot_points = [ [[float(i + 1), plot_points[i]] for i in range(len(plot_points))] ] elif all( isinstance(line, (list, tuple)) and len(line) == 2 for line in plot_points ): - # Single list of (x,y) pairs + # He have a single list of (x,y) pairs plot_points = [plot_points] elif all(isinstance(line, list) for line in plot_points): - # List of lines + if not all(isinstance(line, list) for line in plot_points): + return + + # He have a list of lines if all( isinstance(point, list) and len(point) == 2 for line in plot_points @@ -158,10 +164,6 @@ def eval_ListPlot( plot_points = [ [[float(i + 1), l] for i, l in enumerate(line)] for line in plot_points ] - else: - return - else: - return # Split into segments at missing data plot_points = [[line] for line in plot_points] @@ -204,7 +206,11 @@ def eval_ListPlot( hue_pos = 0.236068 hue_neg = -0.763932 + # List of graphics primitves that rendering will use to draw. + # This includes the plot data, and overall graphics directives + # like the Hue. graphics = [] + for indx, line in enumerate(plot_points): graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) for segment in line: @@ -315,7 +321,12 @@ def get_points_range(points): base_plot_points = [] # list of points in base subdivision plot_points = [] # list of all plotted points mesh_points = [] - graphics = [] # list of resulting graphics primitives + + # List of graphics primitives that rendering will use to draw. + # This includes the plot data, and overall graphics directives + # like the Hue. + graphics = [] + for index, f in enumerate(functions): points = [] xvalues = [] # x value for each point in points From a54f193790701856e876e717694ee62942e00c96 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 17:19:22 -0500 Subject: [PATCH 022/121] Adjust module comment along the lines mmatera. --- mathics/eval/plot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index 09b29cc0d..4d7b0d937 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -1,9 +1,10 @@ """ Evaluation routines for 2D plotting. -Note this is distinct from boxing and formatting/rendering, e.g. to SVG. -That is done as another pass after M-expression evaluation finishes. +These routines build Mathics Expressions that describe plots as Graphics in WL. +Note that this is distinct from boxing, formatting and rendering e.g. to SVG. """ + from math import cos, isinf, isnan, pi, sqrt from typing import Callable, Iterable, List, Optional, Union, Type From d7471ddbd421542ec98533e9dc9739408a503534 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 17:37:47 -0500 Subject: [PATCH 023/121] 2.5 is a better match for PrimePi fitting --- mathics/builtin/drawing/discrete_plot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/drawing/discrete_plot.py b/mathics/builtin/drawing/discrete_plot.py index bf9bf6d3d..4abbf5d9b 100644 --- a/mathics/builtin/drawing/discrete_plot.py +++ b/mathics/builtin/drawing/discrete_plot.py @@ -178,8 +178,8 @@ class DiscretePlot(_DiscretePlot): >> DiscretePlot[PrimePi[k], {k, 100}] = -Graphics- - is about the same as 'Sqrt[k] * 3': - >> DiscretePlot[3 Sqrt[k], {k, 100}] + is about the same as 'Sqrt[k] * 2.5': + >> DiscretePlot[2.5 Sqrt[k], {k, 100}] = -Graphics- >> DiscretePlot[Sin[x], {x, 0, 4 Pi}, DiscretePlotRange->{{0, 4 Pi}, {0, 1.5}}] From a7fecc359f88ae81c39bd560e5e4bb4bf3fb2ec3 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 18:54:37 -0500 Subject: [PATCH 024/121] Two small fixes/tweaks - * Plot was crashing when given a list of functions (tuple vs list problem) * Tweak module comment at the top of mathics.eval.plot --- mathics/builtin/drawing/plot.py | 2 +- mathics/eval/plot.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index a9c3b980d..dfb9c8ac6 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -1991,7 +1991,7 @@ class Plot(_Plot): def get_functions_param(self, functions): if functions.has_form("List", None): - functions = functions.elements + functions = list(functions.elements) else: functions = [functions] return functions diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index 4d7b0d937..24f4927f2 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -1,8 +1,9 @@ """ Evaluation routines for 2D plotting. -These routines build Mathics Expressions that describe plots as Graphics in WL. +These routines build Mathics M-Expressions that describe plots. Note that this is distinct from boxing, formatting and rendering e.g. to SVG. +That is done as another pass after M-expression evaluation finishes. """ from math import cos, isinf, isnan, pi, sqrt From e44f0642f5a4e527ee5b5b533003e5d7e7b46271 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 19:43:35 -0500 Subject: [PATCH 025/121] Mini (and incomplee) refactor of Plot... mathics.builtin.drawing.discrete_plot removed and PloDiscretePlot moved into Plot. This helps the doc structure, at the expense of making a large module larger. Common function and option processing for Plot and DiscretePlot separated. ListPlot origins now are (0, 0), more often. --- mathics/builtin/drawing/discrete_plot.py | 192 --------- mathics/builtin/drawing/plot.py | 518 +++++++++++++++-------- mathics/eval/plot.py | 8 + 3 files changed, 356 insertions(+), 362 deletions(-) delete mode 100644 mathics/builtin/drawing/discrete_plot.py diff --git a/mathics/builtin/drawing/discrete_plot.py b/mathics/builtin/drawing/discrete_plot.py deleted file mode 100644 index 4abbf5d9b..000000000 --- a/mathics/builtin/drawing/discrete_plot.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Discrete Plot -""" -import numbers - -from functools import lru_cache -from typing import Optional - -from mathics.builtin.drawing.plot import ( - Plot, - check_plot_range, - compile_quiet_function, - get_plot_range, -) -from mathics.builtin.graphics import Graphics -from mathics.builtin.options import options_to_rules - -from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED -from mathics.core.convert.python import from_python -from mathics.core.expression import Expression -from mathics.core.list import ListExpression -from mathics.core.symbols import Symbol, SymbolList -from mathics.eval.nevaluator import eval_N -from mathics.eval.plot import eval_ListPlot - - -class _DiscretePlot(Plot): - - attributes = A_HOLD_ALL | A_PROTECTED - - expect_list = False - - messages = { - "prng": ( - "Value of option PlotRange -> `1` is not All, Automatic or " - "an appropriate list of range specifications." - ), - "invexcl": ( - "Value of Exclusions -> `1` is not None, Automatic or an " - "appropriate list of constraints." - ), - } - - options = Graphics.options.copy() - options.update( - { - "Axes": "True", - "AspectRatio": "1 / GoldenRatio", - "PlotRange": "Automatic", - "$OptionSyntax": "Strict", - } - ) - - rules = { - # One argument-form of DiscretePlot - "DiscretePlot[expr_, {var_Symbol, nmax_Integer}]": "DiscretePlot[expr, {var, 1, nmax, 1}]", - # Two argument-form of DiscretePlot - "DiscretePlot[expr_, {var_Symbol, nmin_Integer, nmax_Integer}]": "DiscretePlot[expr, {var, nmin, nmax, 1}]", - } - - def eval(self, function, x, start, nmax, step, evaluation, options): - """DiscretePlot[function_, {x_Symbol, start_Integer, nmax_Integer, step_Integer}, - OptionsPattern[DiscretePlot]]""" - if isinstance(function, Symbol) and function.name is not x.get_name(): - rules = evaluation.definitions.get_ownvalues(function.name) - for rule in rules: - functions = rule.apply(function, evaluation, fully=True) - - if function.head is SymbolList: - functions_param = self.get_functions_param(functions) - for index, f in enumerate(functions_param): - if isinstance(f, Symbol) and f.name is not x.get_name(): - rules = evaluation.definitions.get_ownvalues(f.name) - for rule in rules: - f = rule.apply(f, evaluation, fully=True) - functions_param[index] = f - functions = functions.flatten_with_respect_to_head(SymbolList) - - expr_limits = ListExpression(x, start, nmax) - # FIXME: arrange for self to have a .symbolname property or attribute - expr = Expression( - Symbol(self.get_name()), function, expr_limits, *options_to_rules(options) - ) - function = self.get_functions_param(function) - x_name = x.get_name() - - py_start = start.value - py_nmax = nmax.value - py_step = step.value - if py_start is None or py_nmax is None: - return evaluation.message(self.get_name(), "plln", nmax, expr) - if py_start >= py_nmax: - return evaluation.message(self.get_name(), "plld", expr_limits) - - plotrange_option = self.get_option(options, "PlotRange", evaluation) - plot_range = eval_N(plotrange_option, evaluation).to_python() - - x_range, y_range = self.get_plotrange(plot_range, py_start, py_nmax) - if not check_plot_range(x_range, numbers.Real) or not check_plot_range( - y_range, numbers.Real - ): - evaluation.message(self.get_name(), "prng", plotrange_option) - x_range, y_range = [py_start, py_nmax], "Automatic" - - # x_range and y_range are now either Automatic, All, or of the form [min, max] - assert x_range in ("System`Automatic", "System`All") or isinstance( - x_range, list - ) - assert y_range in ("System`Automatic", "System`All") or isinstance( - y_range, list - ) - - # PlotPoints Option - # constants to generate colors - - base_plot_points = [] # list of points in base subdivision - plot_points = [] # list of all plotted points - - compiled_fn = compile_quiet_function( - function[0], [x_name], evaluation, self.expect_list - ) - - @lru_cache - def apply_fn(fn, x_value: int) -> Optional[float]: - value = fn(x_value) - if value is not None: - value = float(value) - return value - - for x_value in range(py_start, py_nmax, py_step): - point = apply_fn(compiled_fn, x_value) - plot_points.append((x_value, point)) - - x_range = get_plot_range( - [xx for xx, yy in base_plot_points], [xx for xx, yy in plot_points], x_range - ) - y_values = [yy for xx, yy in plot_points] - y_range = get_plot_range(y_values, y_values, option="System`All") - - # FIXME: For now we are going to specify that the min points are (-.1, -.1) - # or pretty close to (0, 0) for positive plots, so that the tick axes are set to zero. - # See GraphicsBox.axis_ticks(). - if x_range[0] > 0: - x_range = (-0.1, x_range[1]) - if y_range[0] > 0: - y_range = (-0.1, y_range[1]) - - options["System`PlotRange"] = from_python([x_range, y_range]) - options["System`Discrete"] = True - - return eval_ListPlot( - plot_points, - x_range, - y_range, - is_discrete_plot=True, - is_joined_plot=False, - filling=False, - options=options, - ) - - -class DiscretePlot(_DiscretePlot): - """ -
-
'DiscretePlot[$expr$, {$x$, $n_max$}]' -
plots $expr$ with $x$ ranging from 1 to $n_max$. - -
'DiscretePlot[$expr$, {$x$, $n_min$, $n_max$}]' -
plots $expr$ with $x$ ranging from $n_min$ to $n_max$. - -
'DiscretePlot[$f$, {$x$, $n_min$, $n_max$, $dn$}]' -
plots $f$ with $x$ ranging from $n_min$ to $n_max$ usings steps $dn$. - -
- - The number of primes for a number $k$: - >> DiscretePlot[PrimePi[k], {k, 100}] - = -Graphics- - - is about the same as 'Sqrt[k] * 2.5': - >> DiscretePlot[2.5 Sqrt[k], {k, 100}] - = -Graphics- - - >> DiscretePlot[Sin[x], {x, 0, 4 Pi}, DiscretePlotRange->{{0, 4 Pi}, {0, 1.5}}] - = -Graphics- - - >> DiscretePlot[Tan[x], {x, -6, 6}] - = -Graphics- - """ - - summary_text = "discrete plot of a one-paremeter function" diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index dfb9c8ac6..ccd73fdc4 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -8,12 +8,12 @@ import itertools import numbers +from functools import lru_cache from math import cos, pi, sin, sqrt +from typing import Optional import palettable -from functools import lru_cache - from mathics.builtin.base import Builtin from mathics.builtin.drawing.graphics3d import Graphics3D from mathics.builtin.graphics import Graphics @@ -269,7 +269,7 @@ def color_data_function(self, name): class _ListPlot(Builtin): """ - Computation part of ListPlot, ListLinePlot, and DiscretePlot + Base class for ListPlot, and ListLinePlot 2-Dimensional plot a list of points in some fashion. """ @@ -398,51 +398,18 @@ class _Plot(Builtin): def eval(self, functions, x, start, stop, evaluation, options): """%(name)s[functions_, {x_Symbol, start_, stop_}, OptionsPattern[%(name)s]]""" - if isinstance(functions, Symbol) and functions.name is not x.get_name(): - rules = evaluation.definitions.get_ownvalues(functions.name) - for rule in rules: - functions = rule.apply(functions, evaluation, fully=True) - - if functions.head is SymbolList: - functions_param = self.get_functions_param(functions) - for index, f in enumerate(functions_param): - if isinstance(f, Symbol) and f.name is not x.get_name(): - rules = evaluation.definitions.get_ownvalues(f.name) - for rule in rules: - f = rule.apply(f, evaluation, fully=True) - functions_param[index] = f - functions = functions.flatten_with_respect_to_head(SymbolList) - - expr_limits = ListExpression(x, start, stop) - # FIXME: arrange for self to have a .symbolname property or attribute - expr = Expression( - Symbol(self.get_name()), functions, expr_limits, *options_to_rules(options) - ) - functions = self.get_functions_param(functions) - x_name = x.get_name() - py_start = start.round_to_float(evaluation) - py_stop = stop.round_to_float(evaluation) - if py_start is None or py_stop is None: - return evaluation.message(self.get_name(), "plln", stop, expr) - if py_start >= py_stop: - return evaluation.message(self.get_name(), "plld", expr_limits) - - plotrange_option = self.get_option(options, "PlotRange", evaluation) - plot_range = eval_N(plotrange_option, evaluation).to_python() - x_range, y_range = self.get_plotrange(plot_range, py_start, py_stop) - if not check_plot_range(x_range, numbers.Real) or not check_plot_range( - y_range, numbers.Real - ): - evaluation.message(self.get_name(), "prng", plotrange_option) - x_range, y_range = [start, stop], "Automatic" - - # x_range and y_range are now either Automatic, All, or of the form [min, max] - assert x_range in ("System`Automatic", "System`All") or isinstance( - x_range, list - ) - assert y_range in ("System`Automatic", "System`All") or isinstance( - y_range, list + ( + functions, + x_name, + py_start, + py_stop, + x_range, + y_range, + _, + _, + ) = self.process_function_and_options( + functions, x, start, stop, evaluation, options ) # Mesh Option @@ -535,6 +502,84 @@ def check_exclusion(excl): evaluation, ) + def get_functions_param(self, functions): + if functions.has_form("List", None): + functions = list(functions.elements) + else: + functions = [functions] + return functions + + def get_plotrange(self, plotrange, start, stop): + x_range = y_range = None + if isinstance(plotrange, numbers.Real): + plotrange = ["System`Full", [-plotrange, plotrange]] + if plotrange == "System`Automatic": + plotrange = ["System`Full", "System`Automatic"] + elif plotrange == "System`All": + plotrange = ["System`All", "System`All"] + if isinstance(plotrange, list) and len(plotrange) == 2: + if isinstance(plotrange[0], numbers.Real) and isinstance( # noqa + plotrange[1], numbers.Real + ): + x_range, y_range = "System`Full", plotrange + else: + x_range, y_range = plotrange + if x_range == "System`Full": + x_range = [start, stop] + return x_range, y_range + + def process_function_and_options( + self, functions, x, start, stop, evaluation, options + ) -> tuple: + + if isinstance(functions, Symbol) and functions.name is not x.get_name(): + rules = evaluation.definitions.get_ownvalues(functions.name) + for rule in rules: + functions = rule.apply(functions, evaluation, fully=True) + + if functions.head is SymbolList: + functions_param = self.get_functions_param(functions) + for index, f in enumerate(functions_param): + if isinstance(f, Symbol) and f.name is not x.get_name(): + rules = evaluation.definitions.get_ownvalues(f.name) + for rule in rules: + f = rule.apply(f, evaluation, fully=True) + functions_param[index] = f + functions = functions.flatten_with_respect_to_head(SymbolList) + + expr_limits = ListExpression(x, start, stop) + # FIXME: arrange for self to have a .symbolname property or attribute + expr = Expression( + Symbol(self.get_name()), functions, expr_limits, *options_to_rules(options) + ) + functions = self.get_functions_param(functions) + x_name = x.get_name() + + py_start = start.round_to_float(evaluation) + py_stop = stop.round_to_float(evaluation) + if py_start is None or py_stop is None: + return evaluation.message(self.get_name(), "plln", stop, expr) + if py_start >= py_stop: + return evaluation.message(self.get_name(), "plld", expr_limits) + + plotrange_option = self.get_option(options, "PlotRange", evaluation) + plot_range = eval_N(plotrange_option, evaluation).to_python() + x_range, y_range = self.get_plotrange(plot_range, py_start, py_stop) + if not check_plot_range(x_range, numbers.Real) or not check_plot_range( + y_range, numbers.Real + ): + evaluation.message(self.get_name(), "prng", plotrange_option) + x_range, y_range = [start, stop], "Automatic" + + # x_range and y_range are now either Automatic, All, or of the form [min, max] + assert x_range in ("System`Automatic", "System`All") or isinstance( + x_range, list + ) + assert y_range in ("System`Automatic", "System`All") or isinstance( + y_range, list + ) + return functions, x_name, py_start, py_stop, x_range, y_range, expr_limits, expr + class _Plot3D(Builtin): messages = { @@ -1145,6 +1190,122 @@ def labels(names): ) +class ColorData(Builtin): + """ + :WMA link: https://reference.wolfram.com/language/ref/ColorData.html +
+
'ColorData["$name$"]' +
returns a color function with the given $name$. +
+ + Define a user-defined color function: + >> Unprotect[ColorData]; ColorData["test"] := ColorDataFunction["test", "Gradients", {0, 1}, Blend[{Red, Green, Blue}, #1] &]; Protect[ColorData] + + Compare it to the default color function, 'LakeColors': + >> {DensityPlot[x + y, {x, -1, 1}, {y, -1, 1}], DensityPlot[x + y, {x, -1, 1}, {y, -1, 1}, ColorFunction->"test"]} + = {-Graphics-, -Graphics-} + """ + + # rules = { + # 'ColorData["LakeColors"]': ( + # """ColorDataFunction["LakeColors", "Gradients", {0, 1}, + # Blend[{RGBColor[0.293416, 0.0574044, 0.529412], + # RGBColor[0.563821, 0.527565, 0.909499], + # RGBColor[0.762631, 0.846998, 0.914031], + # RGBColor[0.941176, 0.906538, 0.834043]}, #1] &]"""), + # } + summary_text = "named color gradients and collections" + messages = { + "notent": "`1` is not a known color scheme. ColorData[] gives you lists of schemes.", + } + + palettes = { + "LakeColors": _PredefinedGradient( + [ + (0.293416, 0.0574044, 0.529412), + (0.563821, 0.527565, 0.909499), + (0.762631, 0.846998, 0.914031), + (0.941176, 0.906538, 0.834043), + ] + ), + "Pastel": _PalettableGradient( + palettable.colorbrewer.qualitative.Pastel1_9, False + ), + "Rainbow": _PalettableGradient( + palettable.colorbrewer.diverging.Spectral_9, True + ), + "RedBlueTones": _PalettableGradient( + palettable.colorbrewer.diverging.RdBu_11, False + ), + "GreenPinkTones": _PalettableGradient( + palettable.colorbrewer.diverging.PiYG_9, False + ), + "GrayTones": _PalettableGradient( + palettable.colorbrewer.sequential.Greys_9, False + ), + "SolarColors": _PalettableGradient( + palettable.colorbrewer.sequential.OrRd_9, True + ), + "CherryTones": _PalettableGradient( + palettable.colorbrewer.sequential.Reds_9, True + ), + "FuchsiaTones": _PalettableGradient( + palettable.colorbrewer.sequential.RdPu_9, True + ), + "SiennaTones": _PalettableGradient( + palettable.colorbrewer.sequential.Oranges_9, True + ), + # specific to Mathics + "Paired": _PalettableGradient( + palettable.colorbrewer.qualitative.Paired_9, False + ), + "Accent": _PalettableGradient( + palettable.colorbrewer.qualitative.Accent_8, False + ), + "Aquatic": _PalettableGradient(palettable.wesanderson.Aquatic1_5, False), + "Zissou": _PalettableGradient(palettable.wesanderson.Zissou_5, False), + "Tableau": _PalettableGradient(palettable.tableau.Tableau_20, False), + "TrafficLight": _PalettableGradient(palettable.tableau.TrafficLight_9, False), + "Moonrise1": _PalettableGradient(palettable.wesanderson.Moonrise1_5, False), + } + + def eval_directory(self, evaluation): + "ColorData[]" + return ListExpression(String("Gradients")) + + def eval(self, name, evaluation): + "ColorData[name_String]" + py_name = name.get_string_value() + if py_name == "Gradients": + return ListExpression(*[String(name) for name in self.palettes.keys()]) + palette = ColorData.palettes.get(py_name, None) + if palette is None: + evaluation.message("ColorData", "notent", name) + return + return palette.color_data_function(py_name) + + @staticmethod + def colors(name, evaluation): + palette = ColorData.palettes.get(name, None) + if palette is None: + evaluation.message("ColorData", "notent", name) + return None + return palette.colors() + + +class ColorDataFunction(Builtin): + """ + :WMA link: https://reference.wolfram.com/language/ref/ColorDataFunction.html +
+
'ColorDataFunction[range, ...]' +
is a function that represents a color scheme. +
+ """ + + summary_text = "color scheme object" + pass + + class DensityPlot(_Plot3D): """ :WMA link: https://reference.wolfram.com/language/ref/DensityPlot.html @@ -1297,120 +1458,163 @@ def final_graphics(self, graphics, options): ) -class ColorData(Builtin): +class DiscretePlot(_Plot): """ - :WMA link: https://reference.wolfram.com/language/ref/ColorData.html
-
'ColorData["$name$"]' -
returns a color function with the given $name$. +
'DiscretePlot[$expr$, {$x$, $n_max$}]' +
plots $expr$ with $x$ ranging from 1 to $n_max$. + +
'DiscretePlot[$expr$, {$x$, $n_min$, $n_max$}]' +
plots $expr$ with $x$ ranging from $n_min$ to $n_max$. + +
'DiscretePlot[$f$, {$x$, $n_min$, $n_max$, $dn$}]' +
plots $f$ with $x$ ranging from $n_min$ to $n_max$ usings steps $dn$. +
- Define a user-defined color function: - >> Unprotect[ColorData]; ColorData["test"] := ColorDataFunction["test", "Gradients", {0, 1}, Blend[{Red, Green, Blue}, #1] &]; Protect[ColorData] + The number of primes for a number $k$: + >> DiscretePlot[PrimePi[k], {k, 100}] + = -Graphics- - Compare it to the default color function, 'LakeColors': - >> {DensityPlot[x + y, {x, -1, 1}, {y, -1, 1}], DensityPlot[x + y, {x, -1, 1}, {y, -1, 1}, ColorFunction->"test"]} - = {-Graphics-, -Graphics-} + is about the same as 'Sqrt[k] * 2.5': + >> DiscretePlot[2.5 Sqrt[k], {k, 100}] + = -Graphics- + + >> DiscretePlot[Sin[x], {x, 0, 4 Pi}, DiscretePlotRange->{{0, 4 Pi}, {0, 1.5}}] + = -Graphics- + + >> DiscretePlot[Tan[x], {x, -6, 6}] + = -Graphics- """ - # rules = { - # 'ColorData["LakeColors"]': ( - # """ColorDataFunction["LakeColors", "Gradients", {0, 1}, - # Blend[{RGBColor[0.293416, 0.0574044, 0.529412], - # RGBColor[0.563821, 0.527565, 0.909499], - # RGBColor[0.762631, 0.846998, 0.914031], - # RGBColor[0.941176, 0.906538, 0.834043]}, #1] &]"""), - # } - summary_text = "named color gradients and collections" - messages = { - "notent": "`1` is not a known color scheme. ColorData[] gives you lists of schemes.", - } + attributes = A_HOLD_ALL | A_PROTECTED - palettes = { - "LakeColors": _PredefinedGradient( - [ - (0.293416, 0.0574044, 0.529412), - (0.563821, 0.527565, 0.909499), - (0.762631, 0.846998, 0.914031), - (0.941176, 0.906538, 0.834043), - ] - ), - "Pastel": _PalettableGradient( - palettable.colorbrewer.qualitative.Pastel1_9, False - ), - "Rainbow": _PalettableGradient( - palettable.colorbrewer.diverging.Spectral_9, True - ), - "RedBlueTones": _PalettableGradient( - palettable.colorbrewer.diverging.RdBu_11, False - ), - "GreenPinkTones": _PalettableGradient( - palettable.colorbrewer.diverging.PiYG_9, False - ), - "GrayTones": _PalettableGradient( - palettable.colorbrewer.sequential.Greys_9, False - ), - "SolarColors": _PalettableGradient( - palettable.colorbrewer.sequential.OrRd_9, True - ), - "CherryTones": _PalettableGradient( - palettable.colorbrewer.sequential.Reds_9, True - ), - "FuchsiaTones": _PalettableGradient( - palettable.colorbrewer.sequential.RdPu_9, True - ), - "SiennaTones": _PalettableGradient( - palettable.colorbrewer.sequential.Oranges_9, True - ), - # specific to Mathics - "Paired": _PalettableGradient( - palettable.colorbrewer.qualitative.Paired_9, False + expect_list = False + + messages = { + "prng": ( + "Value of option PlotRange -> `1` is not All, Automatic or " + "an appropriate list of range specifications." ), - "Accent": _PalettableGradient( - palettable.colorbrewer.qualitative.Accent_8, False + "invexcl": ( + "Value of Exclusions -> `1` is not None, Automatic or an " + "appropriate list of constraints." ), - "Aquatic": _PalettableGradient(palettable.wesanderson.Aquatic1_5, False), - "Zissou": _PalettableGradient(palettable.wesanderson.Zissou_5, False), - "Tableau": _PalettableGradient(palettable.tableau.Tableau_20, False), - "TrafficLight": _PalettableGradient(palettable.tableau.TrafficLight_9, False), - "Moonrise1": _PalettableGradient(palettable.wesanderson.Moonrise1_5, False), } - def eval_directory(self, evaluation): - "ColorData[]" - return ListExpression(String("Gradients")) + options = Graphics.options.copy() + options.update( + { + "Axes": "True", + "AspectRatio": "1 / GoldenRatio", + "PlotRange": "Automatic", + "$OptionSyntax": "Strict", + } + ) - def eval(self, name, evaluation): - "ColorData[name_String]" - py_name = name.get_string_value() - if py_name == "Gradients": - return ListExpression(*[String(name) for name in self.palettes.keys()]) - palette = ColorData.palettes.get(py_name, None) - if palette is None: - evaluation.message("ColorData", "notent", name) - return - return palette.color_data_function(py_name) + rules = { + # One argument-form of DiscretePlot + "DiscretePlot[expr_, {var_Symbol, nmax_Integer}]": "DiscretePlot[expr, {var, 1, nmax, 1}]", + # Two argument-form of DiscretePlot + "DiscretePlot[expr_, {var_Symbol, nmin_Integer, nmax_Integer}]": "DiscretePlot[expr, {var, nmin, nmax, 1}]", + } - @staticmethod - def colors(name, evaluation): - palette = ColorData.palettes.get(name, None) - if palette is None: - evaluation.message("ColorData", "notent", name) - return None - return palette.colors() + summary_text = "discrete plot of a one-paremeter function" + def eval(self, functions, x, start, nmax, step, evaluation, options): + """DiscretePlot[functions_, {x_Symbol, start_Integer, nmax_Integer, step_Integer}, + OptionsPattern[DiscretePlot]]""" -class ColorDataFunction(Builtin): - """ - :WMA link: https://reference.wolfram.com/language/ref/ColorDataFunction.html -
-
'ColorDataFunction[range, ...]' -
is a function that represents a color scheme. -
- """ + ( + functions, + x_name, + py_start, + py_stop, + x_range, + y_range, + expr_limits, + expr, + ) = self.process_function_and_options( + functions, x, start, nmax, evaluation, options + ) - summary_text = "color scheme object" - pass + py_start = start.value + py_nmax = nmax.value + py_step = step.value + if py_start is None or py_nmax is None: + return evaluation.message(self.get_name(), "plln", nmax, expr) + if py_start >= py_nmax: + return evaluation.message(self.get_name(), "plld", expr_limits) + + plotrange_option = self.get_option(options, "PlotRange", evaluation) + plot_range = eval_N(plotrange_option, evaluation).to_python() + + x_range, y_range = self.get_plotrange(plot_range, py_start, py_nmax) + if not check_plot_range(x_range, numbers.Real) or not check_plot_range( + y_range, numbers.Real + ): + evaluation.message(self.get_name(), "prng", plotrange_option) + x_range, y_range = [py_start, py_nmax], "Automatic" + + # x_range and y_range are now either Automatic, All, or of the form [min, max] + assert x_range in ("System`Automatic", "System`All") or isinstance( + x_range, list + ) + assert y_range in ("System`Automatic", "System`All") or isinstance( + y_range, list + ) + + # PlotPoints Option + # constants to generate colors + base_plot_points = [] # list of points in base subdivision + plot_points = [] # list of all plotted points + + for index, f in enumerate(functions): + + compiled_fn = compile_quiet_function( + f, [x_name], evaluation, self.expect_list + ) + + @lru_cache + def apply_fn(fn, x_value: int) -> Optional[float]: + value = fn(x_value) + if value is not None: + value = float(value) + return value + + for x_value in range(py_start, py_nmax, py_step): + point = apply_fn(compiled_fn, x_value) + plot_points.append((x_value, point)) + + x_range = get_plot_range( + [xx for xx, yy in base_plot_points], + [xx for xx, yy in plot_points], + x_range, + ) + + y_values = [yy for xx, yy in plot_points] + y_range = get_plot_range(y_values, y_values, option="System`All") + + # FIXME: For now we are going to specify that the min points are (-.1, -.1) + # or pretty close to (0, 0) for positive plots, so that the tick axes are set to zero. + # See GraphicsBox.axis_ticks(). + if x_range[0] > 0: + x_range = (-0.1, x_range[1]) + if y_range[0] > 0: + y_range = (-0.1, y_range[1]) + + options["System`PlotRange"] = from_python([x_range, y_range]) + options["System`Discrete"] = True + + return eval_ListPlot( + plot_points, + x_range, + y_range, + is_discrete_plot=True, + is_joined_plot=False, + filling=False, + options=options, + ) class Histogram(Builtin): @@ -1989,32 +2193,6 @@ class Plot(_Plot): summary_text = "curves of one or more functions" - def get_functions_param(self, functions): - if functions.has_form("List", None): - functions = list(functions.elements) - else: - functions = [functions] - return functions - - def get_plotrange(self, plotrange, start, stop): - x_range = y_range = None - if isinstance(plotrange, numbers.Real): - plotrange = ["System`Full", [-plotrange, plotrange]] - if plotrange == "System`Automatic": - plotrange = ["System`Full", "System`Automatic"] - elif plotrange == "System`All": - plotrange = ["System`All", "System`All"] - if isinstance(plotrange, list) and len(plotrange) == 2: - if isinstance(plotrange[0], numbers.Real) and isinstance( # noqa - plotrange[1], numbers.Real - ): - x_range, y_range = "System`Full", plotrange - else: - x_range, y_range = plotrange - if x_range == "System`Full": - x_range = [start, stop] - return x_range, y_range - @lru_cache def _apply_fn(self, f, x_value): value = f(x_value) diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index 24f4927f2..f63c98851 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -196,6 +196,14 @@ def eval_ListPlot( x_range, ) + # FIXME: For now we are going to specify that the min points are (-.1, -.1) + # or pretty close to (0, 0) for positive plots, so that the tick axes are set to zero. + # See GraphicsBox.axis_ticks(). + if x_range[0] > 0: + x_range = (-0.1, x_range[1]) + if y_range[0] > 0: + y_range = (-0.1, y_range[1]) + if filling == "System`Axis": # TODO: Handle arbitary axis intercepts filling = 0.0 From 493fbf27bd2313916a2a85a885c82555f1b23013 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 19:51:32 -0500 Subject: [PATCH 026/121] Can we simplify list detection? --- mathics/builtin/drawing/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index ccd73fdc4..f7be6e549 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -503,7 +503,7 @@ def check_exclusion(excl): ) def get_functions_param(self, functions): - if functions.has_form("List", None): + if functions.head is SymbolList: functions = list(functions.elements) else: functions = [functions] From a4fb1db9b4128149ecd9980d5b55c0e933298d04 Mon Sep 17 00:00:00 2001 From: rocky Date: Thu, 15 Dec 2022 20:10:18 -0500 Subject: [PATCH 027/121] Remove incorrect DiscretePlot example --- mathics/builtin/drawing/plot.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index f7be6e549..4cfa779ab 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -1480,9 +1480,6 @@ class DiscretePlot(_Plot): >> DiscretePlot[2.5 Sqrt[k], {k, 100}] = -Graphics- - >> DiscretePlot[Sin[x], {x, 0, 4 Pi}, DiscretePlotRange->{{0, 4 Pi}, {0, 1.5}}] - = -Graphics- - >> DiscretePlot[Tan[x], {x, -6, 6}] = -Graphics- """ From 9967ea6755c5a515c0d3f69a4357c275d50bd92b Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 16 Dec 2022 02:55:38 -0500 Subject: [PATCH 028/121] Another pass... drawing/plot.py: * ListPlot examples. More type annotationsn eval/plot.py: * Color Hues for DiscretePlot, * fix some bugs in axis ranges, and caused by morphing plot data * vague or misleading "line" -> "plot_group" * start docstring for eval_ListPlot --- mathics/builtin/drawing/plot.py | 89 +++++++++++++++++++------- mathics/eval/plot.py | 109 +++++++++++++++++++------------- 2 files changed, 132 insertions(+), 66 deletions(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 4cfa779ab..e4f48e481 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -22,6 +22,7 @@ from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.convert.python import from_python +from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolList, SymbolTrue @@ -75,7 +76,9 @@ def check_plot_range(range, range_type) -> bool: return False -def gradient_palette(color_function, n, evaluation): # always returns RGB values +def gradient_palette( + color_function, n, evaluation: Evaluation +): # always returns RGB values if isinstance(color_function, String): color_data = Expression(SymbolColorData, color_function).evaluate(evaluation) if not color_data.has_form("ColorDataFunction", 4): @@ -122,10 +125,10 @@ class _Chart(Builtin): } ) - def _draw(self, data, color, evaluation, options): + def _draw(self, data, color, evaluation: Evaluation, options: dict): raise NotImplementedError() - def eval(self, points, evaluation, options): + def eval(self, points, evaluation: Evaluation, options: dict): "%(name)s[points_, OptionsPattern[%(name)s]]" points = points.evaluate(evaluation) @@ -281,7 +284,7 @@ class _ListPlot(Builtin): "joind": "Value of option Joined -> `1` is not True or False.", } - def eval(self, points, evaluation, options): + def eval(self, points, evaluation: Evaluation, options: dict): "%(name)s[points_, OptionsPattern[%(name)s]]" plot_name = self.get_name() @@ -395,7 +398,7 @@ class _Plot(Builtin): } ) - def eval(self, functions, x, start, stop, evaluation, options): + def eval(self, functions, x, start, stop, evaluation: Evaluation, options: dict): """%(name)s[functions_, {x_Symbol, start_, stop_}, OptionsPattern[%(name)s]]""" @@ -529,7 +532,7 @@ def get_plotrange(self, plotrange, start, stop): return x_range, y_range def process_function_and_options( - self, functions, x, start, stop, evaluation, options + self, functions, x, start, stop, evaluation: Evaluation, options: dict ) -> tuple: if isinstance(functions, Symbol) and functions.name is not x.get_name(): @@ -598,7 +601,18 @@ class _Plot3D(Builtin): ), } - def eval(self, functions, x, xstart, xstop, y, ystart, ystop, evaluation, options): + def eval( + self, + functions, + x, + xstart, + xstop, + y, + ystart, + ystop, + evaluation: Evaluation, + options: dict, + ): """%(name)s[functions_, {x_Symbol, xstart_, xstop_}, {y_Symbol, ystart_, ystop_}, OptionsPattern[%(name)s]]""" xexpr_limits = ListExpression(x, xstart, xstop) @@ -1269,11 +1283,11 @@ class ColorData(Builtin): "Moonrise1": _PalettableGradient(palettable.wesanderson.Moonrise1_5, False), } - def eval_directory(self, evaluation): + def eval_directory(self, evaluation: Evaluation): "ColorData[]" return ListExpression(String("Gradients")) - def eval(self, name, evaluation): + def eval(self, name, evaluation: Evaluation): "ColorData[name_String]" py_name = name.get_string_value() if py_name == "Gradients": @@ -1460,6 +1474,7 @@ def final_graphics(self, graphics, options): class DiscretePlot(_Plot): """ + :WMA link: https://reference.wolfram.com/language/ref/DiscretePlot.html
'DiscretePlot[$expr$, {$x$, $n_max$}]'
plots $expr$ with $x$ ranging from 1 to $n_max$. @@ -1467,9 +1482,11 @@ class DiscretePlot(_Plot):
'DiscretePlot[$expr$, {$x$, $n_min$, $n_max$}]'
plots $expr$ with $x$ ranging from $n_min$ to $n_max$. -
'DiscretePlot[$f$, {$x$, $n_min$, $n_max$, $dn$}]' -
plots $f$ with $x$ ranging from $n_min$ to $n_max$ usings steps $dn$. +
'DiscretePlot[$expr$, {$x$, $n_min$, $n_max$, $dn$}]' +
plots $expr$ with $x$ ranging from $n_min$ to $n_max$ usings steps $dn$. +
'DiscretePlot[{$expr1$, $expr2$, ...}, ...]' +
plots the values of all $expri$.
The number of primes for a number $k$: @@ -1480,7 +1497,8 @@ class DiscretePlot(_Plot): >> DiscretePlot[2.5 Sqrt[k], {k, 100}] = -Graphics- - >> DiscretePlot[Tan[x], {x, -6, 6}] + A plot can contain several functions, using the same parameter, here $x$: + >> DiscretePlot[{Sin[Pi x/20], Cos[Pi x/20]}, {x, 1, 40}] = -Graphics- """ @@ -1518,7 +1536,9 @@ class DiscretePlot(_Plot): summary_text = "discrete plot of a one-paremeter function" - def eval(self, functions, x, start, nmax, step, evaluation, options): + def eval( + self, functions, x, start, nmax, step, evaluation: Evaluation, options: dict + ): """DiscretePlot[functions_, {x_Symbol, start_Integer, nmax_Integer, step_Integer}, OptionsPattern[DiscretePlot]]""" @@ -1561,12 +1581,19 @@ def eval(self, functions, x, start, nmax, step, evaluation, options): y_range, list ) - # PlotPoints Option - # constants to generate colors base_plot_points = [] # list of points in base subdivision plot_points = [] # list of all plotted points + # list of all plotted points across all functions + plot_groups = [] + + # List of graphics primitves that rendering will use to draw. + # This includes the plot data, and overall graphics directives + # like the Hue. + for index, f in enumerate(functions): + # list of all plotted points for a given function + plot_points = [] compiled_fn = compile_quiet_function( f, [x_name], evaluation, self.expect_list @@ -1588,6 +1615,7 @@ def apply_fn(fn, x_value: int) -> Optional[float]: [xx for xx, yy in plot_points], x_range, ) + plot_groups.append(plot_points) y_values = [yy for xx, yy in plot_points] y_range = get_plot_range(y_values, y_values, option="System`All") @@ -1604,7 +1632,7 @@ def apply_fn(fn, x_value: int) -> Optional[float]: options["System`Discrete"] = True return eval_ListPlot( - plot_points, + plot_groups, x_range, y_range, is_discrete_plot=True, @@ -1644,7 +1672,7 @@ class Histogram(Builtin): ) summary_text = "draw a histogram" - def eval(self, points, spec, evaluation, options): + def eval(self, points, spec, evaluation: Evaluation, options: dict): "%(name)s[points_, spec___, OptionsPattern[%(name)s]]" points = points.evaluate(evaluation) @@ -1885,10 +1913,27 @@ class ListPlot(_ListPlot):
plots several lists of points.
- ListPlot accepts a superset of the Graphics options. + The frequecy of Primes: + >> ListPlot[Prime[Range[30]]] + = -Graphics- + + seems very roughly to fit a table of quadradic numbers: + >> ListPlot[Table[n ^ 2 / 8, {n, 30}]] + = -Graphics- + + ListPlot accepts some Graphics options: + + >> ListPlot[Table[n ^ 2, {n, 30}], Joined->True] + = -Graphics- + + Compare with :'Plot': + /doc/reference-of-built-in-symbols/graphics-drawing-and-images/plotting-data/plot/. - >> ListPlot[Table[n ^ 2, {n, 10}]] + >> ListPlot[Table[n ^ 2, {n, 30}], Filling->Axis] = -Graphics- + + Compare with :'ListPlot': + /doc/reference-of-built-in-symbols/graphics-drawing-and-images/plotting-data/listplot. """ attributes = A_HOLD_ALL | A_PROTECTED @@ -2013,7 +2058,7 @@ class PieChart(_Chart): summary_text = "draw a pie chart" - def _draw(self, data, color, evaluation, options): + def _draw(self, data, color, evaluation, options: dict): data = [[max(0.0, x) for x in group] for group in data] sector_origin = self.get_option(options, "SectorOrigin", evaluation) @@ -2426,7 +2471,7 @@ def get_functions_param(self, functions): return [functions] def construct_graphics( - self, triangles, mesh_points, v_min, v_max, options, evaluation + self, triangles, mesh_points, v_min, v_max, options, evaluation: Evaluation ): graphics = [] for p1, p2, p3 in triangles: @@ -2454,7 +2499,7 @@ def construct_graphics( graphics.append(Expression(SymbolLine, ListExpression(*line))) return graphics - def final_graphics(self, graphics, options): + def final_graphics(self, graphics, options: dict): return Expression( SymbolGraphics3D, ListExpression(*graphics), diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index f63c98851..4b17fb5a1 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -128,102 +128,123 @@ def quiet_f(*args): def eval_ListPlot( - plot_points: list, - x_range: int, - y_range: int, + plot_groups: list, + x_range: list, + y_range: list, is_discrete_plot: bool, is_joined_plot: bool, filling, options: dict, ): - if not isinstance(plot_points, list) or len(plot_points) == 0: + """ + Evaluation part of LisPlot[] and DiscretePlot[] + + plot_groups: the plot point data, It can be in a number of different list formats + x_range: the x range that of the area to show in the plot + y_range: the y range that of the area to show in the plot + is_discrete_plot: True if called from DiscretePlot, False if called from ListPlot + is_joined_plot: True if points are to be joined. This never happens in a discrete plot + options: miscellaneous graphics options from underlying M-Expression + """ + + if not isinstance(plot_groups, list) or len(plot_groups) == 0: return # Classify the kind of data that "point" is, and # canonicalize this into a list of lines. - if all(not isinstance(point, (list, tuple)) for point in plot_points): + if all(not isinstance(point, (list, tuple)) for point in plot_groups): # He have only y values given - plot_points = [ - [[float(i + 1), plot_points[i]] for i in range(len(plot_points))] + y_min = min(plot_groups) + y_max = max(plot_groups) + x_min = 0 + x_max = len(plot_groups) + plot_groups = [ + [[float(i + 1), plot_groups[i]] for i in range(len(plot_groups))] ] elif all( - isinstance(line, (list, tuple)) and len(line) == 2 for line in plot_points + isinstance(plot_group, (list, tuple)) and len(plot_group) == 2 + for plot_group in plot_groups ): # He have a single list of (x,y) pairs - plot_points = [plot_points] - elif all(isinstance(line, list) for line in plot_points): - if not all(isinstance(line, list) for line in plot_points): + plot_groups = [plot_groups] + elif all(isinstance(line, list) for line in plot_groups): + if not all(isinstance(line, list) for line in plot_groups): return - # He have a list of lines + # He have a list of plot groups if all( isinstance(point, list) and len(point) == 2 - for line in plot_points - for point in line + for plot_group in plot_groups + for point in plot_groups ): pass - elif all(not isinstance(point, list) for line in plot_points for point in line): - plot_points = [ - [[float(i + 1), l] for i, l in enumerate(line)] for line in plot_points + elif not is_discrete_plot and all( + not isinstance(point, list) for line in plot_groups for point in line + ): + plot_groups = [ + [[float(i + 1), l] for i, l in enumerate(line)] for line in plot_groups ] - # Split into segments at missing data - plot_points = [[line] for line in plot_points] - for lidx, line in enumerate(plot_points): + # Split into plot segments + plot_groups = [[plot_group] for plot_group in plot_groups] + if isinstance(x_range, (list, tuple)): + x_min, m_max = x_range + y_min, y_max = y_range + + for lidx, plot_group in enumerate(plot_groups): i = 0 - while i < len(plot_points[lidx]): - seg = line[i] + while i < len(plot_groups[lidx]): + seg = plot_group[i] for j, point in enumerate(seg): + x_min = min(x_min, point[0]) + x_max = max(x_min, point[0]) + y_min = min(y_min, point[1]) + y_max = max(y_max, point[1]) if not ( isinstance(point[0], (int, float)) and isinstance(point[1], (int, float)) ): - plot_points[lidx].insert(i, seg[:j]) - plot_points[lidx][i + 1] = seg[j + 1 :] + plot_groups[lidx].insert(i, seg[:j]) + plot_groups[lidx][i + 1] = seg[j + 1 :] i -= 1 break i += 1 - y_range = get_plot_range( - [y for line in plot_points for seg in line for x, y in seg], - [y for line in plot_points for seg in line for x, y in seg], - y_range, - ) - x_range = get_plot_range( - [x for line in plot_points for seg in line for x, y in seg], - [x for line in plot_points for seg in line for x, y in seg], - x_range, - ) - # FIXME: For now we are going to specify that the min points are (-.1, -.1) # or pretty close to (0, 0) for positive plots, so that the tick axes are set to zero. # See GraphicsBox.axis_ticks(). - if x_range[0] > 0: - x_range = (-0.1, x_range[1]) - if y_range[0] > 0: - y_range = (-0.1, y_range[1]) + if x_min > 0: + x_min = -0.1 + if y_min > 0: + y_min = -0.1 + x_range = x_min, x_max + y_range = y_min, y_max + + is_axis_filling = is_discrete_plot if filling == "System`Axis": # TODO: Handle arbitary axis intercepts filling = 0.0 + is_axis_filling = True elif filling == "System`Bottom": filling = y_range[0] elif filling == "System`Top": filling = y_range[1] + # constants to generate colors for a plot group hue = 0.67 hue_pos = 0.236068 hue_neg = -0.763932 - # List of graphics primitves that rendering will use to draw. + # List of graphics primitives that rendering will use to draw. # This includes the plot data, and overall graphics directives # like the Hue. graphics = [] - for indx, line in enumerate(plot_points): + for index, plot_group in enumerate(plot_groups): graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) - for segment in line: + for segment in plot_group: mathics_segment = from_python(segment) if is_joined_plot: graphics.append(Expression(SymbolLine, mathics_segment)) @@ -237,7 +258,7 @@ def eval_ListPlot( fill_area.append([segment[-1][0], filling]) fill_area.append([segment[0][0], filling]) graphics.append(Expression(SymbolPolygon, from_python(fill_area))) - elif is_discrete_plot: + elif is_axis_filling: graphics.append(Expression(SymbolPoint, mathics_segment)) for mathics_point in mathics_segment: graphics.append( @@ -262,7 +283,7 @@ def eval_ListPlot( ) ) - if indx % 4 == 0: + if index % 4 == 0: hue += hue_pos else: hue += hue_neg From ddf8f4730c1402fb372e9983c1c7405dbe62f34b Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 16 Dec 2022 07:35:15 -0500 Subject: [PATCH 029/121] More crash avoidance for ListPlot... some of this isn't right though. Will come back to this. --- mathics/eval/plot.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index 4b17fb5a1..d123cfc58 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -165,7 +165,19 @@ def eval_ListPlot( isinstance(plot_group, (list, tuple)) and len(plot_group) == 2 for plot_group in plot_groups ): - # He have a single list of (x,y) pairs + # He have a single list of (x,y) pairs. + + # FIXME: is this right? + x_range = get_plot_range( + [xx for xx, yy in plot_groups], [xx for xx, yy in plot_groups], x_range + ) + y_range = get_plot_range( + [yy for xx, yy in plot_groups], [yy for xx, yy in plot_groups], y_range + ) + + get_plot_range( + [xx for xx, yy in plot_groups], [xx for xx, yy in plot_groups], x_range + ) plot_groups = [plot_groups] elif all(isinstance(line, list) for line in plot_groups): if not all(isinstance(line, list) for line in plot_groups): @@ -181,8 +193,15 @@ def eval_ListPlot( elif not is_discrete_plot and all( not isinstance(point, list) for line in plot_groups for point in line ): + # FIXME: is this right? + y_min = min(plot_groups)[0] + y_max = max(plot_groups)[0] + x_min = 0 + x_max = len(plot_groups) + plot_groups = [ - [[float(i + 1), l] for i, l in enumerate(line)] for line in plot_groups + [[float(i + 1), l] for i, l in enumerate(plot_group)] + for plot_group in plot_groups ] # Split into plot segments From 19e5c4baa347d6359d92183ee99a44b2706165c7 Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 16 Dec 2022 07:45:21 -0500 Subject: [PATCH 030/121] Clearer ListLinePlot example --- mathics/builtin/drawing/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index e4f48e481..08f0143d5 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -1972,7 +1972,7 @@ class ListLinePlot(_ListPlot): >> ListLinePlot[Table[{n, n ^ 0.5}, {n, 10}]] = -Graphics- - >> ListLinePlot[{{-2, -1}, {-1, -1}}] + >> ListLinePlot[{{-2, -1}, {-1, -1}, {1, 3}}] = -Graphics- """ From 542486bac6c0f9c668eb46d991f5bb802a5b719d Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 16 Dec 2022 14:21:18 -0500 Subject: [PATCH 031/121] 3.7 tolerance & ... 3.7- @lru_cache needs (). Refix a tuple access that I thought was fixed previously --- mathics/builtin/drawing/plot.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 08f0143d5..d4bfd9fde 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -1599,7 +1599,7 @@ def eval( f, [x_name], evaluation, self.expect_list ) - @lru_cache + @lru_cache() def apply_fn(fn, x_value: int) -> Optional[float]: value = fn(x_value) if value is not None: @@ -2235,7 +2235,7 @@ class Plot(_Plot): summary_text = "curves of one or more functions" - @lru_cache + @lru_cache() def _apply_fn(self, f, x_value): value = f(x_value) if value is not None: @@ -2281,7 +2281,7 @@ def get_functions_param(self, functions): functions = [functions] else: # Multiple Functions - functions = functions.elements + functions = list(functions.elements) return functions def get_plotrange(self, plotrange, start, stop): @@ -2302,7 +2302,7 @@ def get_plotrange(self, plotrange, start, stop): x_range, y_range = plotrange return x_range, y_range - @lru_cache + @lru_cache() def _apply_fn(self, fn, x_value): value = fn(x_value) if value is not None and len(value) == 2: @@ -2380,7 +2380,7 @@ def get_plotrange(self, plotrange, start, stop): x_range, y_range = plotrange return x_range, y_range - @lru_cache + @lru_cache() def _apply_fn(self, f, x_value): value = f(x_value) if value is not None: From a9232f1eadad5a9dc48e8054b3298779ec9ee302 Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 16 Dec 2022 16:50:56 -0500 Subject: [PATCH 032/121] Back off on use of Symbols - for now There is more that needs to be understood. I'll be back though. --- mathics/builtin/drawing/plot.py | 19 ++++++++++++------- mathics/core/symbols.py | 4 ---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index d4bfd9fde..45a87fe37 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -506,7 +506,7 @@ def check_exclusion(excl): ) def get_functions_param(self, functions): - if functions.head is SymbolList: + if functions.has_form("List", None): functions = list(functions.elements) else: functions = [functions] @@ -540,14 +540,19 @@ def process_function_and_options( for rule in rules: functions = rule.apply(functions, evaluation, fully=True) - if functions.head is SymbolList: + if functions.get_head() == SymbolList: functions_param = self.get_functions_param(functions) for index, f in enumerate(functions_param): if isinstance(f, Symbol) and f.name is not x.get_name(): rules = evaluation.definitions.get_ownvalues(f.name) for rule in rules: f = rule.apply(f, evaluation, fully=True) - functions_param[index] = f + try: + functions_param[index] = f + except: + from trepan.api import debug + + debug() functions = functions.flatten_with_respect_to_head(SymbolList) expr_limits = ListExpression(x, start, stop) @@ -1967,12 +1972,12 @@ class ListLinePlot(_ListPlot):
plots several lines.
- ListPlot accepts a superset of the Graphics options. - >> ListLinePlot[Table[{n, n ^ 0.5}, {n, 10}]] = -Graphics- - >> ListLinePlot[{{-2, -1}, {-1, -1}, {1, 3}}] + ListPlot accepts a superset of the Graphics options. + + >> ListLinePlot[{{-2, -1}, {-1, -1}, {1, 3}}, Filling->Axis] = -Graphics- """ @@ -2357,7 +2362,7 @@ class PolarPlot(_Plot): def get_functions_param(self, functions): if functions.has_form("List", None): - functions = functions.elements + functions = list(functions.elements) else: functions = [functions] return functions diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 45abc80e7..224698b7b 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -294,10 +294,6 @@ def has_form(self, heads, *element_counts) -> bool: else: return heads == name - @property - def head(self) -> str: - Symbol(self.class_head_name) - @property def is_literal(self) -> bool: """True if the value can't change and has a Python representation, From 4a9de7f891270de4af60f66cf751fb9aec9b4f82 Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 16 Dec 2022 17:08:33 -0500 Subject: [PATCH 033/121] Update CHANGES.rst --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8cf7d7a22..0cb6310e3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -51,6 +51,7 @@ Bugs # ``0`` with a given precision (like in ```0`3```) is now parsed as ``0``, an integer number. #. ``RandomSample`` with one list argument now returns a random ordering of the list items. Previously it would return just one item. +#. Origin placement corrected on ``ListPlot`` and ``LinePlot``. Enhancements From 7bbb9742455d3c565a88ba3dc5337dfa9ab39f2b Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 17 Dec 2022 13:43:41 -0500 Subject: [PATCH 034/121] Add rudimentary LogPlot and ListlogPlot Some Plot attributes were corrected. Some doc examples improved. --- CHANGES.rst | 2 + mathics/builtin/box/graphics.py | 29 +++++-- mathics/builtin/drawing/plot.py | 138 +++++++++++++++++++++++++++----- mathics/core/systemsymbols.py | 2 + mathics/eval/plot.py | 31 +++++-- 5 files changed, 171 insertions(+), 31 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0cb6310e3..8633b6887 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -17,6 +17,8 @@ New Builtins #. ``Curl`` (2-D and 3-D vector forms only) #. ``DiscretePlot`` #. ``Kurtosis`` +#. ``ListLogPlot`` +#. ``LogPlot`` #. ``PauliMatrix`` #. ``Remove`` #. ``SetOptions`` diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index 480a842eb..516ceeb11 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -702,6 +702,7 @@ def boxes_to_svg(self, elements=None, **options) -> str: return svg_body def create_axes(self, elements, graphics_options, xmin, xmax, ymin, ymax): + use_log_for_y_axis = graphics_options.get("System`LogPlot", False) axes = graphics_options.get("System`Axes") if axes is SymbolTrue: axes = (True, True) @@ -744,7 +745,17 @@ def add_element(element): for ( index, - (min, max, p_self0, p_other0, p_origin, ticks, ticks_small, ticks_int), + ( + min, + max, + p_self0, + p_other0, + p_origin, + ticks, + ticks_small, + ticks_int, + is_logscale, + ), ) in enumerate( [ ( @@ -756,6 +767,7 @@ def add_element(element): ticks_x, ticks_x_small, ticks_x_int, + False, ), ( ymin, @@ -766,6 +778,7 @@ def add_element(element): ticks_y, ticks_y_small, ticks_y_int, + use_log_for_y_axis, ), ] ): @@ -798,12 +811,18 @@ def add_element(element): ), ] ) + + tick_value = 10**x if is_logscale else x if ticks_int: - content = String(str(int(x))) - elif x == floor(x): - content = String("%.1f" % x) # e.g. 1.0 (instead of 1.) + content = String(str(int(tick_value))) + elif tick_value == floor(x): + content = String( + "%.1f" % tick_value + ) # e.g. 1.0 (instead of 1.) else: - content = String("%g" % x) # fix e.g. 0.6000000000000001 + content = String( + "%g" % tick_value + ) # fix e.g. 0.6000000000000001 add_element( InsetBox( elements, diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 45a87fe37..8fab951d0 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -10,7 +10,7 @@ import numbers from functools import lru_cache from math import cos, pi, sin, sqrt -from typing import Optional +from typing import Callable, Optional import palettable @@ -19,7 +19,7 @@ from mathics.builtin.graphics import Graphics from mathics.builtin.options import options_to_rules from mathics.core.atoms import Integer, Integer0, Integer1, MachineReal, Real, String -from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED +from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED, A_READ_PROTECTED from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.convert.python import from_python from mathics.core.evaluation import Evaluation @@ -34,6 +34,7 @@ SymbolGraphics, SymbolGraphics3D, SymbolGrid, + SymbolLog10, SymbolLine, SymbolMap, SymbolPolygon, @@ -276,6 +277,8 @@ class _ListPlot(Builtin): 2-Dimensional plot a list of points in some fashion. """ + attributes = A_PROTECTED | A_READ_PROTECTED + messages = { "prng": ( "Value of option PlotRange -> `1` is not All, Automatic or " @@ -284,10 +287,22 @@ class _ListPlot(Builtin): "joind": "Value of option Joined -> `1` is not True or False.", } + use_log_scale = False + def eval(self, points, evaluation: Evaluation, options: dict): "%(name)s[points_, OptionsPattern[%(name)s]]" plot_name = self.get_name() + + # Scale point values down by Log 10. Tick mark values will be adjusted to be 10^n in GraphicsBox. + if self.use_log_scale: + points = ListExpression( + *( + Expression(SymbolLog10, point).evaluate(evaluation) + for point in points + ) + ) + all_points = eval_N(points, evaluation).to_python() # FIXME: arrange for self to have a .symbolname property or attribute expr = Expression(Symbol(self.get_name()), points, *options_to_rules(options)) @@ -347,6 +362,7 @@ def eval(self, points, evaluation: Evaluation, options: dict): is_discrete_plot=False, is_joined_plot=is_joined_plot, filling=filling, + use_log_scale=self.use_log_scale, options=options, ) @@ -364,10 +380,13 @@ def colors(self): class _Plot(Builtin): - attributes = A_HOLD_ALL | A_PROTECTED + + attributes = A_HOLD_ALL | A_PROTECTED | A_READ_PROTECTED expect_list = False + use_log_scale = False + messages = { "invmaxrec": ( "MaxRecursion must be a non-negative integer; the recursion value " @@ -398,6 +417,12 @@ class _Plot(Builtin): } ) + @lru_cache() + def _apply_fn(self, f: Callable, x_value): + value = f(x_value) + if value is not None: + return (x_value, value) + def eval(self, functions, x, start, stop, evaluation: Evaluation, options: dict): """%(name)s[functions_, {x_Symbol, start_, stop_}, OptionsPattern[%(name)s]]""" @@ -488,6 +513,7 @@ def check_exclusion(excl): # exclusions is now either 'None' or a list of reals and 'Automatic' assert exclusions == "System`None" or isinstance(exclusions, list) + use_log_scale = self.use_log_scale return eval_Plot( functions, self._apply_fn, @@ -501,6 +527,7 @@ def check_exclusion(excl): self.expect_list, exclusions, maxrecursion, + use_log_scale, options, evaluation, ) @@ -547,12 +574,8 @@ def process_function_and_options( rules = evaluation.definitions.get_ownvalues(f.name) for rule in rules: f = rule.apply(f, evaluation, fully=True) - try: - functions_param[index] = f - except: - from trepan.api import debug + functions_param[index] = f - debug() functions = functions.flatten_with_respect_to_head(SymbolList) expr_limits = ListExpression(x, start, stop) @@ -712,7 +735,7 @@ def check_plotpoints(steps): f, [x.get_name(), y.get_name()], evaluation, False ) - def apply_fn(compiled_fn, x_value, y_value): + def apply_fn(compiled_fn: Callable, x_value, y_value): try: # Try to used cached value first return stored[(x_value, y_value)] @@ -1495,16 +1518,21 @@ class DiscretePlot(_Plot):
The number of primes for a number $k$: - >> DiscretePlot[PrimePi[k], {k, 100}] + >> DiscretePlot[PrimePi[k], {1, k, 100}] = -Graphics- is about the same as 'Sqrt[k] * 2.5': >> DiscretePlot[2.5 Sqrt[k], {k, 100}] = -Graphics- + Notice above, that when can omit the starting $n_min$ value when it is 1. + A plot can contain several functions, using the same parameter, here $x$: - >> DiscretePlot[{Sin[Pi x/20], Cos[Pi x/20]}, {x, 1, 40}] + >> DiscretePlot[{Sin[Pi x/20], Cos[Pi x/20]}, {x, 0, 40}] = -Graphics- + + Compare with :'Plot': + /doc/reference-of-built-in-symbols/graphics-drawing-and-images/plotting-data/plot/. """ attributes = A_HOLD_ALL | A_PROTECTED @@ -1605,7 +1633,7 @@ def eval( ) @lru_cache() - def apply_fn(fn, x_value: int) -> Optional[float]: + def apply_fn(fn: Callable, x_value: int) -> Optional[float]: value = fn(x_value) if value is not None: value = float(value) @@ -1937,12 +1965,10 @@ class ListPlot(_ListPlot): >> ListPlot[Table[n ^ 2, {n, 30}], Filling->Axis] = -Graphics- - Compare with :'ListPlot': - /doc/reference-of-built-in-symbols/graphics-drawing-and-images/plotting-data/listplot. + Compare with :'Plot': + /doc/reference-of-built-in-symbols/graphics-drawing-and-images/plotting-data/plot. """ - attributes = A_HOLD_ALL | A_PROTECTED - options = Graphics.options.copy() options.update( { @@ -1998,6 +2024,78 @@ class ListLinePlot(_ListPlot): summary_text = "plot lines through lists of points" +class ListLogPlot(_ListPlot): + """ + :WMA link: https://reference.wolfram.com/language/ref/ListLogPlot.html +
+
'ListLogPlot[{$y_1$, $y_2$, ...}]' +
log plots a list of y-values, assuming integer x-values 1, 2, 3, ... + +
'ListLogPlot[{{$x_1$, $y_1$}, {$x_2$, $y_2$}, ...}]' +
log plots a list of $x$, $y$ pairs. + +
'ListLogPlot[{$list_1$, $list_2$, ...}]' +
log plots several lists of points. +
+ + Plotting table of Fibonacci numbers: + >> ListLogPlot[Table[Fibonacci[n], {n, 10}]] + = -Graphics- + + we see that Fibonaccy numbers grow exponentially. So when \ + plotted using on a log scale the result fits \ + points of a sloped line. + + >> ListLogPlot[Table[n!, {n, 10}], Joined -> True] + = -Graphics- + """ + + options = Graphics.options.copy() + options.update( + { + "Axes": "True", + "AspectRatio": "1 / GoldenRatio", + "Mesh": "None", + "PlotRange": "Automatic", + "PlotPoints": "None", + "Filling": "None", + "Joined": "False", + } + ) + summary_text = "log plot lists of points" + + use_log_scale = True + + +class LogPlot(_Plot): + """ + :Semi-log plot: + https://en.wikipedia.org/wiki/Semi-log_plot \ + ( + :WMA link: + https://reference.wolfram.com/language/ref/LogPlot.html) +
+
'LogPlot[$f$, {$x$, $xmin$, $xmax$}]' +
log plots $f$ with $x$ ranging from $xmin$ to $xmax$. + +
'Plot[{$f1$, $f2$, ...}, {$x$, $xmin$, $xmax$}]' +
log plots several functions $f1$, $f2$, ... + +
+ + >> LogPlot[x^x, {x, 1, 5}] + = -Graphics- + + >> LogPlot[{x^x, Exp[x], x!}, {x, 1, 5}] + = -Graphics- + + """ + + summary_text = "plots on a log scale curves of one or more functions" + + use_log_scale = True + + class PieChart(_Chart): """ :Pie Chart: https://en.wikipedia.org/wiki/Pie_chart \ @@ -2241,7 +2339,7 @@ class Plot(_Plot): summary_text = "curves of one or more functions" @lru_cache() - def _apply_fn(self, f, x_value): + def _apply_fn(self, f: Callable, x_value): value = f(x_value) if value is not None: return (x_value, value) @@ -2308,7 +2406,7 @@ def get_plotrange(self, plotrange, start, stop): return x_range, y_range @lru_cache() - def _apply_fn(self, fn, x_value): + def _apply_fn(self, fn: Callable, x_value): value = fn(x_value) if value is not None and len(value) == 2: return value @@ -2386,8 +2484,8 @@ def get_plotrange(self, plotrange, start, stop): return x_range, y_range @lru_cache() - def _apply_fn(self, f, x_value): - value = f(x_value) + def _apply_fn(self, fn: Callable, x_value): + value = fn(x_value) if value is not None: return (value * cos(x_value), value * sin(x_value)) diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 3592eb616..07f2785bf 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -107,6 +107,8 @@ SymbolLessEqual = Symbol("System`LessEqual") SymbolLine = Symbol("System`Line") SymbolLog = Symbol("System`Log") +SymbolLog10 = Symbol("System`Log10") +SymbolLogPlot = Symbol("System`LogPlot") SymbolMachinePrecision = Symbol("System`MachinePrecision") SymbolMakeBoxes = Symbol("System`MakeBoxes") SymbolMap = Symbol("System`Map") diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index d123cfc58..2e841f803 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -9,7 +9,6 @@ from math import cos, isinf, isnan, pi, sqrt from typing import Callable, Iterable, List, Optional, Union, Type -from mathics.builtin.graphics import Graphics from mathics.builtin.numeric import chop from mathics.builtin.options import options_to_rules from mathics.builtin.scoping import dynamic_scoping @@ -20,11 +19,13 @@ from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import SymbolN, SymbolPower +from mathics.core.symbols import SymbolN, SymbolPower, SymbolTrue from mathics.core.systemsymbols import ( SymbolGraphics, SymbolHue, SymbolLine, + SymbolLog10, + SymbolLogPlot, SymbolMessageName, SymbolPoint, SymbolPolygon, @@ -134,6 +135,7 @@ def eval_ListPlot( is_discrete_plot: bool, is_joined_plot: bool, filling, + use_log_scale: bool, options: dict, ): """ @@ -290,7 +292,7 @@ def eval_ListPlot( ) ) else: - graphics.append(Expression(SymbolPoint, from_python(segment))) + graphics.append(Expression(SymbolPoint, mathics_segment)) if filling is not None: for point in segment: graphics.append( @@ -313,10 +315,11 @@ def eval_ListPlot( options["System`PlotRange"] = from_python([x_range, y_range]) + if use_log_scale: + options[SymbolLogPlot.name] = SymbolTrue + return Expression( - SymbolGraphics, - ListExpression(*graphics), - *options_to_rules(options, Graphics.options) + SymbolGraphics, ListExpression(*graphics), *options_to_rules(options) ) @@ -333,6 +336,7 @@ def eval_Plot( list_is_expected: bool, exclusions: list, max_recursion: int, + use_log_scale: bool, options: dict, evaluation: Evaluation, ) -> Expression: @@ -383,6 +387,10 @@ def get_points_range(points): tmp_mesh_points = [] # For this function only continuous = False d = (stop - start) / (num_plot_points - 1) + if use_log_scale: + # Scale point values down by Log 10. + # Tick mark values will be adjusted to be 10^n in GraphicsBox. + f = Expression(SymbolLog10, f) compiled_fn = compile_quiet_function(f, [x_name], evaluation, list_is_expected) for i in range(num_plot_points): x_value = start + i * d @@ -524,8 +532,19 @@ def find_excl(excl): [yy for xx, yy in base_plot_points], [yy for xx, yy in plot_points], y_range ) + # FIXME: For now we are going to specify that the min points are (-.1, -.1) + # or pretty close to (0, 0) for positive plots, so that the tick axes are set to zero. + # See GraphicsBox.axis_ticks(). + if x_range[0] > 0: + x_range = (-0.1, x_range[1]) + if y_range[0] > 0: + y_range = (-0.1, y_range[1]) + options["System`PlotRange"] = from_python([x_range, y_range]) + if use_log_scale: + options[SymbolLogPlot.name] = SymbolTrue + if mesh != "None": for hue, points in zip(function_hues, mesh_points): graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6)) From fc2d76e59eb52196b49c6a079b078c5329228a7c Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 17 Dec 2022 23:21:21 -0500 Subject: [PATCH 035/121] More robust checking of plot points... used in min/max range calculations --- mathics/eval/plot.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index 2e841f803..db0000574 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -155,9 +155,19 @@ def eval_ListPlot( # Classify the kind of data that "point" is, and # canonicalize this into a list of lines. if all(not isinstance(point, (list, tuple)) for point in plot_groups): - # He have only y values given - y_min = min(plot_groups) - y_max = max(plot_groups) + # We have only y values given. + + # Remove entries that are not float or int. + plot_groups = tuple(y for y in plot_groups if isinstance(y, (float, int))) + + if len(plot_groups) == 0: + # Plot groups is empty + y_min = 0 + y_max = 0 + else: + y_min = min(plot_groups) + y_max = max(plot_groups) + x_min = 0 x_max = len(plot_groups) plot_groups = [ From 1245750e44206418ecb5c2b2959613f590940656 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 17 Dec 2022 23:39:34 -0500 Subject: [PATCH 036/121] Fix a couple of bugs --- mathics/builtin/atomic/symbols.py | 2 +- mathics/builtin/drawing/plot.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index fe02ed14e..8a035de03 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/atomic/symbols.py @@ -606,7 +606,7 @@ class Names(Builtin): The wildcard '*' matches any character: >> Names["List*"] - = {List, ListLinePlot, ListPlot, ListQ, Listable} + = {List, ListLinePlot, ListLogPlot, ListPlot, ListQ, Listable} The wildcard '@' matches only lowercase characters: >> Names["List@"] diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 8fab951d0..1c163053f 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -1518,7 +1518,7 @@ class DiscretePlot(_Plot):
The number of primes for a number $k$: - >> DiscretePlot[PrimePi[k], {1, k, 100}] + >> DiscretePlot[PrimePi[k], {k, 1, 100}] = -Graphics- is about the same as 'Sqrt[k] * 2.5': @@ -1662,7 +1662,6 @@ def apply_fn(fn: Callable, x_value: int) -> Optional[float]: y_range = (-0.1, y_range[1]) options["System`PlotRange"] = from_python([x_range, y_range]) - options["System`Discrete"] = True return eval_ListPlot( plot_groups, @@ -1671,6 +1670,7 @@ def apply_fn(fn: Callable, x_value: int) -> Optional[float]: is_discrete_plot=True, is_joined_plot=False, filling=False, + use_log_scale=False, options=options, ) From ec46f8ec9dfb215cab85cfebc041005ffc6ed981 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 18 Dec 2022 09:53:49 -0500 Subject: [PATCH 037/121] Note one area of weakness Correct on doctest strings: - spelling on Fibonacci - grammar on the use of limit on DiscretePlot - summary text (start with active verb) for Graphics3D --- mathics/builtin/box/graphics.py | 2 ++ mathics/builtin/drawing/plot.py | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index 516ceeb11..bd586e1ad 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -812,6 +812,8 @@ def add_element(element): ] ) + # FIXME: for log plots we labels should appear + # as 10^x rather than say 1000000. tick_value = 10**x if is_logscale else x if ticks_int: content = String(str(int(tick_value))) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 1c163053f..b8fa79d0c 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -1525,7 +1525,8 @@ class DiscretePlot(_Plot): >> DiscretePlot[2.5 Sqrt[k], {k, 100}] = -Graphics- - Notice above, that when can omit the starting $n_min$ value when it is 1. + Notice in the above that when the starting value, $n_min$, is 1, we can \ + omit it. A plot can contain several functions, using the same parameter, here $x$: >> DiscretePlot[{Sin[Pi x/20], Cos[Pi x/20]}, {x, 0, 40}] @@ -2042,7 +2043,7 @@ class ListLogPlot(_ListPlot): >> ListLogPlot[Table[Fibonacci[n], {n, 10}]] = -Graphics- - we see that Fibonaccy numbers grow exponentially. So when \ + we see that Fibonacci numbers grow exponentially. So when \ plotted using on a log scale the result fits \ points of a sloped line. @@ -2565,7 +2566,7 @@ class Plot3D(_Plot3D): "MaxRecursion": "2", } ) - summary_text = "3D surfaces of one or more functions" + summary_text = "plots 3D surfaces of one or more functions" def get_functions_param(self, functions): if functions.has_form("List", None): From afd2c044e7b38d08a049ee83919d7aa0d459f64a Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 17 Dec 2022 07:06:47 -0500 Subject: [PATCH 038/121] Compute hash inside __new__ ... rather than do that in __hash__() each time that __hash()__ is called --- mathics/builtin/drawing/plot.py | 2 +- mathics/core/atoms.py | 351 +++++++------------------------- mathics/core/element.py | 7 - mathics/core/symbols.py | 2 +- 4 files changed, 77 insertions(+), 285 deletions(-) diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index b8fa79d0c..6254cf1fa 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -2337,7 +2337,7 @@ class Plot(_Plot): = -Graphics- """ - summary_text = "curves of one or more functions" + summary_text = "plot curves of one or more functions" @lru_cache() def _apply_fn(self, f: Callable, x_value): diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index bd3538350..3a721f18d 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -3,21 +3,15 @@ import base64 import math -import mpmath import re -import sympy - from functools import lru_cache from typing import Optional, Type, Union -from mathics.core.element import ImmutableValueMixin, BoxElementMixin -from mathics.core.number import ( - dps, - prec, - min_prec, - machine_digits, - machine_precision, -) +import mpmath +import sympy + +from mathics.core.element import BoxElementMixin, ImmutableValueMixin +from mathics.core.number import dps, machine_digits, machine_precision, min_prec, prec from mathics.core.symbols import ( Atom, NumericOperators, @@ -26,7 +20,7 @@ SymbolTrue, symbol_set, ) -from mathics.core.systemsymbols import SymbolInfinity, SymbolInputForm, SymbolFullForm +from mathics.core.systemsymbols import SymbolFullForm, SymbolInfinity, SymbolInputForm # Imperical number that seems to work. # We have to be able to match mpmath values with sympy values @@ -102,6 +96,10 @@ def __new__(cls, value) -> "Integer": n = int(value) self = super(Integer, cls).__new__(cls) self.value = n + + # Set a value for self.__hash__() once so that every time + # it is used this is fast. + self.hash = hash(("Integer", n)) return self def __eq__(self, other) -> bool: @@ -111,32 +109,35 @@ def __eq__(self, other) -> bool: else super().__eq__(other) ) - def __le__(self, other) -> bool: + def __ge__(self, other) -> bool: return ( - self.value <= other.value + self.value >= other.value if isinstance(other, Integer) - else super().__le__(other) + else super().__ge__(other) ) - def __lt__(self, other) -> bool: + def __gt__(self, other) -> bool: return ( - self.value < other.value + self.value > other.value if isinstance(other, Integer) - else super().__lt__(other) + else super().__gt__(other) ) - def __ge__(self, other) -> bool: + def __hash__(self): + return self.hash + + def __le__(self, other) -> bool: return ( - self.value >= other.value + self.value <= other.value if isinstance(other, Integer) - else super().__ge__(other) + else super().__le__(other) ) - def __gt__(self, other) -> bool: + def __lt__(self, other) -> bool: return ( - self.value > other.value + self.value < other.value if isinstance(other, Integer) - else super().__gt__(other) + else super().__lt__(other) ) def __ne__(self, other) -> bool: @@ -153,6 +154,20 @@ def abs(self) -> "Integer": def __init__(self, value): super().__init__() + 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) + + @property + def is_literal(self) -> bool: + """For an Integer, the value can't change and has a Python representation, + i.e. a value is set and it does not depend on definition + bindings. So we say it is a literal. + """ + return True + def make_boxes(self, form) -> "String": from mathics.eval.makeboxes import _boxed_string @@ -160,12 +175,6 @@ def make_boxes(self, form) -> "String": return _boxed_string(str(self.value), number_as_text=True) 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) @@ -201,9 +210,6 @@ def get_sort_key(self, pattern_sort=False) -> tuple: 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")) @@ -227,85 +233,6 @@ def is_zero(self) -> bool: IntegerM1 = Integer(-1) -class Rational(Number): - class_head_name = "System`Rational" - - # Think about: Do we ever need this on a __new__ since that does the same thing? - @lru_cache(maxsize=1024) - 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): - from mathics.eval.makeboxes import format_element - - return format_element(self, evaluation, f) - - 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) -> 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 default_format(self, evaluation, form) -> str: - return "Rational[%s, %s]" % self.value.as_numer_denom() - - def get_sort_key(self, pattern_sort=False) -> tuple: - 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) - - # This has to come before Complex class Real(Number): class_head_name = "System`Real" @@ -353,6 +280,11 @@ def __eq__(self, other) -> bool: else: return self.get_sort_key() == other.get_sort_key() + def __hash__(self): + # ignore last 7 binary digits when hashing + _prec = self.get_precision() + return hash(("Real", self.to_sympy().n(dps(_prec)))) + def __ne__(self, other) -> bool: # Real is a total order return not (self == other) @@ -368,11 +300,6 @@ def get_sort_key(self, pattern_sort=False) -> tuple: def is_nan(self, d=None) -> bool: return isinstance(self.value, sympy.core.numbers.NaN) - 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() @@ -579,8 +506,13 @@ def __new__(cls, value): self.value = base64.b64decode(value) else: raise Exception("value does not belongs to a valid type") + + self.hash = hash(("ByteArrayAtom", str(self.value))) return self + def __hash__(self): + return self.hash + def __str__(self) -> str: return base64.b64encode(self.value).decode("utf8") @@ -628,9 +560,6 @@ def to_sympy(self, **kwargs): 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. @@ -668,16 +597,23 @@ def __new__(cls, real, imag): self.real = real self.imag = imag + + # Set a value for self.__hash__() once so that every time + # it is used this is fast. + self.hash = hash(("Complex", real, imag)) return self + def __hash__(self): + return self.hash + + def __str__(self) -> str: + return str(self.to_sympy()) + def atom_to_boxes(self, f, evaluation): from mathics.eval.makeboxes import format_element return format_element(self, evaluation, f) - 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() @@ -744,9 +680,6 @@ def get_precision(self) -> Optional[float]: 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) @@ -783,158 +716,24 @@ def is_approx_zero(self) -> bool: return real_zero and imag_zero -class Integer(Number): - value: int - class_head_name = "System`Integer" - - # We use __new__ here to unsure that two Integer's that have the same value - # return the same object. - def __new__(cls, value) -> "Integer": - n = int(value) - self = super(Integer, cls).__new__(cls) - self.value = n - return self - - def __eq__(self, other) -> bool: - return ( - self.value == other.value - if isinstance(other, Integer) - else super().__eq__(other) - ) - - def __le__(self, other) -> bool: - return ( - self.value <= other.value - if isinstance(other, Integer) - else super().__le__(other) - ) - - def __lt__(self, other) -> bool: - return ( - self.value < other.value - if isinstance(other, Integer) - else super().__lt__(other) - ) - - def __ge__(self, other) -> bool: - return ( - self.value >= other.value - if isinstance(other, Integer) - else super().__ge__(other) - ) - - def __gt__(self, other) -> bool: - return ( - self.value > other.value - if isinstance(other, Integer) - else super().__gt__(other) - ) - - def __ne__(self, other) -> bool: - return ( - self.value != other.value - if isinstance(other, Integer) - else super().__ne__(other) - ) - - def abs(self) -> "Integer": - return -self if self < Integer0 else self - - @lru_cache() - def __init__(self, value): - super().__init__() - - 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) - - @property - def is_literal(self) -> bool: - """For an Integer, the value can't change and has a Python representation, - i.e. a value is set and it does not depend on definition - bindings. So we say it is a literal. - """ - return True - - def make_boxes(self, form) -> "String": - from mathics.eval.makeboxes import _boxed_string - - if form in ("System`InputForm", "System`FullForm"): - return _boxed_string(str(self.value), number_as_text=True) - return String(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) -> Union["MachineReal", "PrecisionReal"]: - if d is None: - d = self.value.bit_length() - if d <= machine_precision: - return MachineReal(float(self.value)) - else: - # machine_precision / log_2(10) + 1 - d = machine_digits - 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 get_sort_key(self, pattern_sort=False) -> tuple: - 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) -Integer2 = Integer(2) -Integer3 = Integer(3) -Integer310 = Integer(310) -Integer10 = Integer(10) -IntegerM1 = Integer(-1) - - class Rational(Number): class_head_name = "System`Rational" - @lru_cache() + # We use __new__ here to unsure that two Integer's that have the same value + # return the same object. + # Think about: Do we ever need this on a __new__ since that does the same thing? + @lru_cache(maxsize=1024) def __new__(cls, numerator, denominator=1) -> "Rational": self = super().__new__(cls) self.value = sympy.Rational(numerator, denominator) + # Set a value for self.__hash__() once so that every time + # it is used this is fast. + self.hash = hash(("Rational", self.value)) return self + def __hash__(self): + return self.hash + def atom_to_boxes(self, f, evaluation): from mathics.eval.makeboxes import format_element @@ -978,9 +777,6 @@ def get_sort_key(self, pattern_sort=False) -> tuple: 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") @@ -1012,8 +808,14 @@ def __new__(cls, value): self = super().__new__(cls) self.value = str(value) + # Set a value for self.__hash__() once so that every time + # it is used this is fast. + self.hash = hash(("String", self.value)) return self + def __hash__(self): + return self.hash + def __str__(self) -> str: return '"%s"' % self.value @@ -1066,9 +868,6 @@ def to_python(self, *args, **kwargs) -> str: 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. diff --git a/mathics/core/element.py b/mathics/core/element.py index 484f23f92..194ab7fc1 100644 --- a/mathics/core/element.py +++ b/mathics/core/element.py @@ -399,13 +399,6 @@ def has_form(self, heads, *element_counts): def is_zero(self) -> bool: return False - def __hash__(self): - """ - To allow usage of expression as dictionary keys, - as in Expression.get_pre_choices - """ - raise NotImplementedError - def is_free(self, form, evaluation) -> bool: """ Check if self has a subexpression of the form `form`. diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 224698b7b..c2a907b35 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -380,7 +380,7 @@ def __new__(cls, name: str, sympy_dummy=None, value=None): # Set a value for self.__hash__() once so that every time # it is used this is fast. # This tuple with "Symbol" is used to give a different hash - # than the hash that would be returned if just name were + # than the hash that would be returned if just string name were # used. self.hash = hash(("Symbol", name)) From 017f038a66fe79d8edb2ef1857ff1c8406607f26 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 18 Dec 2022 19:35:49 -0500 Subject: [PATCH 039/121] Correct singleton atoms and revise atom tests We were allocating singleton numbers/atoms like like Integer(1) more than once. Fix that. Also conver and revise test_atoms from a test-unit to a pytest. It now checks *all* symbols for dissimilarity from one another. It also starts a test for object canonicalization, e.g. Integer(1) and Complex(Integer(1), Integer(0)) are the same object. In a few places we have propagate constants for number other than Integer For example RationalOneHalf. --- mathics/builtin/arithfns/basic.py | 5 +- mathics/builtin/atomic/numbers.py | 3 +- mathics/core/atoms.py | 308 ++++++++++++++++++------------ mathics/core/convert/mpmath.py | 3 +- mathics/core/expression.py | 6 + mathics/core/list.py | 6 + mathics/core/symbols.py | 6 +- test/core/test_atoms.py | 291 +++++++++++++++++++--------- 8 files changed, 405 insertions(+), 223 deletions(-) diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index 265228729..f46860e06 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -30,6 +30,7 @@ IntegerM1, Number, Rational, + RationalOneHalf, Real, String, ) @@ -570,7 +571,7 @@ class Power(BinaryOperator, _MPMathFunction): Expression( SymbolPower, Expression(SymbolPattern, Symbol("x"), Expression(SymbolBlank)), - Rational(1, 2), + RationalOneHalf, ): "HoldForm[Sqrt[x]]", (("InputForm", "OutputForm"), "x_ ^ y_"): ( 'Infix[{HoldForm[x], HoldForm[y]}, "^", 590, Right]' @@ -618,7 +619,7 @@ def eval_check(self, x, y, evaluation): ) return SymbolComplexInfinity if isinstance(x, Complex) and x.real.is_zero: - yhalf = Expression(SymbolTimes, y, Rational(1, 2)) + yhalf = Expression(SymbolTimes, y, RationalOneHalf) factor = self.apply(Expression(SymbolSequence, x.imag, y), evaluation) return Expression( SymbolTimes, factor, Expression(SymbolPower, IntegerM1, yhalf) diff --git a/mathics/builtin/atomic/numbers.py b/mathics/builtin/atomic/numbers.py index 3280b53aa..aeb303256 100644 --- a/mathics/builtin/atomic/numbers.py +++ b/mathics/builtin/atomic/numbers.py @@ -25,6 +25,7 @@ Integer0, Integer10, MachineReal, + MachineReal0, Number, Rational, Real, @@ -1012,7 +1013,7 @@ def apply(self, z, evaluation): "Precision[z_]" if isinstance(z, Real): if z.is_zero: - return MachineReal(0.0) + return MachineReal0 return MachineReal(dps(z.get_precision())) if isinstance(z, Complex): diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index 3a721f18d..7b7fcfeee 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -4,7 +4,6 @@ import base64 import math import re -from functools import lru_cache from typing import Optional, Type, Union import mpmath @@ -44,6 +43,18 @@ def __str__(self) -> str: def is_numeric(self, evaluation=None) -> bool: return True + @property + def is_literal(self) -> bool: + """Number can't change and has a Python representation, + i.e. a value is set and it does not depend on definition + bindings. So we say it is a literal. + """ + return True + + @property + def value(self) -> bool: + return self._value + def _ExponentFunction(value): n = value.get_int_value() @@ -90,35 +101,46 @@ class Integer(Number): value: int class_head_name = "System`Integer" + # Collection of Integers defined so far. + # We use this for object uniqueness. + _integers = {} + # We use __new__ here to unsure that two Integer's that have the same value # return the same object. def __new__(cls, value) -> "Integer": - n = int(value) - self = super(Integer, cls).__new__(cls) - self.value = n - # Set a value for self.__hash__() once so that every time - # it is used this is fast. - self.hash = hash(("Integer", n)) + n = int(value) + key = (cls, n) + self = cls._integers.get(key) + if self is None: + self = super().__new__(cls) + self._value = n + + # Cache object so we don't allocate again. + self._integers[key] = self + + # Set a value for self.__hash__() once so that every time + # it is used this is fast. + self.hash = hash(key) return self def __eq__(self, other) -> bool: return ( - self.value == other.value + self._value == other.value if isinstance(other, Integer) else super().__eq__(other) ) def __ge__(self, other) -> bool: return ( - self.value >= other.value + self._value >= other.value if isinstance(other, Integer) else super().__ge__(other) ) def __gt__(self, other) -> bool: return ( - self.value > other.value + self._value > other.value if isinstance(other, Integer) else super().__gt__(other) ) @@ -128,21 +150,21 @@ def __hash__(self): def __le__(self, other) -> bool: return ( - self.value <= other.value + self._value <= other.value if isinstance(other, Integer) else super().__le__(other) ) def __lt__(self, other) -> bool: return ( - self.value < other.value + self._value < other.value if isinstance(other, Integer) else super().__lt__(other) ) def __ne__(self, other) -> bool: return ( - self.value != other.value + self._value != other.value if isinstance(other, Integer) else super().__ne__(other) ) @@ -150,36 +172,24 @@ def __ne__(self, other) -> bool: def abs(self) -> "Integer": return -self if self < Integer0 else self - @lru_cache(maxsize=1024) - def __init__(self, value): - super().__init__() - 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) - - @property - def is_literal(self) -> bool: - """For an Integer, the value can't change and has a Python representation, - i.e. a value is set and it does not depend on definition - bindings. So we say it is a literal. - """ - return True + return str(self._value) def make_boxes(self, form) -> "String": from mathics.eval.makeboxes import _boxed_string if form in ("System`InputForm", "System`FullForm"): return _boxed_string(str(self.value), number_as_text=True) - return String(str(self.value)) + return String(str(self._value)) def to_sympy(self, **kwargs): - return sympy.Integer(self.value) + return sympy.Integer(self._value) def to_mpmath(self): - return mpmath.mpf(self.value) + return mpmath.mpf(self._value) def to_python(self, *args, **kwargs): return self.value @@ -195,33 +205,35 @@ def round(self, d=None) -> Union["MachineReal", "PrecisionReal"]: return PrecisionReal(sympy.Float(self.value, d)) def get_int_value(self) -> int: - return self.value + return self._value def sameQ(self, other) -> bool: """Mathics SameQ""" - return isinstance(other, Integer) and self.value == other.value + return isinstance(other, Integer) and self._value == other.value def get_sort_key(self, pattern_sort=False) -> tuple: if pattern_sort: return super().get_sort_key(True) else: - return (0, 0, self.value, 0, 1) + return (0, 0, self._value, 0, 1) def do_copy(self) -> "Integer": - return Integer(self.value) + return Integer(self._value) def user_hash(self, update): - update(b"System`Integer>" + str(self.value).encode("utf8")) + update(b"System`Integer>" + str(self._value).encode("utf8")) def __getnewargs__(self): - return (self.value,) + return (self._value,) def __neg__(self) -> "Integer": - return Integer(-self.value) + return Integer(-self._value) @property def is_zero(self) -> bool: - return self.value == 0 + # Note: 0 is self._value or the other way around is a syntax + # error. + return self._value == 0 Integer0 = Integer(0) @@ -295,7 +307,7 @@ def atom_to_boxes(self, f, evaluation): def get_sort_key(self, pattern_sort=False) -> tuple: if pattern_sort: return super().get_sort_key(True) - return (0, 0, self.value, 0, 1) + return (0, 0, self._value, 0, 1) def is_nan(self, d=None) -> bool: return isinstance(self.value, sympy.core.numbers.NaN) @@ -314,29 +326,71 @@ class MachineReal(Real): Stored internally as a python float. """ + # Collection of MachineReals defined so far. + # We use this for object uniqueness. + _machine_reals = {} + def __new__(cls, value) -> "MachineReal": - self = Number.__new__(cls) - self.value = float(value) - if math.isinf(self.value) or math.isnan(self.value): + n = float(value) + if math.isinf(n) or math.isnan(n): raise OverflowError + + key = (cls, n) + self = cls._machine_reals.get(key) + if self is None: + self = Number.__new__(cls) + self._value = n + + # Cache object so we don't allocate again. + self._machine_reals[key] = self + + # Set a value for self.__hash__() once so that every time + # it is used this is fast. + self.hash = hash(key) return self + def __getnewargs__(self): + return (self.value,) + + def __hash__(self): + return self.hash + + def __neg__(self) -> "MachineReal": + return MachineReal(-self.value) + + def do_copy(self) -> "MachineReal": + return MachineReal(self._value) + + def get_precision(self) -> float: + """Returns the default specification for precision in N and other numerical functions.""" + return machine_precision + + def get_float_value(self, permit_complex=False) -> float: + return self.value + @property - def is_literal(self) -> bool: - """For a Real, the value can't change and has a Python representation, - i.e. a value is set and it does not depend on definition - bindings. So we say it is a literal. - """ + def is_approx_zero(self) -> bool: + # In WMA, Chop[10.^(-10)] == 0, + # so, lets take it. + res = abs(self.value) <= 1e-10 + return res + + def is_machine_precision(self) -> bool: return True - def to_python(self, *args, **kwargs) -> float: - return self.value + def make_boxes(self, form): + from mathics.builtin.makeboxes import number_form - def to_sympy(self, *args, **kwargs): - return sympy.Float(self.value) + _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 to_mpmath(self): - return mpmath.mpf(self.value) + @property + def is_zero(self) -> bool: + return self.value == 0.0 def round(self, d=None) -> "MachineReal": return self @@ -364,45 +418,21 @@ def sameQ(self, other) -> bool: else: return False - def is_machine_precision(self) -> bool: - return True - - def get_precision(self) -> float: - """Returns the default specification for precision in N and other numerical functions.""" - return machine_precision - - def get_float_value(self, permit_complex=False) -> float: + def to_python(self, *args, **kwargs) -> float: return self.value - def make_boxes(self, form): - from mathics.builtin.makeboxes 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 to_sympy(self, *args, **kwargs): + return sympy.Float(self.value) - def __neg__(self) -> "MachineReal": - return MachineReal(-self.value) + def to_mpmath(self): + return mpmath.mpf(self.value) @property - def is_zero(self) -> bool: - return self.value == 0.0 + def value(self) -> bool: + return self._value - @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 + +MachineReal0 = MachineReal(0) class PrecisionReal(Real): @@ -414,25 +444,57 @@ class PrecisionReal(Real): Note: Plays nicely with the mpmath.mpf (float) type. """ + # Collection of MachineReals defined so far. + # We use this for object uniqueness. + _precision_reals = {} + value: sympy.Float def __new__(cls, value) -> "PrecisionReal": - self = Number.__new__(cls) - self.value = sympy.Float(value) + n = sympy.Float(value) + key = (cls, n) + self = cls._precision_reals.get(key) + if self is None: + self = Number.__new__(cls) + self._value = n + + # Cache object so we don't allocate again. + self._precision_reals[key] = self + + # Set a value for self.__hash__() once so that every time + # it is used this is fast. + self.hash = hash(key) + return self - @property - def is_literal(self) -> bool: - """For a PrecisionReal, the value can't change and has a Python representation, - i.e. a value is set and it does not depend on definition - bindings. So we say it is a literal. - """ - return True + def __getnewargs__(self): + return (self.value,) + + def __hash__(self): + return self.hash + + def __neg__(self) -> "PrecisionReal": + return PrecisionReal(-self.value) + + def do_copy(self) -> "PrecisionReal": + return PrecisionReal(self.value) + + def get_precision(self) -> float: + """Returns the default specification for precision (in binary digits) in N and other numerical functions.""" + return self.value._prec + 1.0 @property def is_zero(self) -> bool: return self.value == 0.0 + def make_boxes(self, form): + from mathics.builtin.makeboxes 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 round(self, d=None) -> Union[MachineReal, "PrecisionReal"]: if d is None: return MachineReal(float(self.value)) @@ -459,27 +521,6 @@ def sameQ(self, other) -> bool: diff = abs(value - other_value) return diff < 0.5**prec - def get_precision(self) -> float: - """Returns the default specification for precision (in binary digits) in N and other numerical functions.""" - return self.value._prec + 1.0 - - def make_boxes(self, form): - from mathics.builtin.makeboxes 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) - def to_python(self, *args, **kwargs): return float(self.value) @@ -489,6 +530,10 @@ def to_sympy(self, *args, **kwargs): def to_mpmath(self): return mpmath.mpf(self.value) + @property + def value(self): + return self._value + class ByteArrayAtom(Atom, ImmutableValueMixin): value: str @@ -581,11 +626,11 @@ class Complex(Number): 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.") + raise ValueError("Argument 'real' must be a Real number.") if imag is SymbolInfinity: return SymbolI * SymbolInfinity if isinstance(imag, Complex) or not isinstance(imag, Number): - raise ValueError("Argument 'imag' must be a real number.") + raise ValueError("Argument 'imag' must be a Real number.") if imag.sameQ(Integer0): return real @@ -601,6 +646,8 @@ def __new__(cls, real, imag): # Set a value for self.__hash__() once so that every time # it is used this is fast. self.hash = hash(("Complex", real, imag)) + + self._value = (self.real, self.imag) return self def __hash__(self): @@ -719,16 +766,27 @@ def is_approx_zero(self) -> bool: class Rational(Number): class_head_name = "System`Rational" - # We use __new__ here to unsure that two Integer's that have the same value + # Collection of integers defined so far. + _rationals = {} + + # We use __new__ here to unsure that two Rationals's that have the same value # return the same object. - # Think about: Do we ever need this on a __new__ since that does the same thing? - @lru_cache(maxsize=1024) def __new__(cls, numerator, denominator=1) -> "Rational": - self = super().__new__(cls) - self.value = sympy.Rational(numerator, denominator) - # Set a value for self.__hash__() once so that every time - # it is used this is fast. - self.hash = hash(("Rational", self.value)) + + value = sympy.Rational(numerator, denominator) + key = (cls, value) + self = cls._rationals.get(key) + + if self is None: + self = super().__new__(cls) + self._value = value + + # Cache object so we don't allocate again. + self._rationals[key] = self + + # Set a value for self.__hash__() once so that every time + # it is used this is fast. + self.hash = hash(key) return self def __hash__(self): diff --git a/mathics/core/convert/mpmath.py b/mathics/core/convert/mpmath.py index a0f6ec91f..1e0a27a16 100644 --- a/mathics/core/convert/mpmath.py +++ b/mathics/core/convert/mpmath.py @@ -8,6 +8,7 @@ from mathics.core.atoms import ( Complex, MachineReal, + MachineReal0, PrecisionReal, ) @@ -28,7 +29,7 @@ def from_mpmath(value, prec=None, acc=None): # If the error if of the order of the number, the number # is compatible with 0. if prec < 1.0: - return MachineReal(float(0)) + return MachineReal0 # HACK: use str here to prevent loss of precision return PrecisionReal(sympy.Float(str(value), prec)) elif isinstance(value, mpmath.mpc): diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 4019acda1..07f0065ad 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -270,6 +270,12 @@ def _build_elements_properties(self): values = [] for element in self._elements: # Test for the literalness, and the three properties mentioned above + try: + element.is_literal + except: + from trepan.api import debug + + debug() if element.is_literal: values.append(element.value) else: diff --git a/mathics/core/list.py b/mathics/core/list.py index 19cb603e3..20e2882b0 100644 --- a/mathics/core/list.py +++ b/mathics/core/list.py @@ -55,6 +55,12 @@ def __init__( self._is_literal = True values = [] for element in elements: + try: + element.is_literal + except: + from trepan.api import debug + + debug() if element.is_literal: values.append(element.value) else: diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index c2a907b35..565e895b3 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -410,7 +410,7 @@ def __new__(cls, name: str, sympy_dummy=None, value=None): # self.sympy_dummy, for which I have to dig into the # code to see even what type of value should be expected # for it. - self.value = value + self._value = value self._short_name = strip_context(name) cls.defined_symbols[name] = self @@ -634,6 +634,10 @@ def to_sympy(self, **kwargs): return sympy.Symbol(sympy_symbol_prefix + self.name) return builtin.to_sympy(self, **kwargs) + @property + def value(self) -> Any: + return self._value + class PredefinedSymbol(Symbol): """ diff --git a/test/core/test_atoms.py b/test/core/test_atoms.py index 67b387952..03b24ebac 100644 --- a/test/core/test_atoms.py +++ b/test/core/test_atoms.py @@ -6,8 +6,12 @@ from mathics.core.atoms import ( Complex, Integer, + Integer1, + Integer2, MachineReal, + MachineReal0, Rational, + RationalOneHalf, Real, String, ) @@ -16,7 +20,8 @@ from mathics.core.definitions import Definitions import sys -import unittest +import mathics.core.systemsymbols as system_symbols +import mathics.core.atoms as atoms definitions = Definitions(add_builtin=True) @@ -30,106 +35,206 @@ def _symbol_truth_value(x): return "undefined" -def _test_group(k, *group): +def check_group(*group): for i, a in enumerate(group): - sep = "" for j, b in enumerate(group): - if i == j: - continue evaluation = Evaluation(definitions, catch_interrupt=False) try: is_same_under_sameq = Expression(SymbolSameQ, a, b).evaluate(evaluation) except Exception as exc: - print("Exception %s" % exc) - info = sys.exc_info() - sys.excepthook(*info) - return False + assert False, f"Exception {exc}" is_same = a.sameQ(b) - if is_same != _symbol_truth_value(is_same_under_sameq): - print( - "%sTest failed: %s and %s are inconsistent under .sameQ() and SameQ\n" - % (sep, repr(a), repr(b)) - ) - return False - - if is_same and hash(a) != hash(b): - print( - "%sTest failed: hashes for %s and %s must be equal but are not\n" - % (sep, repr(a), repr(b)) - ) - return False - sep = "\n" - - -class HashAndSameQ(unittest.TestCase): - # tests that the following assumption holds: if objects are same under SameQ, their - # hash is always equals. this assumption is relied upon by Gather[] and similar operations - - def testInteger(self): - _test_group(Integer(5), Integer(3242), Integer(-1372)) - - def testRational(self): - _test_group( - Rational(1, 3), - Rational(1, 3), - Rational(2, 6), - Rational(-1, 3), - Rational(-10, 30), - Rational(10, 5), - ) - - def testReal(self): - _test_group( - MachineReal(1.17361), - MachineReal(-1.42), - MachineReal(42.846195714), - MachineReal(42.846195714), - MachineReal(42.846195713), - Real("42.846195713", 18), - Real("-1.42", 3), - ) - - def testComplex(self): - def c(i, r): - return Complex(MachineReal(i), MachineReal(i)) - - _test_group(c(1.2, 1.2), c(0.7, 1.8), c(1.8, 0.7), c(-0.7, 1.8), c(0.7, 1.81)) - - def testString(self): - _test_group(String("xy"), String("x"), String("xyz"), String("abc")) - - def testSymbol(self): - _test_group(Symbol("xy"), Symbol("x"), Symbol("xyz"), Symbol("abc")) - - def testAcrossTypes(self): - _test_group( - Integer(1), - Rational(1, 1), - Real(1), - Complex(Integer(1), Integer(1)), - String("1"), - Symbol("1"), - ) - - def testInstances(self): - # duplicate instantiations of same content (like Integer 5) to test for potential instantiation randomness. - _test_group( - list( - map( - lambda f: (f(), f()), - ( - lambda: Integer(5), - lambda: Rational(5, 2), - lambda: MachineReal(5.12345678), - lambda: Complex(Integer(5), Integer(2)), - lambda: String("xy"), - lambda: Symbol("xy"), - ), - ) + assert ( + is_same != _symbol_truth_value(is_same_under_sameq), + f"{repr(a)} and {repr(b)} are inconsistent under .sameQ() and SameQ", ) - ) + + assert ( + is_same and hash(a) != hash(b), + f"hashes for {repr(a)} and {repr(b)} are not equal", + ) + + +seen_hashes = {} + + +def check_object_dissimilarity(*group): + """Check that all objects in ``group`` are dissimilar. + + Specifically they should be different Python objects. + This is tested using the Python object id() function and + also using the Python ``is`` operator. + + Also, check that all objects __hash__() to the different numbers. + """ + n = len(group) + assert n > 0, "Test program is written incorrectly" + + for i, item in enumerate(group): + j = i + 1 + while j < n: + second_item = group[j] + assert id(item) != id(second_item), f"i: {i}, j: {j}" + assert item.hash != second_item.hash + assert item is not second_item + j += 1 + + +def check_object_uniqueness(klass, args, *group): + """Check that all objects in ``group`` are the same. + + Specifically they should be the same Python object. This is is + tested using the object id() function and also using the Python + ``is`` operator. + + Also, check that all objects __hash__() to the same number + + """ + assert len(group) > 0, "Test program is written incorrectly" + first_item = group[0] + unique_id = id(first_item) + + # Test allocating a new object a 3 times + for _ in range(3): + new_object = klass(*args) + assert id(new_object) == unique_id + assert new_object is first_item + + # Now check __hash__() function or immutability + # for using the object as a key in a dictionary, or set. + + # See that first object hasn't been seen; + # Then add it and see that is now found for + # all objects in the group. + + assert all((item not in seen_hashes for item in group)) + seen_hashes[first_item] = first_item + + # Now check that all of the remaining items are in + # seen_items and are the same item. + + assert all((seen_hashes[item] is item for item in seen_hashes)) + + +def test_Complex(): + def c(i, r): + return Complex(MachineReal(i), MachineReal(i)) + + check_group(c(1.2, 1.2), c(0.7, 1.8), c(1.8, 0.7), c(-0.7, 1.8), c(0.7, 1.81)) + + +def test_Integer(): + check_group(Integer(5), Integer(3242), Integer(-1372)) + # Integer1 should be predefined; 1.0 is used since + # we allow floats in the constructor. + check_object_uniqueness(Integer, [1], Integer1, Integer(1), Integer(1.0)) -if __name__ == "__main__": - unittest.main() +def test_MachineReal(): + check_group(MachineReal(5), MachineReal(3.5), Integer(1.00001)) + # MachineReal0 should be predefined; `int` and float arguments are allowed + # `int` arguemnts are converted to float. + check_object_uniqueness( + MachineReal, [0.0], MachineReal0, MachineReal(0), MachineReal(0.0) + ) + + +def test_Rational(): + check_group( + Rational(1, 3), + Rational(1, 3), + Rational(2, 6), + Rational(-1, 3), + Rational(-10, 30), + Rational(10, 5), + ) + + # RationalOneHalf should be predefined; We reduce ratios, so + # Rationa(1, 2) is Rational(2, 4). + check_object_uniqueness( + Rational, [1, 2], RationalOneHalf, Rational(1, 2), Rational(2, 4) + ) + + +def test_Real(): + check_group( + Real(1.17361), + Real(-1.42), + Real(42.846195714), + Real(42.846195714), + Real(42.846195713), + Real("42.846195713", 18), + Real("-1.42", 3), + ) + + +def test_String(): + check_group(String("xy"), String("x"), String("xyz"), String("abc")) + + +def test_Symbol(): + check_group(Symbol("xy"), Symbol("x"), Symbol("xyz"), Symbol("abc")) + + +def test_object_dissimilarity(): + """check that different objects type whether they have or different value are different""" + + # fmt: off + check_object_dissimilarity( + Complex(Integer(0), Integer(1)), # 0 + Integer(1), # 1 + Integer(2), # 2 + MachineReal(1), # 3 + MachineReal(2), # 4 + MachineReal(5.12345678), # 5 + Rational(1, 0), # 6 + Rational(2, 1), # 7 + Real(1.1), # 8 + Real(2.1), # 9 + String("1"), # 10 + String("I"), # 11 + Symbol("1"), # 12 + Symbol("I"), # 13 + ) + # fmt: on + + # See also test_mixed_object_similarity + + # Check that all pre-defined Integers, MachineReals, and Symbols are different. + + symbol_names = { + key for key in system_symbols.__dict__.keys() if key.startswith("Symbol") + } + symbol_names.remove("Symbol") + symbol_objects = {system_symbols.__dict__[name] for name in symbol_names} + + atom_names = { + key + for key in atoms.__dict__.keys() + if key.startswith("Integer") or key.startswith("MachineReal") + } + atom_names = atom_names - set(("Integer", "MachineReal")) + atom_objects = {atoms.__dict__[name] for name in atom_names} + check_object_dissimilarity(*symbol_objects, *atom_objects) + + +def test_mixed_object_canonicalization(): + """check that objects of different types canonicalize to the same Python object""" + # fmt: off + check_object_uniqueness( + Integer, [2], + Integer2, # 0 + Integer(2), # 1 + Complex(Integer(2), Integer(0)), # 2 + ) + + # Divide by zero produces the same thing. + check_object_uniqueness( + Rational, [1, 0], + Rational(1, 0), # 0 + Rational(1.1, 0), # 1 + Rational(2, 0), # 2 + Complex(Rational(1, 0), Integer(0)), # 3 + ) + # fmt: on From 3f2f1c28869fb3f85235b86562aabba8f68cb3a9 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 18 Dec 2022 20:16:06 -0500 Subject: [PATCH 040/121] Numpy 2.4 doesn't seem to work on 3.8 Is this new or was this always the case? --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3f94980b5..6eaf1ad68 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ elif sys.version_info[:2] == (3, 7): INSTALL_REQUIRES += ["numpy<1.22", "llvmlite", "sympy>=1.8, < 1.11"] else: - INSTALL_REQUIRES += ["numpy", "llvmlite", "sympy>=1.8, < 1.11"] + INSTALL_REQUIRES += ["numpy<1.24", "llvmlite", "sympy>=1.8, < 1.11"] if not is_PyPy: INSTALL_REQUIRES += ["recordclass"] From 478a72f556c157ab90b211a548c5fd75e800000a Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 18 Dec 2022 21:12:53 -0500 Subject: [PATCH 041/121] Go over random module for standards... Also, Complex now can sometimes have a tuple value, so adjust for that. --- mathics/builtin/numbers/randomnumbers.py | 693 ++++++++++++----------- mathics/core/systemsymbols.py | 2 + 2 files changed, 368 insertions(+), 327 deletions(-) diff --git a/mathics/builtin/numbers/randomnumbers.py b/mathics/builtin/numbers/randomnumbers.py index 1b2b4df12..b34cceb37 100644 --- a/mathics/builtin/numbers/randomnumbers.py +++ b/mathics/builtin/numbers/randomnumbers.py @@ -6,26 +6,24 @@ Random numbers are generated using the Mersenne Twister. """ -import pickle - import binascii import hashlib -from operator import mul as operator_mul +import pickle from functools import reduce - +from operator import mul as operator_mul from mathics.builtin.base import Builtin from mathics.builtin.numpy_utils import instantiate_elements, stack -from mathics.core.atoms import Integer, String, Real, Complex -from mathics.eval.nevaluator import eval_N +from mathics.core.atoms import Complex, Integer, Real, String from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull - - -SymbolRandomComplex = Symbol("RandomComplex") -SymbolRandomReal = Symbol("RandomReal") -SymbolTotal = Symbol("Total") +from mathics.core.systemsymbols import ( + SymbolRandomComplex, + SymbolRandomReal, + SymbolTotal, +) +from mathics.eval.nevaluator import eval_N try: import numpy @@ -36,8 +34,8 @@ import random if _numpy: - import time import os + import time # mathics.builtin.__init__.py module scanning logic gets confused # if we assign numpy.random.get_state to a variable here. so we @@ -145,9 +143,11 @@ def randchoice(self, n, size, replace, p): class RandomState(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/RandomState.html)
-
'$RandomState' -
is a long number representing the internal state of the pseudorandom number generator. +
'$RandomState' +
is a long number representing the internal state of the \ + pseudo-random number generator.
>> Mod[$RandomState, 10^100] @@ -171,122 +171,289 @@ class RandomState(Builtin): } summary_text = "internal state of the (pseudo)random number generator" - def apply(self, evaluation): + def eval(self, evaluation): "$RandomState" with RandomEnv(evaluation): return Integer(get_random_state()) -class SeedRandom(Builtin): +class _RandomBase(Builtin): + messages = { + "array": ( + "The array dimensions `1` given in position 2 of `2` should be a " + "list of non-negative machine-sized integers giving the " + "dimensions for the result." + ), + } + rules = { + "%(name)s[spec_]": "%(name)s[spec, {1}]", + "%(name)s[spec_, n_Integer]": "%(name)s[spec, {n}]", + } + + def _size_to_python(self, domain, size, evaluation): + is_proper_spec = size.get_head_name() == "System`List" and all( + n.is_numeric(evaluation) for n in size.elements + ) + + py_size = size.to_python() if is_proper_spec else None + if (py_size is None) or ( + not all(isinstance(i, int) and i >= 0 for i in py_size) + ): + expr = Expression(Symbol(self.get_name()), domain, size) + return evaluation.message(self.get_name(), "array", size, expr), None + + return False, py_size + + +class _RandomSelection(_RandomBase): + # implementation note: weights are clipped to numpy floats. this might be different from MMA + # where weights might be handled with full dynamic precision support through the whole computation. + # we try to limit the error by normalizing weights with full precision, and then clipping to float. + # since weights are probabilities into a finite set, this should not make a difference. + + messages = { + "wghtv": "The weights on the left-hand side of `1` has to be a list of non-negative numbers " + + "with the same length as the list of items on the right-hand side.", + "lrwl": "`1` has to be a list of items or a rule of the form weights -> choices.", + "smplen": "RandomSample cannot choose `1` samples, as this are more samples than there are in `2`. " + + "Use RandomChoice to choose items from a set with replacing.", + } + + def eval(self, domain, size, evaluation): + """%(name)s[domain_, size_]""" + if domain.get_head_name() == "System`Rule": # elements and weights + err, py_weights = self._weights_to_python(domain.elements[0], evaluation) + if py_weights is None: + return err + elements = domain.elements[1].elements + if domain.elements[1].get_head_name() != "System`List" or len( + py_weights + ) != len(elements): + return evaluation.message(self.get_name(), "wghtv", domain) + elif domain.get_head_name() == "System`List": # only elements + py_weights = None + elements = domain.elements + else: + return evaluation.message(self.get_name(), "lrwl", domain) + err, py_size = self._size_to_python(domain, size, evaluation) + if py_size is None: + return err + if not self._replace: # i.e. RandomSample? + n_chosen = reduce(operator_mul, py_size, 1) + if len(elements) < n_chosen: + return evaluation.message("smplen", size, domain), None + with RandomEnv(evaluation) as rand: + return instantiate_elements( + rand.randchoice( + len(elements), size=py_size, replace=self._replace, p=py_weights + ), + lambda i: elements[i], + ) + + 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(evaluation) for w in weights.elements + ) + + if ( + is_proper_spec and len(weights.elements) > 1 + ): # normalize before we lose accuracy + norm_weights = Expression( + SymbolDivide, weights, Expression(SymbolTotal, weights) + ).evaluate(evaluation) + if norm_weights is None or not all( + w.is_numeric(evaluation) for w in norm_weights.elements + ): + return evaluation.message(self.get_name(), "wghtv", weights), None + weights = norm_weights + + py_weights = eval_N(weights, evaluation).to_python() if is_proper_spec else None + if (py_weights is None) or ( + not all(isinstance(w, (int, float)) and w >= 0 for w in py_weights) + ): + return evaluation.message(self.get_name(), "wghtv", weights), None + + return False, py_weights + + +class Random(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/Random.html
-
'SeedRandom[$n$]' -
resets the pseudorandom generator with seed $n$. -
'SeedRandom[]' -
uses the current date and time as the seed. +
'Random[]' +
gives a uniformly distributed pseudorandom Real number in the range 0 to 1. + +
'Random[$type$, $range$]' +
gives a uniformly distributed pseudorandom number of the type $type$, in the specified interval $range$. Possible types are 'Integer', 'Real' or 'Complex'.
+ Legacy function. Superseded by RandomReal, RandomInteger and RandomComplex. - 'SeedRandom' can be used to get reproducible random numbers: - >> SeedRandom[42] - >> RandomInteger[100] - = ... - >> RandomInteger[100] - = ... - >> SeedRandom[42] - >> RandomInteger[100] - = ... - >> RandomInteger[100] - = ... + """ - String seeds are supported as well: - >> SeedRandom["Mathics"] - >> RandomInteger[100] - = ... + rules = { + "Random[Integer]": "RandomInteger[]", + "Random[Integer, zmax_Integer]": "RandomInteger[zmax]", + "Random[Integer, {zmin_Integer, zmax_Integer}]": "RandomInteger[{zmin, zmax}]", + "Random[Real]": "RandomReal[]", + "Random[Real, zmax_?NumberQ]": "RandomReal[zmax]", + "Random[Real, {zmin_Real, zmax_Real}]": "RandomReal[{zmin, zmax}]", + "Random[Complex]": "RandomComplex[]", + "Random[Complex, zmax_Complex]": "RandomComplex[zmax]", + "Random[Complex, {zmin_?NumberQ, zmax_?NumberQ}]": "RandomComplex[{zmin, zmax}]", + } - Calling 'SeedRandom' without arguments will seed the random - number generator to a random state: - >> SeedRandom[] - >> RandomInteger[100] - = ... + summary_text = "pick a random number" - #> SeedRandom[x] - : Argument x should be an integer or string. - = SeedRandom[x] + +class RandomComplex(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/RandomComplex.html) +
+
'RandomComplex[{$z_min$, $z_max$}]' +
yields a pseudorandom complex number in the rectangle with complex corners $z_min$ and $z_max$. - messages = { - "seed": "Argument `1` should be an integer or string.", - } - summary_text = "set the seed of the (pseudo)random number generator" +
'RandomComplex[$z_max$]' +
yields a pseudorandom complex number in the rectangle with corners at the origin and at $z_max$. - def apply(self, x, evaluation): - "SeedRandom[x_]" +
'RandomComplex[]' +
yields a pseudorandom complex number with real and imaginary parts from 0 to 1. - if isinstance(x, Integer): - value = x.value - elif isinstance(x, String): - # OS/version-independent hash - value = int( - hashlib.md5(x.get_string_value().encode("utf8")).hexdigest(), 16 - ) - else: - return evaluation.message("SeedRandom", "seed", x) - with RandomEnv(evaluation) as rand: - rand.seed(value) - return SymbolNull +
'RandomComplex[$range$, $n$]' +
gives a list of $n$ pseudorandom complex numbers. - def apply_empty(self, evaluation): - "SeedRandom[]" +
'RandomComplex[$range$, {$n1$, $n2$, ...}]' +
gives a nested list of pseudorandom complex numbers. +
- with RandomEnv(evaluation) as rand: - rand.seed() - return SymbolNull + >> RandomComplex[] + = ... + #> 0 <= Re[%] <= 1 && 0 <= Im[%] <= 1 + = True + + >> RandomComplex[{1+I, 5+5I}] + = ... + #> 1 <= Re[%] <= 5 && 1 <= Im[%] <= 5 + = True + >> RandomComplex[1+I, 5] + = {..., ..., ..., ..., ...} + + >> RandomComplex[{1+I, 2+2I}, {2, 2}] + = {{..., ...}, {..., ...}} + + #> RandomComplex[{6, 2 Pi + I}] + = 6... + + #> RandomComplex[{6.3, 2.5 I}] // FullForm + = Complex[..., ...] + """ -class _RandomBase(Builtin): messages = { + "unifr": ( + "The endpoints specified by `1` for the endpoints of the " + "discrete uniform distribution range are not complex valued." + ), "array": ( "The array dimensions `1` given in position 2 of `2` should be a " "list of non-negative machine-sized integers giving the " "dimensions for the result." ), } + rules = { - "%(name)s[spec_]": "%(name)s[spec, {1}]", - "%(name)s[spec_, n_Integer]": "%(name)s[spec, {n}]", + "RandomComplex[]": "RandomComplex[{0, 1+I}]", + "RandomComplex[zmax_?NumberQ]": "RandomComplex[{0, zmax}]", + "RandomComplex[zmax_?NumberQ, ns_]": "RandomComplex[{0, zmax}, ns]", } + summary_text = "pick a complex number at random from a rectangular region" - def _size_to_python(self, domain, size, evaluation): - is_proper_spec = size.get_head_name() == "System`List" and all( - n.is_numeric(evaluation) for n in size.elements + @staticmethod + def to_complex(value, evaluation): + result = eval_N(value, evaluation) + + if hasattr(result, "value") and not isinstance(result.value, tuple): + result_value = result.value + else: + # TODO: result.value does not work, because + # Complex does not have a ``value`` attribute. + # Otherwise, we could return here ``None``. + result_value = result.to_python() + + if isinstance(result_value, (float, int)): + return complex(result_value) + if isinstance(result_value, complex): + return result_value + + return None + + def eval(self, zmin, zmax, evaluation): + "RandomComplex[{zmin_, zmax_}]" + + min_value, max_value = ( + self.to_complex(zmin, evaluation), + self.to_complex(zmax, evaluation), ) + if min_value is None or max_value is None: + return evaluation.message( + "RandomComplex", "unifr", ListExpression(zmin, zmax) + ) - py_size = size.to_python() if is_proper_spec else None - if (py_size is None) or ( - not all(isinstance(i, int) and i >= 0 for i in py_size) - ): - expr = Expression(Symbol(self.get_name()), domain, size) - return evaluation.message(self.get_name(), "array", size, expr), None + with RandomEnv(evaluation) as rand: + real = Real(rand.randreal(min_value.real, max_value.real)) + imag = Real(rand.randreal(min_value.imag, max_value.imag)) + return Complex(real, imag) - return False, py_size + def eval_list(self, zmin, zmax, ns, evaluation): + "RandomComplex[{zmin_, zmax_}, ns_]" + expr = Expression(SymbolRandomComplex, ListExpression(zmin, zmax), ns) + + min_value, max_value = ( + self.to_complex(zmin, evaluation), + self.to_complex(zmax, evaluation), + ) + if min_value is None or max_value is None: + return evaluation.message( + "RandomComplex", "unifr", ListExpression(zmin, zmax) + ) + + py_ns = ns.to_python() + if not isinstance(py_ns, list): + py_ns = [py_ns] + + if not all([isinstance(i, int) and i >= 0 for i in py_ns]): + return evaluation.message("RandomComplex", "array", ns, expr) + + with RandomEnv(evaluation) as rand: + real = rand.randreal(min_value.real, max_value.real, py_ns) + imag = rand.randreal(min_value.imag, max_value.imag, py_ns) + return instantiate_elements( + stack(real, imag), lambda c: Complex(Real(c[0]), Real(c[1])), d=2 + ) class RandomInteger(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/RandomInteger.html)
-
'RandomInteger[{$min$, $max$}]' -
yields a pseudorandom integer in the range from $min$ to - $max$ inclusive. -
'RandomInteger[$max$]' -
yields a pseudorandom integer in the range from 0 to $max$ - inclusive. -
'RandomInteger[]' -
gives 0 or 1. -
'RandomInteger[$range$, $n$]' -
gives a list of $n$ pseudorandom integers. -
'RandomInteger[$range$, {$n1$, $n2$, ...}]' -
gives a nested list of pseudorandom integers. +
'RandomInteger[{$min$, $max$}]' +
yields a pseudorandom integer in the range from $min$ to \ + $max$ inclusive. + +
'RandomInteger[$max$]' +
yields a pseudorandom integer in the range from 0 to $max$ \ + inclusive. + +
'RandomInteger[]' +
gives 0 or 1. + +
'RandomInteger[$range$, $n$]' +
gives a list of $n$ pseudorandom integers. + +
'RandomInteger[$range$, {$n1$, $n2$, ...}]' +
gives a nested list of pseudorandom integers.
>> RandomInteger[{1, 5}] @@ -320,9 +487,10 @@ class RandomInteger(Builtin): "RandomInteger[max_Integer, ns_]": "RandomInteger[{0, max}, ns]", "RandomInteger[spec_, n_Integer]": "RandomInteger[spec, {n}]", } + summary_text = "pick an integer number at random from a range" - def apply(self, rmin, rmax, evaluation): + def eval(self, rmin, rmax, evaluation): "RandomInteger[{rmin_, rmax_}]" if not isinstance(rmin, Integer) or not isinstance(rmax, Integer): @@ -333,7 +501,7 @@ def apply(self, rmin, rmax, evaluation): with RandomEnv(evaluation) as rand: return Integer(rand.randint(rmin, rmax)) - def apply_list(self, rmin, rmax, ns, evaluation): + def eval_list(self, rmin, rmax, ns, evaluation): "RandomInteger[{rmin_, rmax_}, ns_List]" if not isinstance(rmin, Integer) or not isinstance(rmax, Integer): return evaluation.message( @@ -348,6 +516,7 @@ def apply_list(self, rmin, rmax, ns, evaluation): class RandomReal(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/RandomReal.html)
'RandomReal[{$min$, $max$}]'
yields a pseudorandom real number in the range from $min$ to $max$. @@ -404,7 +573,7 @@ class RandomReal(Builtin): } summary_text = "pick a real number at random from an interval" - def apply(self, xmin, xmax, evaluation): + def eval(self, xmin, xmax, evaluation): "RandomReal[{xmin_, xmax_}]" if not ( @@ -417,7 +586,7 @@ def apply(self, xmin, xmax, evaluation): with RandomEnv(evaluation) as rand: return Real(rand.randreal(min_value, max_value)) - def apply_list(self, xmin, xmax, ns, evaluation): + def eval_list(self, xmin, xmax, ns, evaluation): "RandomReal[{xmin_, xmax_}, ns_List]" if not ( @@ -440,151 +609,73 @@ def apply_list(self, xmin, xmax, ns, evaluation): ) -class RandomComplex(Builtin): +class SeedRandom(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/SeedRandom.html)
-
'RandomComplex[{$z_min$, $z_max$}]' -
yields a pseudorandom complex number in the rectangle with complex corners $z_min$ and $z_max$. -
'RandomComplex[$z_max$]' -
yields a pseudorandom complex number in the rectangle with corners at the origin and at $z_max$. -
'RandomComplex[]' -
yields a pseudorandom complex number with real and imaginary parts from 0 to 1. -
'RandomComplex[$range$, $n$]' -
gives a list of $n$ pseudorandom complex numbers. -
'RandomComplex[$range$, {$n1$, $n2$, ...}]' -
gives a nested list of pseudorandom complex numbers. +
'SeedRandom[$n$]' +
resets the pseudorandom generator with seed $n$. + +
'SeedRandom[]' +
uses the current date and time as the seed.
- >> RandomComplex[] + 'SeedRandom' can be used to get reproducible random numbers: + >> SeedRandom[42] + >> RandomInteger[100] = ... - #> 0 <= Re[%] <= 1 && 0 <= Im[%] <= 1 - = True - - >> RandomComplex[{1+I, 5+5I}] + >> RandomInteger[100] + = ... + >> SeedRandom[42] + >> RandomInteger[100] + = ... + >> RandomInteger[100] = ... - #> 1 <= Re[%] <= 5 && 1 <= Im[%] <= 5 - = True - - >> RandomComplex[1+I, 5] - = {..., ..., ..., ..., ...} - >> RandomComplex[{1+I, 2+2I}, {2, 2}] - = {{..., ...}, {..., ...}} + String seeds are supported as well: + >> SeedRandom["Mathics"] + >> RandomInteger[100] + = ... - #> RandomComplex[{6, 2 Pi + I}] - = 6... + Calling 'SeedRandom' without arguments will seed the random + number generator to a random state: + >> SeedRandom[] + >> RandomInteger[100] + = ... - #> RandomComplex[{6.3, 2.5 I}] // FullForm - = Complex[..., ...] + #> SeedRandom[x] + : Argument x should be an integer or string. + = SeedRandom[x] """ messages = { - "unifr": ( - "The endpoints specified by `1` for the endpoints of the " - "discrete uniform distribution range are not complex valued." - ), - "array": ( - "The array dimensions `1` given in position 2 of `2` should be a " - "list of non-negative machine-sized integers giving the " - "dimensions for the result." - ), - } - - rules = { - "RandomComplex[]": "RandomComplex[{0, 1+I}]", - "RandomComplex[zmax_?NumberQ]": "RandomComplex[{0, zmax}]", - "RandomComplex[zmax_?NumberQ, ns_]": "RandomComplex[{0, zmax}, ns]", + "seed": "Argument `1` should be an integer or string.", } - summary_text = "pick a complex number at random from a rectangular region" - - @staticmethod - def to_complex(value, evaluation): - result = eval_N(value, evaluation) - - if hasattr(result, "value"): - result_value = result.value - else: - # TODO: result.value does not work, because - # Complex does not have a ``value`` attribute. - # Otherwise, we could return here ``None``. - result_value = result.to_python() - if isinstance(result_value, (float, int)): - return complex(result_value) - if isinstance(result_value, complex): - return result_value - - return None + summary_text = "set the seed of the (pseudo)random number generator" - def apply(self, zmin, zmax, evaluation): - "RandomComplex[{zmin_, zmax_}]" + def eval(self, x, evaluation): + "SeedRandom[x_]" - min_value, max_value = ( - self.to_complex(zmin, evaluation), - self.to_complex(zmax, evaluation), - ) - if min_value is None or max_value is None: - return evaluation.message( - "RandomComplex", "unifr", ListExpression(zmin, zmax) + if isinstance(x, Integer): + value = x.value + elif isinstance(x, String): + # OS/version-independent hash + value = int( + hashlib.md5(x.get_string_value().encode("utf8")).hexdigest(), 16 ) - + else: + return evaluation.message("SeedRandom", "seed", x) with RandomEnv(evaluation) as rand: - real = Real(rand.randreal(min_value.real, max_value.real)) - imag = Real(rand.randreal(min_value.imag, max_value.imag)) - return Complex(real, imag) - - def apply_list(self, zmin, zmax, ns, evaluation): - "RandomComplex[{zmin_, zmax_}, ns_]" - expr = Expression(SymbolRandomComplex, ListExpression(zmin, zmax), ns) - - min_value, max_value = ( - self.to_complex(zmin, evaluation), - self.to_complex(zmax, evaluation), - ) - if min_value is None or max_value is None: - return evaluation.message( - "RandomComplex", "unifr", ListExpression(zmin, zmax) - ) - - py_ns = ns.to_python() - if not isinstance(py_ns, list): - py_ns = [py_ns] + rand.seed(value) + return SymbolNull - if not all([isinstance(i, int) and i >= 0 for i in py_ns]): - return evaluation.message("RandomComplex", "array", ns, expr) + def eval_empty(self, evaluation): + "SeedRandom[]" with RandomEnv(evaluation) as rand: - real = rand.randreal(min_value.real, max_value.real, py_ns) - imag = rand.randreal(min_value.imag, max_value.imag, py_ns) - return instantiate_elements( - stack(real, imag), lambda c: Complex(Real(c[0]), Real(c[1])), d=2 - ) - - -class Random(Builtin): - """ -
-
'Random[]' -
gives a uniformly distributed pseudorandom Real number in the range 0 to 1. -
'Random[$type$, $range$]' -
gives a uniformly distributed pseudorandom number of the type $type$, in the specified interval $range$. Possible types are 'Integer', 'Real' or 'Complex'. -
- Legacy function. Superseded by RandomReal, RandomInteger and RandomComplex. - - """ - - rules = { - "Random[Integer]": "RandomInteger[]", - "Random[Integer, zmax_Integer]": "RandomInteger[zmax]", - "Random[Integer, {zmin_Integer, zmax_Integer}]": "RandomInteger[{zmin, zmax}]", - "Random[Real]": "RandomReal[]", - "Random[Real, zmax_?NumberQ]": "RandomReal[zmax]", - "Random[Real, {zmin_Real, zmax_Real}]": "RandomReal[{zmin, zmax}]", - "Random[Complex]": "RandomComplex[]", - "Random[Complex, zmax_Complex]": "RandomComplex[zmax]", - "Random[Complex, {zmin_?NumberQ, zmax_?NumberQ}]": "RandomComplex[{zmin, zmax}]", - } - summary_text = "a random number" + rand.seed() + return SymbolNull # If numpy is not in the system, the following classes are going to be redefined as None. flake8 complains about this. @@ -592,98 +683,33 @@ class Random(Builtin): # implementation. -class _RandomSelection(_RandomBase): - # implementation note: weights are clipped to numpy floats. this might be different from MMA - # where weights might be handled with full dynamic precision support through the whole computation. - # we try to limit the error by normalizing weights with full precision, and then clipping to float. - # since weights are probabilities into a finite set, this should not make a difference. - - messages = { - "wghtv": "The weights on the left-hand side of `1` has to be a list of non-negative numbers " - + "with the same length as the list of items on the right-hand side.", - "lrwl": "`1` has to be a list of items or a rule of the form weights -> choices.", - "smplen": "RandomSample cannot choose `1` samples, as this are more samples than there are in `2`. " - + "Use RandomChoice to choose items from a set with replacing.", - } +class RandomChoice(_RandomSelection): + """ + :WMA: https://reference.wolfram.com/language/ref/RandomChoice.html - def apply(self, domain, size, evaluation): - """%(name)s[domain_, size_]""" - if domain.get_head_name() == "System`Rule": # elements and weights - err, py_weights = self._weights_to_python(domain.elements[0], evaluation) - if py_weights is None: - return err - elements = domain.elements[1].elements - if domain.elements[1].get_head_name() != "System`List" or len( - py_weights - ) != len(elements): - return evaluation.message(self.get_name(), "wghtv", domain) - elif domain.get_head_name() == "System`List": # only elements - py_weights = None - elements = domain.elements - else: - return evaluation.message(self.get_name(), "lrwl", domain) - err, py_size = self._size_to_python(domain, size, evaluation) - if py_size is None: - return err - if not self._replace: # i.e. RandomSample? - n_chosen = reduce(operator_mul, py_size, 1) - if len(elements) < n_chosen: - return evaluation.message("smplen", size, domain), None - with RandomEnv(evaluation) as rand: - return instantiate_elements( - rand.randchoice( - len(elements), size=py_size, replace=self._replace, p=py_weights - ), - lambda i: elements[i], - ) +
- 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(evaluation) for w in weights.elements - ) +
'RandomChoice[$items$]' +
randomly picks one item from $items$. - if ( - is_proper_spec and len(weights.elements) > 1 - ): # normalize before we lose accuracy - norm_weights = Expression( - SymbolDivide, weights, Expression(SymbolTotal, weights) - ).evaluate(evaluation) - if norm_weights is None or not all( - w.is_numeric(evaluation) for w in norm_weights.elements - ): - return evaluation.message(self.get_name(), "wghtv", weights), None - weights = norm_weights +
'RandomChoice[$items$, $n$]' +
randomly picks $n$ items from $items$. Each pick in the $n$ picks happens from the \ + given set of $items$, so each item can be picked any number of times. - py_weights = eval_N(weights, evaluation).to_python() if is_proper_spec else None - if (py_weights is None) or ( - not all(isinstance(w, (int, float)) and w >= 0 for w in py_weights) - ): - return evaluation.message(self.get_name(), "wghtv", weights), None +
'RandomChoice[$items$, {$n1$, $n2$, ...}]' +
randomly picks items from $items$ and arranges the picked items in the nested list \ + structure described by {$n1$, $n2$, ...}. - return False, py_weights +
'RandomChoice[$weights$ -> $items$, $n$]' +
randomly picks $n$ items from $items$ and uses the corresponding numeric values in \ + $weights$ to determine how probable it is for each item in $items$ to get picked (in the \ + long run, items with higher weights will get picked more often than ones with lower weight). +
'RandomChoice[$weights$ -> $items$]' +
randomly picks one items from $items$ using weights $weights$. -class RandomChoice(_RandomSelection): - """ -
-
'RandomChoice[$items$]' -
randomly picks one item from $items$. -
'RandomChoice[$items$, $n$]' -
randomly picks $n$ items from $items$. Each pick in the $n$ picks happens from the - given set of $items$, so each item can be picked any number of times. -
'RandomChoice[$items$, {$n1$, $n2$, ...}]' -
randomly picks items from $items$ and arranges the picked items in the nested list - structure described by {$n1$, $n2$, ...}. -
'RandomChoice[$weights$ -> $items$, $n$]' -
randomly picks $n$ items from $items$ and uses the corresponding numeric values in - $weights$ to determine how probable it is for each item in $items$ to get picked (in the - long run, items with higher weights will get picked more often than ones with lower weight). -
'RandomChoice[$weights$ -> $items$]' -
randomly picks one items from $items$ using weights $weights$. -
'RandomChoice[$weights$ -> $items$, {$n1$, $n2$, ...}]' -
randomly picks a structured list of items from $items$ using weights $weights$. +
'RandomChoice[$weights$ -> $items$, {$n1$, $n2$, ...}]' +
randomly picks a structured list of items from $items$ using weights $weights$.
Note: 'SeedRandom' is used below so we get repeatable "random" numbers that we can test. @@ -706,49 +732,62 @@ class RandomChoice(_RandomSelection): """ _replace = True - summary_text = "choice items at random from a list" + summary_text = "pick items randomly from a given list" class RandomSample(_RandomSelection): """ + :WMA: https://reference.wolfram.com/language/ref/RandomSample.html +
-
'RandomSample[$items$]' -
randomly picks one item from $items$. -
'RandomSample[$items$, $n$]' -
randomly picks $n$ items from $items$. Each pick in the $n$ picks happens after the - previous items picked have been removed from $items$, so each item can be picked at most - once. -
'RandomSample[$items$, {$n1$, $n2$, ...}]' -
randomly picks items from $items$ and arranges the picked items in the nested list - structure described by {$n1$, $n2$, ...}. Each item gets picked at most once. -
'RandomSample[$weights$ -> $items$, $n$]' -
randomly picks $n$ items from $items$ and uses the corresponding numeric values in - $weights$ to determine how probable it is for each item in $items$ to get picked (in the - long run, items with higher weights will get picked more often than ones with lower weight). - Each item gets picked at most once. -
'RandomSample[$weights$ -> $items$]' -
randomly picks one items from $items$ using weights $weights$. Each item gets picked - at most once. -
'RandomSample[$weights$ -> $items$, {$n1$, $n2$, ...}]' -
randomly picks a structured list of items from $items$ using weights $weights$. Each - item gets picked at most once. +
'RandomSample[$items$]' +
randomly picks one item from $items$. + +
'RandomSample[$items$, $n$]' +
randomly picks $n$ items from $items$. Each pick in the $n$ picks happens after the \ + previous items picked have been removed from $items$, so each item can be picked at most \ + once. + +
'RandomSample[$items$, {$n1$, $n2$, ...}]' +
randomly picks items from $items$ and arranges the picked items in the nested list \ + structure described by {$n1$, $n2$, ...}. \ + Each item gets picked at most once. + +
'RandomSample[$weights$ -> $items$, $n$]' +
randomly picks $n$ items from $items$ and uses the corresponding numeric values in \ + $weights$ to determine how probable it is for each item in $items$ to get picked (in the \ + long run, items with higher weights will get picked more often than ones with lower weight). \ + Each item gets picked at most once. + +
'RandomSample[$weights$ -> $items$]' +
randomly picks one items from $items$ using weights $weights$. \ + Each item gets picked at most once. + +
'RandomSample[$weights$ -> $items$, {$n1$, $n2$, ...}]' +
randomly picks a structured list of items from $items$ using weights $weights$. Each \ + item gets picked at most once.
>> SeedRandom[42] >> RandomSample[{a, b, c, d}] = {b, d, a, c} + >> SeedRandom[42] >> RandomSample[{a, b, c, d, e, f, g, h}, 7] = {b, f, a, h, c, e, d} + >> SeedRandom[42] >> RandomSample[{"a", {1, 2}, x, {}}, 3] = {{1, 2}, {}, a} + >> SeedRandom[42] >> RandomSample[Range[10]] = {9, 2, 6, 1, 8, 3, 10, 5, 4, 7} + >> SeedRandom[42] >> RandomSample[Range[100], {2, 3}] = {{84, 54, 71}, {46, 45, 40}} + >> SeedRandom[42] >> RandomSample[Range[100] -> Range[100], 5] = {62, 98, 86, 78, 40} @@ -765,5 +804,5 @@ class RandomSample(_RandomSelection): if not _numpy: # hide symbols from non-numpy envs _RandomSelection = None - RandomChoice = None - RandomSample = None + RandomChoice = None # noqa + RandomSample = None # noqa diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 07f2785bf..475c6a552 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -157,6 +157,8 @@ SymbolPrecision = Symbol("System`Precision") SymbolQuiet = Symbol("System`Quiet") SymbolRGBColor = Symbol("System`RGBColor") +SymbolRandomComplex = Symbol("System`RandomComplex") +SymbolRandomReal = Symbol("System`RandomReal") SymbolRational = Symbol("System`Rational") SymbolRe = Symbol("System`Re") SymbolReal = Symbol("System`Real") From 443d4cfc7bab4a32a687ab8e9862165f6353dc09 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 18 Dec 2022 21:16:19 -0500 Subject: [PATCH 042/121] Remove stray ")" and remove debug code --- mathics/builtin/numbers/randomnumbers.py | 2 +- mathics/core/expression.py | 6 ------ mathics/core/list.py | 6 ------ 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/mathics/builtin/numbers/randomnumbers.py b/mathics/builtin/numbers/randomnumbers.py index b34cceb37..8a9565a56 100644 --- a/mathics/builtin/numbers/randomnumbers.py +++ b/mathics/builtin/numbers/randomnumbers.py @@ -143,7 +143,7 @@ def randchoice(self, n, size, replace, p): class RandomState(Builtin): """ - :WMA: https://reference.wolfram.com/language/ref/RandomState.html) + :WMA: https://reference.wolfram.com/language/ref/RandomState.html
'$RandomState'
is a long number representing the internal state of the \ diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 07f0065ad..4019acda1 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -270,12 +270,6 @@ def _build_elements_properties(self): values = [] for element in self._elements: # Test for the literalness, and the three properties mentioned above - try: - element.is_literal - except: - from trepan.api import debug - - debug() if element.is_literal: values.append(element.value) else: diff --git a/mathics/core/list.py b/mathics/core/list.py index 20e2882b0..19cb603e3 100644 --- a/mathics/core/list.py +++ b/mathics/core/list.py @@ -55,12 +55,6 @@ def __init__( self._is_literal = True values = [] for element in elements: - try: - element.is_literal - except: - from trepan.api import debug - - debug() if element.is_literal: values.append(element.value) else: From 2481381790f0fa79be7f13422311caf572e37e6c Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 19 Dec 2022 23:05:24 -0500 Subject: [PATCH 043/121] Make builtin/atomic/symbols.py current... Bring code in line with current standards --- mathics/builtin/atomic/symbols.py | 83 ++++++++++++++++--------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index 8a035de03..a4a3249b3 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/atomic/symbols.py @@ -2,41 +2,33 @@ """ Symbolic Handling -Symbolic data. Every symbol has a unique name, exists in a certain context or namespace, and can have a variety of type of values and attributes. +Symbolic data. Every symbol has a unique name, exists in a certain context \ +or namespace, and can have a variety of type of values and attributes. """ import re -from mathics.builtin.base import ( - Builtin, - PrefixOperator, - Test, -) - from mathics.builtin.atomic.strings import to_regex - +from mathics.builtin.base import Builtin, PrefixOperator, Test from mathics.core.assignment import get_symbol_values from mathics.core.atoms import String - from mathics.core.attributes import ( - attributes_bitset_to_list, A_HOLD_ALL, A_HOLD_FIRST, A_LOCKED, A_PROTECTED, A_READ_PROTECTED, A_SEQUENCE_HOLD, + attributes_bitset_to_list, ) - -from mathics.core.expression import Expression from mathics.core.convert.expression import to_mathics_list +from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.rules import Rule - from mathics.core.symbols import ( Symbol, - SymbolHoldForm, SymbolFalse, + SymbolHoldForm, SymbolNull, SymbolTrue, SymbolUpSet, @@ -99,6 +91,7 @@ def _get_usage_string(symbol, evaluation, is_long_form: bool, htmlout=False): class Context(Builtin): r""" + :WMA: https://reference.wolfram.com/language/ref/Context.html
'Context[$symbol$]'
yields the name of the context where $symbol$ is defined in. @@ -137,7 +130,7 @@ class Context(Builtin): summary_text = "give the name of the context of a symbol" - def apply(self, symbol, evaluation): + def eval(self, symbol, evaluation): "Context[symbol_]" name = symbol.get_name() @@ -151,6 +144,7 @@ def apply(self, symbol, evaluation): class Definition(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/Definition.html
'Definition[$symbol$]'
prints as the definitions given for $symbol$. @@ -370,21 +364,22 @@ def format_definition_input(self, symbol, evaluation): # In Mathematica 5, this appears under "Types of Values". class DownValues(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/DownValues.html
-
'DownValues[$symbol$]' -
gives the list of downvalues associated with $symbol$. +
'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 + '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, + 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 @@ -397,12 +392,12 @@ class DownValues(Builtin): >> f[a] = a ^ 2 - The default order of patterns can be computed using 'Sort' with + 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 + By assigning values to 'DownValues', you can override the default \ ordering: >> DownValues[g] := {g[x_] :> x ^ 2, g[x_Integer] :> x} >> g[2] @@ -417,7 +412,7 @@ class DownValues(Builtin): attributes = A_HOLD_ALL | A_PROTECTED summary_text = "give a list of transformation rules corresponding to all downvalues defined for a symbol" - def apply(self, symbol, evaluation): + def eval(self, symbol, evaluation): "DownValues[symbol_]" return get_symbol_values(symbol, "DownValues", "down", evaluation) @@ -425,11 +420,13 @@ def apply(self, symbol, evaluation): class Information(PrefixOperator): """ + :WMA: https://reference.wolfram.com/language/ref/Information.html
'Information[$symbol$]'
Prints information about a $symbol$
'Information' does not print information for 'ReadProtected' symbols. + 'Information' uses 'InputForm' to format values. #> a = 2; @@ -596,6 +593,7 @@ def format_definition_input(self, symbol, evaluation, options): class Names(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/Names.html
'Names["$pattern$"]'
returns the list of names matching $pattern$. @@ -626,7 +624,7 @@ class Names(Builtin): summary_text = "find a list of symbols with names matching a pattern" - def apply(self, pattern, evaluation): + def eval(self, pattern, evaluation): "Names[pattern_]" headname = pattern.get_head_name() if headname == "System`StringExpression": @@ -650,6 +648,7 @@ def apply(self, pattern, evaluation): # In Mathematica 5, this appears under "Types of Values". class OwnValues(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/OwnValues.html
'OwnValues[$symbol$]'
gives the list of ownvalue associated with $symbol$. @@ -674,7 +673,7 @@ class OwnValues(Builtin): attributes = A_HOLD_ALL | A_PROTECTED summary_text = "give the rule corresponding to any ownvalue defined for a symbol" - def apply(self, symbol, evaluation): + def eval(self, symbol, evaluation): "OwnValues[symbol_]" return get_symbol_values(symbol, "OwnValues", "own", evaluation) @@ -682,9 +681,10 @@ def apply(self, symbol, evaluation): class Symbol_(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/Symbol.html
-
'Symbol' -
is the head of symbols. +
'Symbol' +
is the head of symbols.
>> Head[x] @@ -711,7 +711,7 @@ class Symbol_(Builtin): summary_text = "the head of a symbol; create a symbol from a name" - def apply(self, string, evaluation): + def eval(self, string, evaluation): "Symbol[string_String]" from mathics.core.parser import is_symbol_name @@ -725,9 +725,10 @@ def apply(self, string, evaluation): class SymbolName(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/SymbolName.html
-
'SymbolName[$s$]' -
returns the name of the symbol $s$ (without any leading +
'SymbolName[$s$]' +
returns the name of the symbol $s$ (without any leading \ context name).
@@ -740,7 +741,7 @@ class SymbolName(Builtin): summary_text = "give the name of a symbol as a string" - def apply(self, symbol, evaluation): + def eval(self, symbol, evaluation): "SymbolName[symbol_Symbol]" # MMA docs say "SymbolName always give the short name, @@ -750,9 +751,10 @@ def apply(self, symbol, evaluation): class SymbolQ(Test): """ + :WMA: https://reference.wolfram.com/language/ref/SymbolName.html
-
'SymbolQ[$x$]' -
is 'True' if $x$ is a symbol, or 'False' otherwise. +
'SymbolQ[$x$]' +
is 'True' if $x$ is a symbol, or 'False' otherwise.
>> SymbolQ[a] @@ -772,9 +774,11 @@ def test(self, expr): # In Mathematica 5, this appears under "Types of Values". class UpValues(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/UpValues.html
'UpValues[$symbol$]' -
gives the list of transformation rules corresponding to upvalues define with $symbol$. +
gives the list of transformation rules corresponding to upvalues \ + define with $symbol$.
>> a + b ^= 2 @@ -793,7 +797,7 @@ class UpValues(Builtin): attributes = A_HOLD_ALL | A_PROTECTED summary_text = "give a list of transformation rules corresponding to upvalues defined for a symbol" - def apply(self, symbol, evaluation): + def eval(self, symbol, evaluation): "UpValues[symbol_]" return get_symbol_values(symbol, "UpValues", "up", evaluation) @@ -801,9 +805,10 @@ def apply(self, symbol, evaluation): class ValueQ(Builtin): """ + :WMA: https://reference.wolfram.com/language/ref/ValueQ.html
-
'ValueQ[$expr$]' -
returns 'True' if and only if $expr$ is defined. +
'ValueQ[$expr$]' +
returns 'True' if and only if $expr$ is defined.
>> ValueQ[x] @@ -819,7 +824,7 @@ class ValueQ(Builtin): attributes = A_HOLD_FIRST | A_PROTECTED summary_text = "test whether a symbol can be considered to have a value" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "ValueQ[expr_]" evaluated_expr = expr.evaluate(evaluation) if expr.sameQ(evaluated_expr): From 67e4328bfa73873c8f61e539449f38f2bbd204e4 Mon Sep 17 00:00:00 2001 From: mmatera Date: Tue, 20 Dec 2022 22:59:39 -0300 Subject: [PATCH 044/121] url in docstrings for modules string - statistic - specialfns --- mathics/builtin/specialfns/bessel.py | 40 +++++++++++++++++++---- mathics/builtin/specialfns/elliptic.py | 8 +++++ mathics/builtin/specialfns/expintegral.py | 6 ++++ mathics/builtin/specialfns/gamma.py | 17 ++++++++++ mathics/builtin/specialfns/zeta.py | 4 +++ mathics/builtin/statistics/dependency.py | 2 +- mathics/builtin/statistics/location.py | 4 +++ mathics/builtin/statistics/orderstats.py | 12 +++++++ mathics/builtin/string/characters.py | 16 +++++++++ mathics/builtin/string/charcodes.py | 4 +++ mathics/builtin/string/operations.py | 25 ++++++++++++++ mathics/builtin/string/patterns.py | 33 +++++++++++++++++-- mathics/builtin/string/regexp.py | 3 ++ 13 files changed, 165 insertions(+), 9 deletions(-) diff --git a/mathics/builtin/specialfns/bessel.py b/mathics/builtin/specialfns/bessel.py index 9b82fc553..46a4bc7ab 100644 --- a/mathics/builtin/specialfns/bessel.py +++ b/mathics/builtin/specialfns/bessel.py @@ -61,7 +61,7 @@ class AiryAi(_MPMathFunction): class AiryAiPrime(_MPMathFunction): """ - Derivative of Airy function (:Sympy: https://docs.sympy.org/latest/modules/functions/special.html#sympy.functions.special.bessel.airyaiprime, :WMA:https://reference.wolfram.com/language/ref/AiryAiPrime.html) + Derivative of Airy function (:Sympy: https://docs.sympy.org/latest/modules/functions/special.html#sympy.functions.special.bessel.airyaiprime, :WMA link:https://reference.wolfram.com/language/ref/AiryAiPrime.html)
'AiryAiPrime[$x$]'
returns the derivative of the Airy function 'AiryAi[$x$]'. @@ -91,6 +91,8 @@ def get_mpmath_function(self, args): class AiryAiZero(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/AiryAiZero.html +
'AiryAiZero[$k$]'
returns the $k$th zero of the Airy function Ai($z$). @@ -150,6 +152,8 @@ def apply_N(self, k, precision, evaluation): class AiryBi(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/AiryBi.html +
'AiryBi[$x$]'
returns the Airy function of the second kind Bi($x$). @@ -181,6 +185,8 @@ class AiryBi(_MPMathFunction): class AiryBiPrime(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/AiryBiPrime.html +
'AiryBiPrime[$x$]'
returns the derivative of the Airy function of the second @@ -211,6 +217,8 @@ def get_mpmath_function(self, args): class AiryBiZero(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/AiryBiZero.html +
'AiryBiZero[$k$]'
returns the $k$th zero of the Airy function Bi($z$). @@ -296,6 +304,7 @@ class AngerJ(_Bessel): class BesselI(_Bessel): """ + :Modified Bessel function of the first kind: https://en.wikipedia.org/wiki/Bessel_function#Bessel_functions_of_the_first_kind:_J%CE%B1 (:Sympy: https://docs.sympy.org/latest/modules/functions/special.html#sympy.functions.special.bessel.besseli, :WMA: https://reference.wolfram.com/language/ref/BesselI.html)
@@ -367,6 +376,7 @@ class BesselJ(_Bessel): class BesselK(_Bessel): """ + :Modified Bessel function of the second kind: https://en.wikipedia.org/wiki/Bessel_function#Modified_Bessel_functions:_I%CE%B1,_K%CE%B1 (:SymPy: https://docs.sympy.org/latest/modules/functions/special.html#sympy.functions.special.bessel.besselk, :WMA:https://reference.wolfram.com/language/ref/BesselJ.html)
@@ -395,6 +405,9 @@ class BesselK(_Bessel): class BesselY(_Bessel): """ + :Bessel function of the second kind: https://en.wikipedia.org/wiki/Bessel_function#Bessel_functions_of_the_second_kind:_Y%CE%B1 (:SymPy: https://docs.sympy.org/latest/modules/functions/special.html#sympy.functions.special.bessel.bessely, :WMA:https://reference.wolfram.com/language/ref/BesselY.html) + +
'BesselY[$n$, $z$]'
returns the Bessel function of the second kind Y_$n$($z$). @@ -431,6 +444,8 @@ class BesselY(_Bessel): class BesselJZero(_Bessel): """ + :WMA link:https://reference.wolfram.com/language/ref/BesselJZero.html +
'BesselJZero[$n$, $k$]'
returns the $k$th zero of the Bessel function of the first kind J_$n$($z$). @@ -450,6 +465,8 @@ class BesselJZero(_Bessel): class BesselYZero(_Bessel): """ + :WMA link:https://reference.wolfram.com/language/ref/BesselYZero.html +
'BesselYZero[$n$, $k$]'
returns the $k$th zero of the Bessel function of the second kind Y_$n$($z$). @@ -472,6 +489,8 @@ class BesselYZero(_Bessel): class HankelH1(_Bessel): """ + :WMA link:https://reference.wolfram.com/language/ref/HankelH1.html +
'HankelH1[$n$, $z$]'
returns the Hankel function of the first kind H_$n$^1 ($z$). @@ -492,6 +511,8 @@ class HankelH1(_Bessel): class HankelH2(_Bessel): """ + :WMA link:https://reference.wolfram.com/language/ref/HankelH2.html +
'HankelH2[$n$, $z$]'
returns the Hankel function of the second kind H_$n$^2 ($z$). @@ -515,6 +536,7 @@ class HankelH2(_Bessel): class KelvinBei(_Bessel): """ + :Kelvin function bei: https://en.wikipedia.org/wiki/Kelvin_functions#bei(x) (:mpmath: https://mpmath.org/doc/current/functions/bessel.html#bei, :WMA: https://reference.wolfram.com/language/ref/KelvinBei.html)
@@ -584,6 +606,7 @@ class KelvinBer(_Bessel): class KelvinKei(_Bessel): """ + :Kelvin function kei: https://en.wikipedia.org/wiki/Kelvin_functions#kei(x) (:mpmath: https://mpmath.org/doc/current/functions/bessel.html#kei, :WMA: https://reference.wolfram.com/language/ref/KelvinKei.html)
@@ -651,7 +674,6 @@ class KelvinKer(_Bessel): class SphericalBesselJ(_Bessel): """ - :Spherical Bessel function of the first kind: https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions (:Sympy: https://docs.sympy.org/latest/modules/functions/special.html#sympy.functions.special.bessel.jn, :WMA: https://reference.wolfram.com/language/ref/SphericalBesselJ.html)
@@ -697,8 +719,8 @@ class SphericalBesselY(_Bessel): class SphericalHankelH1(_Bessel): """ - - :Spherical Bessel function of the first kind: https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions + :Spherical Bessel function of the first kind: https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions\ + (:WMA link:https://reference.wolfram.com/language/ref/SphericalHankelH1.html)
'SphericalHankelH1[$n$, $z$]' @@ -718,7 +740,8 @@ class SphericalHankelH1(_Bessel): class SphericalHankelH2(_Bessel): """ - :Spherical Bessel function of the second kind: https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions + :Spherical Bessel function of the second kind: https://en.wikipedia.org/wiki/Bessel_function#Spherical_Bessel_functions\ + (:WMA link:https://reference.wolfram.com/language/ref/SphericalHankelH2.html)
'SphericalHankelH1[$n$, $z$]' @@ -737,7 +760,10 @@ class SphericalHankelH2(_Bessel): class StruveH(_Bessel): """ - :Struve functions H: https://en.wikipedia.org/wiki/Struve_function + + :Struve functions H: https://en.wikipedia.org/wiki/Struve_function\ + (:WMA link:https://reference.wolfram.com/language/ref/Struve.html) +
'StruveH[$n$, $z$]'
returns the Struve function H_$n$($z$). @@ -785,6 +811,8 @@ class StruveL(_Bessel): class WeberE(_Bessel): """ + :WMA link:https://reference.wolfram.com/language/ref/WeberE.html +
'WeberE[$n$, $z$]'
returns the Weber function E_$n$($z$). diff --git a/mathics/builtin/specialfns/elliptic.py b/mathics/builtin/specialfns/elliptic.py index 9c2ec23f4..bde0facd1 100644 --- a/mathics/builtin/specialfns/elliptic.py +++ b/mathics/builtin/specialfns/elliptic.py @@ -22,6 +22,8 @@ class EllipticE(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/EllipticE.html +
'EllipticE[$m$]'
computes the complete elliptic integral $E$($m$). @@ -72,6 +74,8 @@ def apply_phi_m(self, phi, m, evaluation): class EllipticF(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/EllipticF.html +
'EllipticF[$m$]'
computes the elliptic integral of the first kind $F$($ϕ$|$m$). @@ -108,6 +112,8 @@ def apply(self, phi, m, evaluation): class EllipticK(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/EllipticK.html +
'EllipticK[$m$]'
computes the elliptic integral of the first kind $K$($m$). @@ -148,6 +154,8 @@ def apply(self, m, evaluation): class EllipticPi(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/EllipticPi.html +
'EllipticPi[$n$, $m$]'
computes the elliptic integral of the third kind $Pi$($m$). diff --git a/mathics/builtin/specialfns/expintegral.py b/mathics/builtin/specialfns/expintegral.py index cb473b4e6..912414585 100644 --- a/mathics/builtin/specialfns/expintegral.py +++ b/mathics/builtin/specialfns/expintegral.py @@ -10,6 +10,8 @@ class ExpIntegralE(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ExpIntegralE.html +
'ExpIntegralE[$n$, $z$]'
returns the exponential integral function $E_n(z)$. @@ -27,6 +29,8 @@ class ExpIntegralE(_MPMathFunction): class ExpIntegralEi(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ExpIntegralEi.html +
'ExpIntegralEi[$z$]'
returns the exponential integral function $Ei(z)$. @@ -43,6 +47,8 @@ class ExpIntegralEi(_MPMathFunction): class ProductLog(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ProductLog.html +
'ProductLog[$z$]'
returns the value of the Lambert W function at $z$. diff --git a/mathics/builtin/specialfns/gamma.py b/mathics/builtin/specialfns/gamma.py index 740b7e045..ed172c25b 100644 --- a/mathics/builtin/specialfns/gamma.py +++ b/mathics/builtin/specialfns/gamma.py @@ -42,6 +42,8 @@ class Beta(_MPMathMultiFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Beta.html +
'Beta[$a$, $b$]'
is the Euler's Beta function. @@ -141,6 +143,8 @@ def apply_3(self, z, a, b, evaluation): class Factorial(PostfixOperator, _MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Factorial.html +
'Factorial[$n$]'
'$n$!' @@ -178,6 +182,8 @@ class Factorial(PostfixOperator, _MPMathFunction): class Factorial2(PostfixOperator, _MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Factorial2.html +
'Factorial2[$n$]'
'$n$!!' @@ -261,6 +267,8 @@ def fact2_generic(x): class Gamma(_MPMathMultiFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Gamma.html +
'Gamma[$z$]'
is the gamma function on the complex number $z$. @@ -347,6 +355,8 @@ def from_sympy(self, sympy_name, elements): class LogGamma(_MPMathMultiFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/LogGamma.html + In number theory the logarithm of the gamma function often appears. For positive real numbers, this can be evaluated as 'Log[Gamma[$z$]]'.
@@ -390,6 +400,8 @@ def get_sympy_names(self): class Pochhammer(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Pochhammer.html + 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.
@@ -414,6 +426,8 @@ class Pochhammer(SympyFunction): class PolyGamma(_MPMathMultiFunction): r""" + :WMA link:https://reference.wolfram.com/language/ref/PolyGamma.html + PolyGamma is a meromorphic function on the complex numbers and is defined as a derivative of the logarithm of the gamma function.
PolyGamma[z] @@ -429,6 +443,7 @@ class PolyGamma(_MPMathMultiFunction): >> PolyGamma[3, 5] = -22369 / 3456 + Pi ^ 4 / 15 """ + attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED mpmath_names = { @@ -448,6 +463,8 @@ class PolyGamma(_MPMathMultiFunction): class StieltjesGamma(SympyFunction): r""" + :WMA link:https://reference.wolfram.com/language/ref/StieltjesGamma.html + PolyGamma is a meromorphic function on the complex numbers and is defined as a derivative of the logarithm of the gamma function.
'StieltjesGamma[$n$]' diff --git a/mathics/builtin/specialfns/zeta.py b/mathics/builtin/specialfns/zeta.py index cfb98a8f4..bf3b853a8 100644 --- a/mathics/builtin/specialfns/zeta.py +++ b/mathics/builtin/specialfns/zeta.py @@ -13,6 +13,8 @@ class LerchPhi(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/LerchPhi.html +
'LerchPhi[z,s,a]'
gives the Lerch transcendent Φ(z,s,a). @@ -44,6 +46,8 @@ def apply(self, z, s, a, evaluation): class Zeta(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Zeta.html +
'Zeta[$z$]'
returns the Riemann zeta function of $z$. diff --git a/mathics/builtin/statistics/dependency.py b/mathics/builtin/statistics/dependency.py index 4a4ba22e2..e583ab315 100644 --- a/mathics/builtin/statistics/dependency.py +++ b/mathics/builtin/statistics/dependency.py @@ -32,7 +32,7 @@ class Correlation(Builtin): """ :Pearson correlation coefficient:https://en.wikipedia.org/wiki/Pearson_correlation_coefficient (:WMA: https://reference.wolfram.com/language/ref/Correlation.html) - +
'Correlation[$a$, $b$]'
computes Pearson's correlation of two equal-sized vectors $a$ and $b$. diff --git a/mathics/builtin/statistics/location.py b/mathics/builtin/statistics/location.py index a57528201..0d2750b47 100644 --- a/mathics/builtin/statistics/location.py +++ b/mathics/builtin/statistics/location.py @@ -12,6 +12,8 @@ class Mean(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Mean.html +
'Mean[$list$]'
returns the statistical mean of $list$. @@ -38,6 +40,8 @@ class Mean(Builtin): class Median(_Rectangular): """ + :WMA link:https://reference.wolfram.com/language/ref/Median.html +
'Median[$list$]'
returns the median of $list$. diff --git a/mathics/builtin/statistics/orderstats.py b/mathics/builtin/statistics/orderstats.py index 0e48e0edf..70125e5b7 100644 --- a/mathics/builtin/statistics/orderstats.py +++ b/mathics/builtin/statistics/orderstats.py @@ -31,6 +31,7 @@ class Quantile(Builtin): """ + :Quantile: https://en.wikipedia.org/wiki/Quantile (:WMA: https://reference.wolfram.com/language/ref/Quantile.html) 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. @@ -145,6 +146,7 @@ def ranked(i): class Quartiles(Builtin): """ + :Quartile: https://en.wikipedia.org/wiki/Quartile (:WMA: https://reference.wolfram.com/language/ref/Quartiles.html)
'Quartiles[$list$]'
returns the 1/4, 1/2, and 3/4 quantiles of $list$. @@ -162,6 +164,8 @@ class Quartiles(Builtin): class RankedMax(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/RankedMax.html +
'RankedMax[$list$, $n$]'
returns the $n$th largest element of $list$ (with $n$ = 1 yielding the largest element, @@ -195,6 +199,8 @@ def apply(self, element, n: Integer, evaluation): class RankedMin(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/RankedMin.html +
'RankedMin[$list$, $n$]'
returns the $n$th smallest element of $list$ (with $n$ = 1 yielding the smallest element, $n$ = 2 yielding the second smallest element, and so on). @@ -225,6 +231,8 @@ def apply(self, element, n: Integer, evaluation): class Sort(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Sort.html +
'Sort[$list$]'
sorts $list$ (or the elements of any other expression) according to canonical ordering. @@ -289,6 +297,8 @@ def __gt__(self, other): class TakeLargest(_RankedTakeLargest): """ + :WMA link:https://reference.wolfram.com/language/ref/TakeLargest.html +
'TakeLargest[$list$, $f$, $n$]'
returns the a sorted list of the $n$ largest items in $list$. @@ -316,6 +326,8 @@ def apply(self, element, n, evaluation, options): class TakeSmallest(_RankedTakeSmallest): """ + :WMA link:https://reference.wolfram.com/language/ref/TakeSmallest.html +
'TakeSmallest[$list$, $f$, $n$]'
returns the a sorted list of the $n$ smallest items in $list$. diff --git a/mathics/builtin/string/characters.py b/mathics/builtin/string/characters.py index aba55d5d1..605b5b9d5 100644 --- a/mathics/builtin/string/characters.py +++ b/mathics/builtin/string/characters.py @@ -14,6 +14,8 @@ class Characters(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Characters.html +
'Characters["$string$"]'
returns a list of the characters in $string$. @@ -46,6 +48,8 @@ def apply(self, string, evaluation): class CharacterRange(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/CharacterRange.html +
'CharacterRange["$a$", "$b$"]'
returns a list of the Unicode characters from $a$ to $b$ inclusive. @@ -78,6 +82,8 @@ def apply(self, start, stop, evaluation): class DigitQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DigitQ.html +
'DigitQ[$string$]'
yields 'True' if all the characters in the $string$ are digits, and yields 'False' otherwise. @@ -108,6 +114,8 @@ class DigitQ(Builtin): class LetterQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/LetterQ.html +
'LetterQ[$string$]'
yields 'True' if all the characters in the $string$ are letters, and yields 'False' otherwise. @@ -142,6 +150,8 @@ class LetterQ(Builtin): class LowerCaseQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/LowerCaseQ.html +
'LowerCaseQ[$s$]'
returns True if $s$ consists wholly of lower case characters. @@ -163,6 +173,8 @@ def test(self, s): class ToLowerCase(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ToLowerCase.html +
'ToLowerCase[$s$]'
returns $s$ in all lower case. @@ -182,6 +194,8 @@ def apply(self, s, evaluation): class ToUpperCase(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ToUpperCase.html +
'ToUpperCase[$s$]'
returns $s$ in all upper case. @@ -201,6 +215,8 @@ def apply(self, s, evaluation): class UpperCaseQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/UpperCaseQ.html +
'UpperCaseQ[$s$]'
returns True if $s$ consists wholly of upper case characters. diff --git a/mathics/builtin/string/charcodes.py b/mathics/builtin/string/charcodes.py index 5e7b08219..fd3551377 100644 --- a/mathics/builtin/string/charcodes.py +++ b/mathics/builtin/string/charcodes.py @@ -33,6 +33,8 @@ def unpack_bytes(codes): class ToCharacterCode(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ToCharacterCode.html +
'ToCharacterCode["$string$"]'
converts the string to a list of character codes (Unicode @@ -137,6 +139,8 @@ class _InvalidCodepointError(ValueError): class FromCharacterCode(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FromCharacterCode.html +
'FromCharacterCode[$n$]'
returns the character corresponding to Unicode codepoint $n$. diff --git a/mathics/builtin/string/operations.py b/mathics/builtin/string/operations.py index 0086fa609..884de0cc4 100644 --- a/mathics/builtin/string/operations.py +++ b/mathics/builtin/string/operations.py @@ -77,6 +77,9 @@ def hexdigest(self): class Hash(Builtin): """ + :Hash function:https://en.wikipedia.org/wiki/Hash_function \ + (:WMA link:https://reference.wolfram.com/language/ref/Hash.html) +
'Hash[$expr$]'
returns an integer hash for the given $expr$. @@ -155,6 +158,8 @@ def apply(self, expr, hashtype, outformat, evaluation): class StringDrop(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StringDrop.html +
'StringDrop["$string$", $n$]'
gives $string$ with the first $n$ characters dropped. @@ -255,6 +260,8 @@ def apply(self, string, something, evaluation): class StringInsert(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StringInsert.html +
'StringInsert["$string$", "$snew$", $n$]'
yields a string with $snew$ inserted starting at position $n$ in $string$. @@ -445,6 +452,8 @@ def apply(self, strsource, strnew, pos, evaluation): class StringJoin(BinaryOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/StringJoin.html +
'StringJoin["$s1$", "$s2$", ...]'
returns the concatenation of the strings $s1$, $s2$, . @@ -486,6 +495,8 @@ def apply(self, items, evaluation): class StringLength(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StringLength.html +
'StringLength["$string$"]'
gives the length of $string$. @@ -516,6 +527,8 @@ def apply(self, str, evaluation): class StringPosition(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StringPosition.html +
'StringPosition["$string$", $patt$]'
gives a list of starting and ending positions where $patt$ matches "$string$". @@ -672,6 +685,8 @@ def do_apply(py_string, compiled_patts, py_n, overlap): class StringReplace(_StringFind): """ + :WMA link:https://reference.wolfram.com/language/ref/StringReplace.html +
'StringReplace["$string$", "$a$"->"$b$"]'
replaces each occurrence of $old$ with $new$ in $string$. @@ -789,6 +804,8 @@ def apply(self, string, rule, n, evaluation, options): class StringReverse(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StringReverse.html +
'StringReverse["$string$"]'
reverses the order of the characters in "string". @@ -808,6 +825,8 @@ def apply(self, string, evaluation): class StringRiffle(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StringRiffle.html +
'StringRiffle[{s1, s2, s3, ...}]'
returns a new string by concatenating all the $si$, with spaces inserted between them. @@ -936,6 +955,8 @@ def apply(self, liststr, seps, evaluation): class StringSplit(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StringSplit.html +
'StringSplit[$s$]'
splits the string $s$ at whitespace, discarding the whitespace and returning a list of strings. @@ -1054,6 +1075,8 @@ def apply(self, string, patt, evaluation, options): class StringTake(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StringTake.html +
'StringTake["$string$", $n$]'
gives the first $n$ characters in $string$. @@ -1164,6 +1187,8 @@ def apply_strings(self, strings, spec, evaluation): class StringTrim(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StringTrim.html +
'StringTrim[$s$]'
returns a version of $s$ with whitespace removed from start and end. diff --git a/mathics/builtin/string/patterns.py b/mathics/builtin/string/patterns.py index 9600f4af3..eafd4fa6d 100644 --- a/mathics/builtin/string/patterns.py +++ b/mathics/builtin/string/patterns.py @@ -37,6 +37,8 @@ class DigitCharacter(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DigitCharacter.html +
'DigitCharacter'
represents the digits 0-9. @@ -61,8 +63,10 @@ class DigitCharacter(Builtin): class EndOfLine(Builtin): r""" + :WMA link:https://reference.wolfram.com/language/ref/EndOfLine.html +
-
'EndOfString' +
'EndOfLine'
represents the end of a line in a string.
@@ -82,6 +86,8 @@ class EndOfLine(Builtin): class EndOfString(Builtin): r""" + :WMA link:https://reference.wolfram.com/language/ref/EndOfString.html +
'EndOfString'
represents the end of a string. @@ -95,11 +101,14 @@ class EndOfString(Builtin): = aab . abc """ + summary_text = "end of the whole string" class LetterCharacter(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/LetterCharacter.html +
'LetterCharacter'
represents letters. @@ -118,8 +127,10 @@ class LetterCharacter(Builtin): class StartOfLine(Builtin): r""" + :WMA link:https://reference.wolfram.com/language/ref/StartOfLine.html +
-
'StartOfString' +
'StartOfLine'
represents the start of a line in a string.
@@ -134,11 +145,14 @@ class StartOfLine(Builtin): . , def . , hij} """ + summary_text = "start of a line" class StartOfString(Builtin): r""" + :WMA link:https://reference.wolfram.com/language/ref/StartOfString.html +
'StartOfString'
represents the start of a string. @@ -157,6 +171,8 @@ class StartOfString(Builtin): class StringCases(_StringFind): """ + :WMA link:https://reference.wolfram.com/language/ref/StringCases.html +
'StringCases["$string$", $pattern$]'
gives all occurences of $pattern$ in $string$. @@ -229,6 +245,8 @@ def apply(self, string, rule, n, evaluation, options): class StringExpression(BinaryOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/StringExpression.html +
'StringExpression[s_1, s_2, ...]'
represents a sequence of strings and symbolic string objects $s_i$. @@ -265,6 +283,8 @@ def apply(self, args, evaluation): class StringFreeQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StringFreeQ.html +
'StringFreeQ["$string$", $patt$]'
returns True if no substring in $string$ matches the string expression $patt$, and returns False otherwise. @@ -363,6 +383,8 @@ def apply(self, string, patt, evaluation, options): class StringMatchQ(Builtin): r""" + :WMA link:https://reference.wolfram.com/language/ref/StringMatchQ.html +
'StringMatchQ["string", $patern$]'
checks is "string" matches $pattern$ @@ -469,6 +491,8 @@ def apply(self, string, patt, evaluation, options): class WhitespaceCharacter(Builtin): r""" + :WMA link:https://reference.wolfram.com/language/ref/WhitespaceCharacter.html +
'WhitespaceCharacter'
represents a single whitespace character. @@ -486,12 +510,15 @@ class WhitespaceCharacter(Builtin): >> StringMatchQ[" \n", Whitespace] = True """ + summary_text = "space, newline, tab, or other whitespace character" # strings.to_regex() seems to have the implementation here. class WordBoundary(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/WordBoundary.html +
'WordBoundary'
represents the boundary between words. @@ -506,6 +533,8 @@ class WordBoundary(Builtin): class WordCharacter(Builtin): r""" + :WMA link:https://reference.wolfram.com/language/ref/WordCharacter.html +
'WordCharacter'
represents a single letter or digit character. diff --git a/mathics/builtin/string/regexp.py b/mathics/builtin/string/regexp.py index 9da5ae957..0493b568d 100644 --- a/mathics/builtin/string/regexp.py +++ b/mathics/builtin/string/regexp.py @@ -10,6 +10,8 @@ # builtin.strings.atomic.to_regex seems to have the implementation. class RegularExpression(Builtin): r""" + :WMA link:https://reference.wolfram.com/language/ref/RegularExpression.html +
'RegularExpression["regex"]'
represents the regex specified by the string $"regex"$. @@ -30,4 +32,5 @@ class RegularExpression(Builtin): : Element RegularExpression[2] is not a valid string or pattern element in RegularExpression[2]. = StringSplit[ab23c, RegularExpression[2]] """ + summary_text = "string to regular expression." From e590d19d3a51b632e8603b593c4c9d7fe54c3cdb Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 21 Dec 2022 20:34:56 -0500 Subject: [PATCH 045/121] Address review observations... Also isort imports, split long WMA lines, and adjust dl indents where they were found to be off --- mathics/builtin/specialfns/bessel.py | 17 +++-- mathics/builtin/specialfns/elliptic.py | 13 +--- mathics/builtin/specialfns/expintegral.py | 6 +- mathics/builtin/specialfns/gamma.py | 38 +++++----- mathics/builtin/statistics/orderstats.py | 11 +-- mathics/builtin/string/characters.py | 12 +-- mathics/builtin/string/patterns.py | 89 ++++++++++++----------- 7 files changed, 94 insertions(+), 92 deletions(-) diff --git a/mathics/builtin/specialfns/bessel.py b/mathics/builtin/specialfns/bessel.py index 46a4bc7ab..3a861eb9c 100644 --- a/mathics/builtin/specialfns/bessel.py +++ b/mathics/builtin/specialfns/bessel.py @@ -4,14 +4,9 @@ import mpmath - from mathics.builtin.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin from mathics.core.atoms import Integer -from mathics.core.convert.mpmath import from_mpmath -from mathics.core.number import machine_precision, get_precision, PrecisionValueError -from mathics.core.number import prec as _prec - from mathics.core.attributes import ( A_LISTABLE, A_N_HOLD_FIRST, @@ -19,6 +14,13 @@ A_PROTECTED, A_READ_PROTECTED, ) +from mathics.core.convert.mpmath import from_mpmath +from mathics.core.number import ( + PrecisionValueError, + get_precision, + machine_precision, + prec as _prec, +) class _Bessel(_MPMathFunction): @@ -761,8 +763,9 @@ class SphericalHankelH2(_Bessel): class StruveH(_Bessel): """ - :Struve functions H: https://en.wikipedia.org/wiki/Struve_function\ - (:WMA link:https://reference.wolfram.com/language/ref/Struve.html) + :Struve functions H: + https://en.wikipedia.org/wiki/Struve_function\ + (:WMA:https://reference.wolfram.com/language/ref/StruveH.html)
'StruveH[$n$, $z$]' diff --git a/mathics/builtin/specialfns/elliptic.py b/mathics/builtin/specialfns/elliptic.py index bde0facd1..23bf3b64f 100644 --- a/mathics/builtin/specialfns/elliptic.py +++ b/mathics/builtin/specialfns/elliptic.py @@ -5,20 +5,15 @@ """ -from mathics.core.attributes import ( - A_LISTABLE, - A_NUMERIC_FUNCTION, - A_PROTECTED, -) +import sympy + from mathics.builtin.base import SympyFunction from mathics.core.atoms import Integer +from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED from mathics.core.convert.expression import to_numeric_sympy_args from mathics.core.convert.sympy import from_sympy - from mathics.eval.numerify import numerify -import sympy - class EllipticE(SympyFunction): """ @@ -77,7 +72,7 @@ class EllipticF(SympyFunction): :WMA link:https://reference.wolfram.com/language/ref/EllipticF.html
-
'EllipticF[$m$]' +
'EllipticF[$phi$, $m$]'
computes the elliptic integral of the first kind $F$($ϕ$|$m$).
diff --git a/mathics/builtin/specialfns/expintegral.py b/mathics/builtin/specialfns/expintegral.py index 912414585..63cc79849 100644 --- a/mathics/builtin/specialfns/expintegral.py +++ b/mathics/builtin/specialfns/expintegral.py @@ -32,7 +32,7 @@ class ExpIntegralEi(_MPMathFunction): :WMA link:https://reference.wolfram.com/language/ref/ExpIntegralEi.html
-
'ExpIntegralEi[$z$]' +
'ExpIntegralEi[$z$]'
returns the exponential integral function $Ei(z)$.
@@ -50,8 +50,8 @@ class ProductLog(_MPMathFunction): :WMA link:https://reference.wolfram.com/language/ref/ProductLog.html
-
'ProductLog[$z$]' -
returns the value of the Lambert W function at $z$. +
'ProductLog[$z$]' +
returns the value of the Lambert W function at $z$.
The defining equation: diff --git a/mathics/builtin/specialfns/gamma.py b/mathics/builtin/specialfns/gamma.py index ed172c25b..086f7a3d7 100644 --- a/mathics/builtin/specialfns/gamma.py +++ b/mathics/builtin/specialfns/gamma.py @@ -2,32 +2,23 @@ Gamma and Related Functions """ import sys -import sympy + import mpmath +import sympy from mathics.builtin.arithmetic import ( _MPMathFunction, _MPMathMultiFunction, call_mpmath, ) -from mathics.builtin.base import SympyFunction, PostfixOperator -from mathics.core.atoms import ( - Integer, - Integer0, - Integer1, - Number, -) -from mathics.core.attributes import ( - A_LISTABLE, - A_NUMERIC_FUNCTION, - A_PROTECTED, -) +from mathics.builtin.base import PostfixOperator, SympyFunction +from mathics.core.atoms import Integer, Integer0, Integer1, Number +from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED from mathics.core.convert.mpmath import from_mpmath from mathics.core.convert.python import from_python from mathics.core.convert.sympy import from_sympy from mathics.core.expression import Expression -from mathics.eval.nevaluator import eval_N -from mathics.core.number import min_prec, dps +from mathics.core.number import dps, min_prec from mathics.core.symbols import Symbol, SymbolSequence from mathics.core.systemsymbols import ( SymbolAutomatic, @@ -36,7 +27,7 @@ SymbolGamma, SymbolIndeterminate, ) - +from mathics.eval.nevaluator import eval_N from mathics.eval.numerify import numerify @@ -143,7 +134,13 @@ def apply_3(self, z, a, b, evaluation): class Factorial(PostfixOperator, _MPMathFunction): """ - :WMA link:https://reference.wolfram.com/language/ref/Factorial.html + :Factorial: + https://en.wikipedia.org/wiki/Factorial ( + :SymPy:https://docs.sympy.org/latest/modules/functions/combinatorial.html#factorial, + :mpmath: + https://mpmath.org/doc/current/functions/gamma.html#mpmath.factorial, + :WMA: + https://reference.wolfram.com/language/ref/Factorial.html)
'Factorial[$n$]' @@ -267,7 +264,12 @@ def fact2_generic(x): class Gamma(_MPMathMultiFunction): """ - :WMA link:https://reference.wolfram.com/language/ref/Gamma.html + :Gamma function: + https://en.wikipedia.org/wiki/Gamma_function ( + :SymPy:https://docs.sympy.org/latest/modules/functions/special.html#module-sympy.functions.special.gamma_functions, + :mpmath: + https://mpmath.org/doc/current/functions/gamma.html#gamma, + :WMA:https://reference.wolfram.com/language/ref/Gamma.html)
'Gamma[$z$]' diff --git a/mathics/builtin/statistics/orderstats.py b/mathics/builtin/statistics/orderstats.py index 70125e5b7..2ed6712e1 100644 --- a/mathics/builtin/statistics/orderstats.py +++ b/mathics/builtin/statistics/orderstats.py @@ -8,7 +8,7 @@ Important special cases of order statistics are finding minimum and maximum value of a sample and sample quantiles. """ -from mpmath import floor as mpfloor, ceil as mpceil +from mpmath import ceil as mpceil, floor as mpfloor from mathics.algorithm.introselect import introselect from mathics.builtin.base import Builtin @@ -16,13 +16,8 @@ from mathics.core.atoms import Atom, Integer, Symbol, SymbolTrue from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import ( - SymbolFloor, - SymbolPlus, - SymbolTimes, -) +from mathics.core.symbols import SymbolFloor, SymbolPlus, SymbolTimes from mathics.core.systemsymbols import SymbolSubtract - from mathics.eval.numerify import numerify SymbolRankedMax = Symbol("RankedMax") @@ -329,7 +324,7 @@ class TakeSmallest(_RankedTakeSmallest): :WMA link:https://reference.wolfram.com/language/ref/TakeSmallest.html
-
'TakeSmallest[$list$, $f$, $n$]' +
'TakeSmallest[$list$, $n$]'
returns the a sorted list of the $n$ smallest items in $list$.
diff --git a/mathics/builtin/string/characters.py b/mathics/builtin/string/characters.py index 605b5b9d5..b1265f839 100644 --- a/mathics/builtin/string/characters.py +++ b/mathics/builtin/string/characters.py @@ -176,8 +176,8 @@ class ToLowerCase(Builtin): :WMA link:https://reference.wolfram.com/language/ref/ToLowerCase.html
-
'ToLowerCase[$s$]' -
returns $s$ in all lower case. +
'ToLowerCase[$s$]' +
returns $s$ in all lower case.
>> ToLowerCase["New York"] @@ -197,8 +197,8 @@ class ToUpperCase(Builtin): :WMA link:https://reference.wolfram.com/language/ref/ToUpperCase.html
-
'ToUpperCase[$s$]' -
returns $s$ in all upper case. +
'ToUpperCase[$s$]' +
returns $s$ in all upper case.
>> ToUpperCase["New York"] @@ -218,8 +218,8 @@ class UpperCaseQ(Test): :WMA link:https://reference.wolfram.com/language/ref/UpperCaseQ.html
-
'UpperCaseQ[$s$]' -
returns True if $s$ consists wholly of upper case characters. +
'UpperCaseQ[$s$]' +
returns True if $s$ consists wholly of upper case characters.
>> UpperCaseQ["ABC"] diff --git a/mathics/builtin/string/patterns.py b/mathics/builtin/string/patterns.py index eafd4fa6d..601bfa7c5 100644 --- a/mathics/builtin/string/patterns.py +++ b/mathics/builtin/string/patterns.py @@ -5,31 +5,20 @@ import re - from mathics.builtin.atomic.strings import ( - _StringFind, _evaluate_match, - _pattern_search, _parallel_match, + _pattern_search, + _StringFind, anchor_pattern, to_regex, ) - from mathics.builtin.base import BinaryOperator, Builtin - -from mathics.core.atoms import ( - Integer1, - String, -) +from mathics.core.atoms import Integer1, String from mathics.core.attributes import A_FLAT, A_LISTABLE, A_ONE_IDENTITY, A_PROTECTED from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import ( - Symbol, - SymbolFalse, - SymbolTrue, -) - +from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue SymbolStringMatchQ = Symbol("StringMatchQ") SymbolStringExpression = Symbol("StringExpression") @@ -37,7 +26,8 @@ class DigitCharacter(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/DigitCharacter.html + :WMA link: + https://reference.wolfram.com/language/ref/DigitCharacter.html
'DigitCharacter' @@ -63,10 +53,11 @@ class DigitCharacter(Builtin): class EndOfLine(Builtin): r""" - :WMA link:https://reference.wolfram.com/language/ref/EndOfLine.html + :WMA link: + https://reference.wolfram.com/language/ref/EndOfLine.html
-
'EndOfLine' +
'EndOfLine'
represents the end of a line in a string.
@@ -86,10 +77,11 @@ class EndOfLine(Builtin): class EndOfString(Builtin): r""" - :WMA link:https://reference.wolfram.com/language/ref/EndOfString.html + :WMA link: + https://reference.wolfram.com/language/ref/EndOfString.html
-
'EndOfString' +
'EndOfString'
represents the end of a string.
@@ -107,7 +99,8 @@ class EndOfString(Builtin): class LetterCharacter(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/LetterCharacter.html + :WMA link: + https://reference.wolfram.com/language/ref/LetterCharacter.html
'LetterCharacter' @@ -127,10 +120,11 @@ class LetterCharacter(Builtin): class StartOfLine(Builtin): r""" - :WMA link:https://reference.wolfram.com/language/ref/StartOfLine.html + :WMA link: + https://reference.wolfram.com/language/ref/StartOfLine.html
-
'StartOfLine' +
'StartOfLine'
represents the start of a line in a string.
@@ -151,10 +145,11 @@ class StartOfLine(Builtin): class StartOfString(Builtin): r""" - :WMA link:https://reference.wolfram.com/language/ref/StartOfString.html + :WMA link: + https://reference.wolfram.com/language/ref/StartOfString.html
-
'StartOfString' +
'StartOfString'
represents the start of a string.
@@ -171,7 +166,8 @@ class StartOfString(Builtin): class StringCases(_StringFind): """ - :WMA link:https://reference.wolfram.com/language/ref/StringCases.html + :WMA link: + https://reference.wolfram.com/language/ref/StringCases.html
'StringCases["$string$", $pattern$]' @@ -283,17 +279,23 @@ def apply(self, args, evaluation): class StringFreeQ(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/StringFreeQ.html + :WMA link: + https://reference.wolfram.com/language/ref/StringFreeQ.html
-
'StringFreeQ["$string$", $patt$]' -
returns True if no substring in $string$ matches the string expression $patt$, and returns False otherwise. -
'StringFreeQ[{"s1", "s2", ...}, patt]' -
returns the list of results for each element of string list. -
'StringFreeQ["string", {p1, p2, ...}]' -
returns True if no substring matches any of the $pi$. -
'StringFreeQ[patt]' -
represents an operator form of StringFreeQ that can be applied to an expression. +
'StringFreeQ["$string$", $patt$]' +
returns True if no substring in $string$ matches the string \ + expression $patt$, and returns False otherwise. + +
'StringFreeQ[{"s1", "s2", ...}, patt]' +
returns the list of results for each element of string list. + +
'StringFreeQ["string", {p1, p2, ...}]' +
returns True if no substring matches any of the $pi$. + +
'StringFreeQ[patt]' +
represents an operator form of StringFreeQ that can be applied \ + to an expression.
>> StringFreeQ["mathics", "m" ~~ __ ~~ "s"] @@ -383,12 +385,14 @@ def apply(self, string, patt, evaluation, options): class StringMatchQ(Builtin): r""" - :WMA link:https://reference.wolfram.com/language/ref/StringMatchQ.html + :WMA link: + https://reference.wolfram.com/language/ref/StringMatchQ.html
-
'StringMatchQ["string", $patern$]' -
checks is "string" matches $pattern$ +
'StringMatchQ["string", $patern$]' +
checks is "string" matches $pattern$
+ >> StringMatchQ["abc", "abc"] = True @@ -491,7 +495,8 @@ def apply(self, string, patt, evaluation, options): class WhitespaceCharacter(Builtin): r""" - :WMA link:https://reference.wolfram.com/language/ref/WhitespaceCharacter.html + :WMA link: + https://reference.wolfram.com/language/ref/WhitespaceCharacter.html
'WhitespaceCharacter' @@ -517,7 +522,8 @@ class WhitespaceCharacter(Builtin): # strings.to_regex() seems to have the implementation here. class WordBoundary(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/WordBoundary.html + :WMA link: + https://reference.wolfram.com/language/ref/WordBoundary.html
'WordBoundary' @@ -533,7 +539,8 @@ class WordBoundary(Builtin): class WordCharacter(Builtin): r""" - :WMA link:https://reference.wolfram.com/language/ref/WordCharacter.html + :WMA link: + https://reference.wolfram.com/language/ref/WordCharacter.html
'WordCharacter' From 02399b67d62ad0fabd3f5b076637ed04a3617239 Mon Sep 17 00:00:00 2001 From: mmatera Date: Fri, 23 Dec 2022 15:27:06 -0300 Subject: [PATCH 046/121] doc for required modules --- mathics/builtin/binary/bytearray.py | 2 + mathics/builtin/colors/color_operations.py | 12 +++ mathics/builtin/drawing/image.py | 116 +++++++++++++++++++++ mathics/builtin/list/eol.py | 52 ++++++++- 4 files changed, 181 insertions(+), 1 deletion(-) diff --git a/mathics/builtin/binary/bytearray.py b/mathics/builtin/binary/bytearray.py index 7eed69a03..a7b254d34 100644 --- a/mathics/builtin/binary/bytearray.py +++ b/mathics/builtin/binary/bytearray.py @@ -19,6 +19,8 @@ class ByteArray(Builtin): r""" + :WMA link:https://reference.wolfram.com/language/ref/ByteArray.html +
'ByteArray[{$b_1$, $b_2$, ...}]'
Represents a sequence of Bytes $b_1$, $b_2$, ... diff --git a/mathics/builtin/colors/color_operations.py b/mathics/builtin/colors/color_operations.py index 7a8d77f2e..caced3147 100644 --- a/mathics/builtin/colors/color_operations.py +++ b/mathics/builtin/colors/color_operations.py @@ -42,6 +42,8 @@ class Blend(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Blend.html +
'Blend[{$c1$, $c2$}]'
represents the color between $c1$ and $c2$. @@ -154,6 +156,8 @@ def apply(self, colors, u, evaluation): class ColorConvert(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ColorConvert.html +
'ColorConvert[$c$, $colspace$]'
returns the representation of $c$ in the color space $colspace$. $c$ @@ -208,6 +212,8 @@ def apply(self, input, colorspace, evaluation): class ColorNegate(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ColorNegate.html +
'ColorNegate[$image$]'
returns the negative of $image$ in which colors have been negated. @@ -239,6 +245,8 @@ def apply_for_image(self, image, evaluation): class Darker(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Darker.html +
'Darker[$c$, $f$]'
is equivalent to 'Blend[{$c$, Black}, $f$]'. @@ -262,6 +270,8 @@ class Darker(Builtin): class DominantColors(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/DominantColors.html +
'DominantColors[$image$]'
gives a list of colors which are dominant in the given image. @@ -433,6 +443,8 @@ def result(): class Lighter(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Lighter.html +
'Lighter[$c$, $f$]'
is equivalent to 'Blend[{$c$, White}, $f$]'. diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index a09e56f85..a3b398491 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -95,6 +95,8 @@ class _ImageTest(Test): class _SkimageBuiltin(_ImageBuiltin): """ + ## :native: + Image Builtins that require scikit-image. """ @@ -146,6 +148,8 @@ def extract(im, evaluation): class ImageImport(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageImport.html +
'ImageImport["path"]'
import an image from the file "path". @@ -186,6 +190,8 @@ def apply(self, path, evaluation): class ImageExport(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageExport.html +
'ImageExport["path", $image$]'
export $image$ as file in "path". @@ -251,6 +257,8 @@ def apply(self, image, args, evaluation): class ImageAdd(_ImageArithmetic): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageAdd.html +
'ImageAdd[$image$, $expr_1$, $expr_2$, ...]'
adds all $expr_i$ to $image$ where each $expr_i$ must be an image or a real number. @@ -287,6 +295,8 @@ class ImageAdd(_ImageArithmetic): class ImageSubtract(_ImageArithmetic): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageSubtract.html +
'ImageSubtract[$image$, $expr_1$, $expr_2$, ...]'
subtracts all $expr_i$ from $image$ where each $expr_i$ must be an image or a real number. @@ -313,6 +323,8 @@ class ImageSubtract(_ImageArithmetic): class ImageMultiply(_ImageArithmetic): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageMultiply.html +
'ImageMultiply[$image$, $expr_1$, $expr_2$, ...]'
multiplies all $expr_i$ with $image$ where each $expr_i$ must be an image or a real number. @@ -344,6 +356,8 @@ class ImageMultiply(_ImageArithmetic): class RandomImage(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/RandomImage.html +
'RandomImage[$max$]'
creates an image of random pixels with values 0 to $max$. @@ -417,6 +431,9 @@ def apply(self, minval, maxval, w, h, evaluation, options): class ImageResize(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/ImageResize.html +
'ImageResize[$image$, $width$]'
@@ -603,6 +620,7 @@ def apply_resize_width_height(self, image, width, height, evaluation, options): class ImageReflect(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageReflect.html
'ImageReflect[$image$]'
Flips $image$ top to bottom. @@ -680,6 +698,9 @@ def no_op(i): class ImageRotate(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/ImageRotate.html +
'ImageRotate[$image$]'
Rotates $image$ 90 degrees counterclockwise. @@ -735,6 +756,8 @@ def rotate(im): class ImagePartition(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ImagePartition.html +
'ImagePartition[$image$, $s$]'
Partitions an image into an array of $s$ x $s$ pixel subimages. @@ -796,6 +819,9 @@ def apply(self, image, w: Integer, h: Integer, evaluation): class ImageAdjust(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/ImageAdjust.html +
'ImageAdjust[$image$]'
adjusts the levels in $image$. @@ -860,6 +886,8 @@ def apply_contrast_brightness_gamma(self, image, c, b, g, evaluation): class Blur(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/Blur.html +
'Blur[$image$]'
gives a blurred version of $image$. @@ -883,6 +911,9 @@ class Blur(_ImageBuiltin): class Sharpen(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/Sharpen.html +
'Sharpen[$image$]'
gives a sharpened version of $image$. @@ -909,6 +940,8 @@ def apply(self, image, r, evaluation): class GaussianFilter(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/GaussianFilter.html +
'GaussianFilter[$image$, $r$]'
blurs $image$ using a Gaussian blur filter of radius $r$. @@ -936,6 +969,9 @@ def apply_radius(self, image, radius, evaluation): class PillowImageFilter(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/PillowImageFilter.html +
'PillowImageFilter[$image$, "filtername"]'
applies an image filter "filtername" from the pillow library. @@ -951,6 +987,8 @@ def compute(self, image, f): class MinFilter(PillowImageFilter): """ + :WMA link:https://reference.wolfram.com/language/ref/MinFilter.html +
'MinFilter[$image$, $r$]'
gives $image$ with a minimum filter of radius $r$ applied on it. This always @@ -971,6 +1009,9 @@ def apply(self, image, r: Integer, evaluation): class MaxFilter(PillowImageFilter): """ + + :WMA link:https://reference.wolfram.com/language/ref/MaxFilter.html +
'MaxFilter[$image$, $r$]'
gives $image$ with a maximum filter of radius $r$ applied on it. This always @@ -991,6 +1032,8 @@ def apply(self, image, r: Integer, evaluation): class MedianFilter(PillowImageFilter): """ + :WMA link:https://reference.wolfram.com/language/ref/MedianFilter.html +
'MedianFilter[$image$, $r$]'
gives $image$ with a median filter of radius $r$ applied on it. This always @@ -1011,6 +1054,9 @@ def apply(self, image, r: Integer, evaluation): class EdgeDetect(_SkimageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/EdgeDetect.html +
'EdgeDetect[$image$]'
returns an image showing the edges in $image$. @@ -1053,6 +1099,9 @@ def _matrix(rows): class BoxMatrix(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/BoxMatrix.html +
'BoxMatrix[$s]'
Gives a box shaped kernel of size 2 $s$ + 1. @@ -1073,6 +1122,8 @@ def apply(self, r, evaluation): class DiskMatrix(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/DiskMatrix.html +
'DiskMatrix[$s]'
Gives a disk shaped kernel of size 2 $s$ + 1. @@ -1101,6 +1152,9 @@ def rows(): class DiamondMatrix(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/DiamondMatrix.html +
'DiamondMatrix[$s]'
Gives a diamond shaped kernel of size 2 $s$ + 1. @@ -1136,6 +1190,8 @@ def rows(): class ImageConvolve(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageConvolve.html +
'ImageConvolve[$image$, $kernel$]'
Computes the convolution of $image$ using $kernel$. @@ -1186,6 +1242,8 @@ def apply(self, image, k, evaluation): class Dilation(_MorphologyFilter): """ + :WMA link:https://reference.wolfram.com/language/ref/Dilation.html +
'Dilation[$image$, $ker$]'
Gives the morphological dilation of $image$ with respect to structuring element $ker$. @@ -1201,6 +1259,8 @@ class Dilation(_MorphologyFilter): class Erosion(_MorphologyFilter): """ + :WMA link:https://reference.wolfram.com/language/ref/Erosion.html +
'Erosion[$image$, $ker$]'
Gives the morphological erosion of $image$ with respect to structuring element $ker$. @@ -1216,6 +1276,8 @@ class Erosion(_MorphologyFilter): class Opening(_MorphologyFilter): """ + :WMA link:https://reference.wolfram.com/language/ref/Opening.html +
'Opening[$image$, $ker$]'
Gives the morphological opening of $image$ with respect to structuring element $ker$. @@ -1231,6 +1293,8 @@ class Opening(_MorphologyFilter): class Closing(_MorphologyFilter): """ + :WMA link:https://reference.wolfram.com/language/ref/Closing.html +
'Closing[$image$, $ker$]'
Gives the morphological closing of $image$ with respect to structuring element $ker$. @@ -1246,6 +1310,8 @@ class Closing(_MorphologyFilter): class MorphologicalComponents(_SkimageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/MorphologicalComponents.html +
'MorphologicalComponents[$image$]'
Builds a 2-D array in which each pixel of $image$ is replaced @@ -1277,6 +1343,8 @@ def apply(self, image, t, evaluation): class ImageColorSpace(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageColorSpace.html +
'ImageColorSpace[$image$]'
gives $image$'s color space, e.g. "RGB" or "CMYK". @@ -1296,6 +1364,8 @@ def apply(self, image, evaluation): class ColorQuantize(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ColorQuantize.html +
'ColorQuantize[$image$, $n$]'
gives a version of $image$ using only $n$ colors. @@ -1334,6 +1404,9 @@ def apply(self, image, n: Integer, evaluation): class Threshold(_SkimageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/Threshold.html +
'Threshold[$image$]'
gives a value suitable for binarizing $image$. @@ -1386,6 +1459,8 @@ def apply(self, image, evaluation, options): class Binarize(_SkimageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/Binarize.html +
'Binarize[$image$]'
gives a binarized version of $image$, in which each pixel is either 0 or 1. @@ -1432,6 +1507,9 @@ def apply_t1_t2(self, image, t1, t2, evaluation): class ColorSeparate(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/ColorSeparate.html +
'ColorSeparate[$image$]'
Gives each channel of $image$ as a separate grayscale image. @@ -1454,6 +1532,8 @@ def apply(self, image, evaluation): class ColorCombine(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ColorCombine.html +
'ColorCombine[$channels$, $colorspace$]'
Gives an image with $colorspace$ and the respective components described by the given channels. @@ -1521,6 +1601,9 @@ def _linearize(a): class Colorize(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/Colorize.html +
'Colorize[$values$]'
returns an image where each number in the rectangular matrix $values$ is a pixel and each @@ -1585,6 +1668,9 @@ def apply(self, values, evaluation, options): class ImageData(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/ImageData.html +
'ImageData[$image$]'
gives a list of all color values of $image$ as a matrix. @@ -1631,6 +1717,7 @@ def apply(self, image, stype, evaluation): class ImageTake(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageTake.html
'ImageTake[$image$, $n$]'
gives the first $n$ rows of $image$. @@ -1683,6 +1770,9 @@ def apply_rows_cols( class PixelValue(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/PixelValue.html +
'PixelValue[$image$, {$x$, $y$}]'
gives the value of the pixel at position {$x$, $y$} in $image$. @@ -1728,6 +1818,8 @@ def apply(self, image, x, y, evaluation): class PixelValuePositions(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/PixelValuePositions.html +
'PixelValuePositions[$image$, $val$]'
gives the positions of all pixels in $image$ that have value $val$. @@ -1779,6 +1871,9 @@ def apply(self, image, val, d, evaluation): class ImageDimensions(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/ImageDimensions.html +
'ImageDimensions[$image$]'
Returns the dimensions of $image$ in pixels. @@ -1806,6 +1901,8 @@ def apply(self, image, evaluation): class ImageAspectRatio(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageAspectRatio.html +
'ImageAspectRatio[$image$]'
gives the aspect ratio of $image$. @@ -1829,6 +1926,9 @@ def apply(self, image, evaluation): class ImageChannels(_ImageBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/ImageChannels.html +
'ImageChannels[$image$]'
gives the number of channels in $image$. @@ -1851,6 +1951,7 @@ def apply(self, image, evaluation): class ImageType(_ImageBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageType.html
'ImageType[$image$]'
gives the interval storage type of $image$, e.g. "Real", "Bit32", or "Bit". @@ -1877,6 +1978,9 @@ def apply(self, image, evaluation): class BinaryImageQ(_ImageTest): """ + + :WMA link:https://reference.wolfram.com/language/ref/BinaryImageQ.html +
'BinaryImageQ[$image]'
returns True if the pixels of $image are binary bit values, and False otherwise. @@ -1914,6 +2018,9 @@ def _image_pixels(matrix): class ImageQ(_ImageTest): """ + + :WMA link:https://reference.wolfram.com/language/ref/ImageQ.html +
'ImageQ[Image[$pixels]]'
returns True if $pixels has dimensions from which an Image can be constructed, and False otherwise. @@ -2141,6 +2248,9 @@ def options(self): class ImageAtom(AtomBuiltin): """ + + :WMA link:https://reference.wolfram.com/language/ref/ImageAtom.html +
'Image[...]'
produces the internal representation of an image from an array @@ -2172,6 +2282,9 @@ def apply_create(self, array, evaluation): class TextRecognize(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/TextRecognize.html +
'TextRecognize[{$image$}]'
Recognizes text in $image$ and returns it as string. @@ -2240,6 +2353,9 @@ def apply(self, image, evaluation, options): class WordCloud(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/WordCloud.html +
'WordCloud[{$word1$, $word2$, ...}]'
Gives a word cloud with the given list of words. diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index 15cccabaa..05be60604 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -62,6 +62,8 @@ class Append(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Append.html +
'Append[$expr$, $elem$]'
returns $expr$ with $elem$ appended. @@ -101,6 +103,8 @@ def apply(self, expr, item, evaluation): class AppendTo(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/AppendTo.html +
'AppendTo[$s$, $elem$]'
append $elem$ to value of $s$ and sets $s$ to the result. @@ -154,6 +158,8 @@ def apply(self, s, element, evaluation): class Cases(Builtin): r""" + :WMA link:https://reference.wolfram.com/language/ref/Cases.html +
'Cases[$list$, $pattern$]'
returns the elements of $list$ that match $pattern$. @@ -261,6 +267,8 @@ def callback(level): class Count(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Count.html +
'Count[$list$, $pattern$]'
returns the number of times $pattern$ appears in $list$. @@ -285,6 +293,8 @@ class Count(Builtin): class DeleteCases(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DeleteCases.html +
'DeleteCases[$list$, $pattern$]'
returns the elements of $list$ that do not match $pattern$. @@ -383,6 +393,8 @@ def from_python(self): class Drop(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Drop.html +
'Drop[$expr$, $n$]'
returns $expr$ with the first $n$ elements removed. @@ -435,6 +447,8 @@ def apply(self, items, seqs, evaluation): class First(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/First.html +
'First[$expr$]'
returns the first element in $expr$. @@ -475,6 +489,8 @@ def apply(self, expr, evaluation): class FirstCase(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FirstCase.html +
FirstCase[{$e1$, $e2$, ...}, $pattern$]
gives the first $ei$ to match $pattern$, or $Missing[\"NotFound\"]$ if none matching pattern is found. @@ -484,7 +500,7 @@ class FirstCase(Builtin):
FirstCase[$expr$, $pattern$, $default$]
gives $default$ if no element matching $pattern$ is found. -
FirstCase[$expr$, $pattern$, $default$, $levelspec$] \ +
FirstCase[$expr$, $pattern$, $default$, $levelspec$]
finds only objects that appear on levels specified by $levelspec$.
FirstCase[$pattern$] @@ -505,6 +521,8 @@ class FirstCase(Builtin): class Extract(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Extract.html +
'Extract[$expr$, $list$]'
extracts parts of $expr$ specified by $list$. @@ -532,6 +550,8 @@ class Extract(Builtin): class FirstPosition(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FirstPosition.html +
'FirstPosition[$expr$, $pattern$]'
gives the position of the first element in $expr$ that matches $pattern$, or Missing["NotFound"] if no such element is found. @@ -691,6 +711,8 @@ def is_interger_list(expr_list): class Last(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Last.html +
'Last[$expr$]'
returns the last element in $expr$. @@ -729,6 +751,8 @@ def apply(self, expr, evaluation): class Length(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Length.html +
'Length[$expr$]'
returns the number of elements in $expr$. @@ -769,6 +793,8 @@ def apply(self, expr, evaluation): class MemberQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/MemberQ.html +
'MemberQ[$list$, $pattern$]'
returns 'True' if $pattern$ matches any element of $list$, or 'False' otherwise. @@ -793,6 +819,8 @@ class MemberQ(Builtin): class Most(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Most.html +
'Most[$expr$]'
returns $expr$ with the last element removed. @@ -827,6 +855,8 @@ def apply(self, expr, evaluation): class Part(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Part.html +
'Part[$expr$, $i$]'
returns part $i$ of $expr$. @@ -999,6 +1029,8 @@ def apply(self, list, i, evaluation): class Pick(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Pick.html +
'Pick[$list$, $sel$]'
returns those items in $list$ that are True in $sel$. @@ -1049,6 +1081,8 @@ def apply_pattern(self, items, sel, pattern, evaluation): class Position(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Position.html +
'Position[$expr$, $patt$]'
returns the list of positions for which $expr$ matches $patt$. @@ -1111,6 +1145,8 @@ def callback(level, pos): class Prepend(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Prepend.html +
'Prepend[$expr$, $item$]'
returns $expr$ with $item$ prepended to its elements. @@ -1155,6 +1191,8 @@ def apply(self, expr, item, evaluation): class PrependTo(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/PrependTo.html +
'PrependTo[$s$, $item$]'
prepends $item$ to value of $s$ and sets $s$ to the result. @@ -1220,6 +1258,8 @@ def apply(self, s, item, evaluation): class ReplacePart(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ReplacePart.html +
'ReplacePart[$expr$, $i$ -> $new$]'
replaces part $i$ in $expr$ with $new$. @@ -1310,6 +1350,8 @@ def apply(self, expr, replacements, evaluation): class Rest(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Rest.html +
'Rest[$expr$]'
returns $expr$ with the first element removed. @@ -1350,6 +1392,8 @@ def apply(self, expr, evaluation): class Select(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Select.html +
'Select[{$e1$, $e2$, ...}, $f$]'
returns a list of the elements $ei$ for which $f$[$ei$] returns 'True'. @@ -1391,6 +1435,8 @@ def cond(element): class Span(BinaryOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/Span.html +
'Span'
is the head of span ranges like '1;;3'. @@ -1439,6 +1485,8 @@ class Span(BinaryOperator): class Take(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Take.html +
'Take[$expr$, $n$]'
returns $expr$ with all but the first $n$ elements removed. @@ -1512,6 +1560,8 @@ def apply(self, items, seqs, evaluation): class UpTo(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/UpTo.html +
'Upto'[$n$]
is a symbolic specification that represents up to $n$ objects or positions. If $n$ objects or positions are available, all are used. If fewer are available, only those available are used. From 5685b5ac3110b85ea5a2790011ec03de422ba3bf Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 23 Dec 2022 15:10:21 -0500 Subject: [PATCH 047/121] Further passes to get to standards * dd indentation * isort modules * Ensure lines are under 80 characters * eol: order Builtin --- mathics/builtin/binary/bytearray.py | 27 ++--- mathics/builtin/colors/color_operations.py | 94 +++++++-------- mathics/builtin/drawing/image.py | 87 ++++++++------ mathics/builtin/drawing/image_internals.py | 2 +- mathics/builtin/list/eol.py | 127 ++++++++++----------- 5 files changed, 173 insertions(+), 164 deletions(-) diff --git a/mathics/builtin/binary/bytearray.py b/mathics/builtin/binary/bytearray.py index a7b254d34..aa8f6e4a5 100644 --- a/mathics/builtin/binary/bytearray.py +++ b/mathics/builtin/binary/bytearray.py @@ -4,29 +4,24 @@ """ from mathics.builtin.base import Builtin -from mathics.core.atoms import ( - ByteArrayAtom, - Integer, - String, -) +from mathics.core.atoms import ByteArrayAtom, Integer, String from mathics.core.convert.expression import to_mathics_list from mathics.core.expression import Expression -from mathics.core.systemsymbols import ( - SymbolByteArray, - SymbolFailed, -) +from mathics.core.systemsymbols import SymbolByteArray, SymbolFailed class ByteArray(Builtin): r""" - :WMA link:https://reference.wolfram.com/language/ref/ByteArray.html + :WMA link: + https://reference.wolfram.com/language/ref/ByteArray.html
'ByteArray[{$b_1$, $b_2$, ...}]' -
Represents a sequence of Bytes $b_1$, $b_2$, ... +
Represents a sequence of Bytes $b_1$, $b_2$, ...
'ByteArray["string"]' -
Constructs a byte array where bytes comes from decode a b64 encoded String +
Constructs a byte array where bytes comes from decode a b64-encoded \ + String
>> A=ByteArray[{1, 25, 3}] @@ -51,7 +46,7 @@ class ByteArray(Builtin): } summary_text = "array of bytes" - def apply_str(self, string, evaluation): + def eval_str(self, string, evaluation): "ByteArray[string_String]" try: atom = ByteArrayAtom(string.value) @@ -60,15 +55,15 @@ def apply_str(self, string, evaluation): return SymbolFailed return Expression(SymbolByteArray, atom) - def apply_to_str(self, baa, evaluation): + def eval_to_str(self, baa, evaluation): "ToString[ByteArray[baa_ByteArrayAtom]]" return String('ByteArray["' + baa.__str__() + '"]') - def apply_normal(self, baa, evaluation): + def eval_normal(self, baa, evaluation): "System`Normal[ByteArray[baa_ByteArrayAtom]]" return to_mathics_list(*baa.value, elements_conversion_fn=Integer) - def apply_list(self, values, evaluation): + def eval_list(self, values, evaluation): "ByteArray[values_List]" if not values.has_form("List", None): return diff --git a/mathics/builtin/colors/color_operations.py b/mathics/builtin/colors/color_operations.py index caced3147..b5cbcc429 100644 --- a/mathics/builtin/colors/color_operations.py +++ b/mathics/builtin/colors/color_operations.py @@ -8,21 +8,12 @@ import itertools from math import floor + from mathics.builtin.base import Builtin -from mathics.builtin.colors.color_directives import ( - _ColorObject, - ColorError, - RGBColor, -) +from mathics.builtin.colors.color_directives import ColorError, RGBColor, _ColorObject from mathics.builtin.colors.color_internals import convert_color -from mathics.builtin.drawing.image import _ImageBuiltin, Image - -from mathics.core.atoms import ( - Integer, - MachineReal, - Rational, - Real, -) +from mathics.builtin.drawing.image import Image, _ImageBuiltin +from mathics.core.atoms import Integer, MachineReal, Rational, Real from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.expression import Expression from mathics.core.list import ListExpression @@ -32,8 +23,8 @@ _image_requires = ("numpy", "PIL") try: - import PIL.ImageOps import numpy + import PIL.ImageOps _enabled = True except ImportError: @@ -45,14 +36,16 @@ class Blend(Builtin): :WMA link:https://reference.wolfram.com/language/ref/Blend.html
-
'Blend[{$c1$, $c2$}]' -
represents the color between $c1$ and $c2$. -
'Blend[{$c1$, $c2$}, $x$]' -
represents the color formed by blending $c1$ and $c2$ with - factors 1 - $x$ and $x$ respectively. -
'Blend[{$c1$, $c2$, ..., $cn$}, $x$]' -
blends between the colors $c1$ to $cn$ according to the - factor $x$. +
'Blend[{$c1$, $c2$}]' +
represents the color between $c1$ and $c2$. + +
'Blend[{$c1$, $c2$}, $x$]' +
represents the color formed by blending $c1$ and $c2$ with + factors 1 - $x$ and $x$ respectively. + +
'Blend[{$c1$, $c2$, ..., $cn$}, $x$]' +
blends between the colors $c1$ to $cn$ according to the + factor $x$.
>> Blend[{Red, Blue}] @@ -111,7 +104,7 @@ def do_blend(self, colors, values): result = [r + p for r, p in zip(result, part)] return type(components=result) - def apply(self, colors, u, evaluation): + def eval(self, colors, u, evaluation): "Blend[{colors___}, u_]" colors_orig = colors @@ -156,12 +149,13 @@ def apply(self, colors, u, evaluation): class ColorConvert(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/ColorConvert.html + :WMA link: + https://reference.wolfram.com/language/ref/ColorConvert.html
-
'ColorConvert[$c$, $colspace$]' -
returns the representation of $c$ in the color space $colspace$. $c$ - may be a color or an image. +
'ColorConvert[$c$, $colspace$]' +
returns the representation of $c$ in the color space $colspace$. $c$ \ + may be a color or an image.
Valid values for $colspace$ are: @@ -182,15 +176,15 @@ class ColorConvert(Builtin): } summary_text = "convert between color models" - def apply(self, input, colorspace, evaluation): + def eval(self, input, colorspace, evaluation): "ColorConvert[input_, colorspace_String]" if isinstance(input, Image): return input.color_convert(colorspace.get_string_value()) else: from mathics.builtin.colors.color_directives import ( - expression_to_color, color_to_expression, + expression_to_color, ) py_color = expression_to_color(input) @@ -229,7 +223,7 @@ class ColorNegate(_ImageBuiltin): summary_text = "the negative color of a given color" - def apply_for_color(self, color, evaluation): + def eval_for_color(self, color, evaluation): "ColorNegate[color_RGBColor]" # Get components r, g, b = [element.to_python() for element in color.elements] @@ -238,7 +232,7 @@ def apply_for_color(self, color, evaluation): # Reconstitute return Expression(SymbolRGBColor, Real(r), Real(g), Real(b)) - def apply_for_image(self, image, evaluation): + def eval_for_image(self, image, evaluation): "ColorNegate[image_Image]" return image.filter(lambda im: PIL.ImageOps.invert(im)) @@ -273,22 +267,30 @@ class DominantColors(_ImageBuiltin): :WMA link:https://reference.wolfram.com/language/ref/DominantColors.html
-
'DominantColors[$image$]' +
'DominantColors[$image$]'
gives a list of colors which are dominant in the given image. -
'DominantColors[$image$, $n$]' + +
'DominantColors[$image$, $n$]'
returns at most $n$ colors. -
'DominantColors[$image$, $n$, $prop$]' -
returns the given property $prop$, which may be "Color" (return RGB colors), "LABColor" (return - LAB colors), "Count" (return the number of pixels a dominant color covers), "Coverage" (return the - fraction of the image a dominant color covers), or "CoverageImage" (return a black and white image - indicating with white the parts that are covered by a dominant color). + +
'DominantColors[$image$, $n$, $prop$]' +
returns the given property $prop$, which may be: +
    +
  • "Color": return RGB colors, +
  • "LABColor": return LAB colors, +
  • "Count": return the number of pixels a dominant color covers, +
  • "Coverage": return the fraction of the image a dominant color \ + covers, or +
  • "CoverageImage": return a black and white image indicating with \ + white the parts that are covered by a dominant color. +
- The option "ColorCoverage" specifies the minimum amount of coverage needed to include a dominant color - in the result. + The option "ColorCoverage" specifies the minimum amount of coverage needed to \ + include a dominant color in the result. - The option "MinColorDistance" specifies the distance (in LAB color space) up to which colors are merged - and thus regarded as belonging to the same dominant color. + The option "MinColorDistance" specifies the distance (in LAB color space) up \ + to which colors are merged and thus regarded as belonging to the same dominant color. >> img = Import["ExampleData/lena.tif"] = -Image- @@ -326,7 +328,7 @@ class DominantColors(_ImageBuiltin): options = {"ColorCoverage": "Automatic", "MinColorDistance": "Automatic"} summary_text = "find a list of dominant colors" - def apply(self, image, n, prop, evaluation, options): + def eval(self, image, n, prop, evaluation, options): "DominantColors[image_Image, n_Integer, prop_String, OptionsPattern[%(name)s]]" py_prop = prop.get_string_value() @@ -389,9 +391,9 @@ def apply(self, image, n, prop, evaluation, options): num_pixels = im.size[0] * im.size[1] from mathics.algorithm.clusters import ( - agglomerate, - PrecomputedDistances, FixedDistanceCriterion, + PrecomputedDistances, + agglomerate, ) norm = numpy.linalg.norm @@ -426,7 +428,7 @@ def result(): elif py_prop == "Coverage": yield Rational(int(count), num_pixels) elif py_prop == "CoverageImage": - mask = numpy.ndarray(shape=pixels.shape, dtype=numpy.bool) + mask = numpy.ndarray(shape=pixels.shape, dtype=bool) mask.fill(0) for i in members: mask = mask | (pixels == i) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index a3b398491..c13ef3fda 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -2,38 +2,37 @@ """ Image[] and image-related functions -Note that you (currently) need scikit-image installed in order for this module to work. +Note that you (currently) need scikit-image installed in order for this \ +module to work. """ # This tells documentation how to sort this module -# Here we are also hiding "drawing" since this erroneously appears at the top level. +# Here, we are also hiding "drawing" since this erroneously appears at +# the top level. sort_order = "mathics.builtin.image-and-image-related-functions" -from collections import defaultdict import base64 import functools import math import os.path as osp - +from collections import defaultdict from typing import Optional, Tuple -from mathics.builtin.base import Builtin, AtomBuiltin, Test, String +from mathics.builtin.base import AtomBuiltin, Builtin, String, Test from mathics.builtin.box.image import ImageBox from mathics.builtin.colors.color_internals import ( - convert_color, colorspaces as known_colorspaces, + convert_color, ) - from mathics.builtin.drawing.image_internals import ( + convolve, + matrix_to_numpy, + numpy_flip, + numpy_to_matrix, pixels_as_float, pixels_as_ubyte, pixels_as_uint, - matrix_to_numpy, - numpy_to_matrix, - numpy_flip, - convolve, ) - from mathics.core.atoms import ( Atom, Integer, @@ -62,14 +61,13 @@ try: import warnings + import numpy import PIL import PIL.ImageEnhance - import PIL.ImageOps import PIL.ImageFilter + import PIL.ImageOps from PIL.ExifTags import TAGS as ExifTags - import numpy - _enabled = True except ImportError: _enabled = False @@ -257,11 +255,13 @@ def apply(self, image, args, evaluation): class ImageAdd(_ImageArithmetic): """ - :WMA link:https://reference.wolfram.com/language/ref/ImageAdd.html + :WMA link: + https://reference.wolfram.com/language/ref/ImageAdd.html
'ImageAdd[$image$, $expr_1$, $expr_2$, ...]' -
adds all $expr_i$ to $image$ where each $expr_i$ must be an image or a real number. +
adds all $expr_i$ to $image$ where each $expr_i$ must be an image \ + or a real number.
>> i = Image[{{0, 0.5, 0.2, 0.1, 0.9}, {1.0, 0.1, 0.3, 0.8, 0.6}}]; @@ -295,11 +295,13 @@ class ImageAdd(_ImageArithmetic): class ImageSubtract(_ImageArithmetic): """ - :WMA link:https://reference.wolfram.com/language/ref/ImageSubtract.html + :WMA link: + https://reference.wolfram.com/language/ref/ImageSubtract.html
'ImageSubtract[$image$, $expr_1$, $expr_2$, ...]' -
subtracts all $expr_i$ from $image$ where each $expr_i$ must be an image or a real number. +
subtracts all $expr_i$ from $image$ where each $expr_i$ must be an \ + image or a real number.
>> i = Image[{{0, 0.5, 0.2, 0.1, 0.9}, {1.0, 0.1, 0.3, 0.8, 0.6}}]; @@ -578,7 +580,7 @@ def apply_resize_width_height(self, image, width, height, evaluation, options): return evaluation.message("ImageResize", "imgrsm", resampling) try: - from skimage import transform, __version__ as skimage_version + from skimage import __version__ as skimage_version, transform multichannel = image.pixels.ndim == 3 @@ -1669,12 +1671,14 @@ def apply(self, values, evaluation, options): class ImageData(_ImageBuiltin): """ - :WMA link:https://reference.wolfram.com/language/ref/ImageData.html + :WMA link: + https://reference.wolfram.com/language/ref/ImageData.html
-
'ImageData[$image$]' +
'ImageData[$image$]'
gives a list of all color values of $image$ as a matrix. -
'ImageData[$image$, $stype$]' + +
'ImageData[$image$, $stype$]'
gives a list of color values in type $stype$.
@@ -1717,15 +1721,19 @@ def apply(self, image, stype, evaluation): class ImageTake(_ImageBuiltin): """ - :WMA link:https://reference.wolfram.com/language/ref/ImageTake.html + :WMA link: + https://reference.wolfram.com/language/ref/ImageTake.html
-
'ImageTake[$image$, $n$]' +
'ImageTake[$image$, $n$]'
gives the first $n$ rows of $image$. -
'ImageTake[$image$, -$n$]' + +
'ImageTake[$image$, -$n$]'
gives the last $n$ rows of $image$. -
'ImageTake[$image$, {$r1$, $r2$}]' + +
'ImageTake[$image$, {$r1$, $r2$}]'
gives rows $r1$, ..., $r2$ of $image$. -
'ImageTake[$image$, {$r1$, $r2$}, {$c1$, $c2$}]' + +
'ImageTake[$image$, {$r1$, $r2$}, {$c1$, $c2$}]'
gives a cropped version of $image$.
""" @@ -1774,7 +1782,7 @@ class PixelValue(_ImageBuiltin): :WMA link:https://reference.wolfram.com/language/ref/PixelValue.html
-
'PixelValue[$image$, {$x$, $y$}]' +
'PixelValue[$image$, {$x$, $y$}]'
gives the value of the pixel at position {$x$, $y$} in $image$.
@@ -1821,7 +1829,7 @@ class PixelValuePositions(_ImageBuiltin): :WMA link:https://reference.wolfram.com/language/ref/PixelValuePositions.html
-
'PixelValuePositions[$image$, $val$]' +
'PixelValuePositions[$image$, $val$]'
gives the positions of all pixels in $image$ that have value $val$.
@@ -1875,7 +1883,7 @@ class ImageDimensions(_ImageBuiltin): :WMA link:https://reference.wolfram.com/language/ref/ImageDimensions.html
-
'ImageDimensions[$image$]' +
'ImageDimensions[$image$]'
Returns the dimensions of $image$ in pixels.
@@ -1901,10 +1909,11 @@ def apply(self, image, evaluation): class ImageAspectRatio(_ImageBuiltin): """ - :WMA link:https://reference.wolfram.com/language/ref/ImageAspectRatio.html + :WMA link: + https://reference.wolfram.com/language/ref/ImageAspectRatio.html
-
'ImageAspectRatio[$image$]' +
'ImageAspectRatio[$image$]'
gives the aspect ratio of $image$.
@@ -1927,7 +1936,8 @@ def apply(self, image, evaluation): class ImageChannels(_ImageBuiltin): """ - :WMA link:https://reference.wolfram.com/language/ref/ImageChannels.html + :WMA link: + https://reference.wolfram.com/language/ref/ImageChannels.html
'ImageChannels[$image$]' @@ -1951,7 +1961,8 @@ def apply(self, image, evaluation): class ImageType(_ImageBuiltin): """ - :WMA link:https://reference.wolfram.com/language/ref/ImageType.html + + :WMA link:https://reference.wolfram.com/language/ref/ImageType.html
'ImageType[$image$]'
gives the interval storage type of $image$, e.g. "Real", "Bit32", or "Bit". @@ -1979,7 +1990,8 @@ def apply(self, image, evaluation): class BinaryImageQ(_ImageTest): """ - :WMA link:https://reference.wolfram.com/language/ref/BinaryImageQ.html + :WMA link: + https://reference.wolfram.com/language/ref/BinaryImageQ.html
'BinaryImageQ[$image]' @@ -2305,6 +2317,7 @@ class TextRecognize(Builtin): def apply(self, image, evaluation, options): "TextRecognize[image_Image, OptionsPattern[%(name)s]]" import pyocr + from mathics.builtin.codetables import iso639_3 language = self.get_option(options, "Language", evaluation) @@ -2476,8 +2489,8 @@ def _word_cloud(self, words, evaluation, options): return # inspired by http://minimaxir.com/2016/05/wordclouds/ - import random import os + import random def color_func( word, font_size, position, orientation, random_state=None, **kwargs diff --git a/mathics/builtin/drawing/image_internals.py b/mathics/builtin/drawing/image_internals.py index 26cf19154..c60ed0bce 100644 --- a/mathics/builtin/drawing/image_internals.py +++ b/mathics/builtin/drawing/image_internals.py @@ -63,7 +63,7 @@ def pixels_as_float(pixels): return pixels.astype(numpy.float32) / 255.0 elif dtype == numpy.uint16: return pixels.astype(numpy.float32) / 65535.0 - elif dtype == numpy.bool: + elif dtype == bool: return pixels.astype(numpy.float32) else: raise NotImplementedError diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index 05be60604..3dfc6e2b9 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -3,16 +3,12 @@ """ Elements of Lists -Functions for accessing elements of lists using either indices, positions, or patterns of criteria. +Functions for accessing elements of lists using either indices, positions, or \ +patterns of criteria. """ from itertools import chain -from mathics.builtin.base import ( - BinaryOperator, - Builtin, -) - from mathics.algorithm.parts import ( _drop_span_selector, _parts, @@ -23,7 +19,7 @@ walk_levels, walk_parts, ) - +from mathics.builtin.base import BinaryOperator, Builtin from mathics.builtin.box.layout import RowBox from mathics.builtin.lists import list_boxes from mathics.core.atoms import Integer, Integer0, Integer1, String @@ -87,7 +83,7 @@ class Append(Builtin): summary_text = "add an element at the end of an expression" - def apply(self, expr, item, evaluation): + def eval(self, expr, item, evaluation): "Append[expr_, item_]" if isinstance(expr, Atom): @@ -103,7 +99,8 @@ def apply(self, expr, item, evaluation): class AppendTo(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/AppendTo.html + :WMA link: + https://reference.wolfram.com/language/ref/AppendTo.html
'AppendTo[$s$, $elem$]' @@ -139,7 +136,7 @@ class AppendTo(Builtin): } summary_text = "add an element at the end of an stored list or expression" - def apply(self, s, element, evaluation): + def eval(self, s, element, evaluation): "AppendTo[s_, element_]" resolved_s = s.evaluate(evaluation) if s == resolved_s: @@ -217,7 +214,7 @@ class Cases(Builtin): summary_text = "list elements matching a pattern" - def apply(self, items, pattern, ls, evaluation, options): + def eval(self, items, pattern, ls, evaluation, options): "Cases[items_, pattern_, ls_:{1}, OptionsPattern[]]" if isinstance(items, Atom): return ListExpression() @@ -323,7 +320,7 @@ class DeleteCases(Builtin): } summary_text = "delete all occurrences of a pattern" - def apply_ls_n(self, items, pattern, levelspec, n, evaluation): + def eval_ls_n(self, items, pattern, levelspec, n, evaluation): "DeleteCases[items_, pattern_, levelspec_:1, n_:System`Infinity]" if isinstance(items, Atom): @@ -429,7 +426,7 @@ class Drop(Builtin): } summary_text = "remove a number of elements from a list" - def apply(self, items, seqs, evaluation): + def eval(self, items, seqs, evaluation): "Drop[items_, seqs___]" seqs = seqs.get_sequence() @@ -445,6 +442,35 @@ def apply(self, items, seqs, evaluation): e.message(evaluation) +class Extract(Builtin): + """ + :WMA link:https://reference.wolfram.com/language/ref/Extract.html + +
+
'Extract[$expr$, $list$]' +
extracts parts of $expr$ specified by $list$. + +
'Extract[$expr$, {$list1$, $list2$, ...}]' +
extracts a list of parts. +
+ + 'Extract[$expr$, $i$, $j$, ...]' is equivalent to 'Part[$expr$, {$i$, $j$, ...}]'. + + >> Extract[a + b + c, {2}] + = b + >> Extract[{{a, b}, {c, d}}, {{1}, {2, 2}}] + = {{a, b}, d} + """ + + attributes = A_N_HOLD_REST | A_PROTECTED + + rules = { + "Extract[expr_, list_List]": "Part[expr, Sequence @@ list]", + "Extract[expr_, {lists___List}]": "Extract[expr, #]& /@ {lists}", + } + summary_text = "extract elements that appear at a list of positions" + + class First(Builtin): """ :WMA link:https://reference.wolfram.com/language/ref/First.html @@ -474,7 +500,7 @@ class First(Builtin): } summary_text = "first element of a list or expression" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "First[expr_]" if isinstance(expr, Atom): @@ -519,35 +545,6 @@ class FirstCase(Builtin): summary_text = "first element that matches a pattern" -class Extract(Builtin): - """ - :WMA link:https://reference.wolfram.com/language/ref/Extract.html - -
-
'Extract[$expr$, $list$]' -
extracts parts of $expr$ specified by $list$. - -
'Extract[$expr$, {$list1$, $list2$, ...}]' -
extracts a list of parts. -
- - 'Extract[$expr$, $i$, $j$, ...]' is equivalent to 'Part[$expr$, {$i$, $j$, ...}]'. - - >> Extract[a + b + c, {2}] - = b - >> Extract[{{a, b}, {c, d}}, {{1}, {2, 2}}] - = {{a, b}, d} - """ - - attributes = A_N_HOLD_REST | A_PROTECTED - - rules = { - "Extract[expr_, list_List]": "Part[expr, Sequence @@ list]", - "Extract[expr_, {lists___List}]": "Extract[expr, #]& /@ {lists}", - } - summary_text = "extract elements that appear at a list of positions" - - class FirstPosition(Builtin): """ :WMA link:https://reference.wolfram.com/language/ref/FirstPosition.html @@ -625,7 +622,7 @@ class FirstPosition(Builtin): } summary_text = "position of the first element matching a pattern" - def apply( + def eval( self, expr, pattern, evaluation, default=None, minLevel=None, maxLevel=None ): "FirstPosition[expr_, pattern_]" @@ -669,11 +666,11 @@ def check_pattern(input_list, pat, result, beginLevel): else default ) - def apply_default(self, expr, pattern, default, evaluation): + def eval_default(self, expr, pattern, default, evaluation): "FirstPosition[expr_, pattern_, default_]" - return self.apply(expr, pattern, evaluation, default=default) + return self.eval(expr, pattern, evaluation, default=default) - def apply_level(self, expr, pattern, default, level, evaluation): + def eval_level(self, expr, pattern, default, level, evaluation): "FirstPosition[expr_, pattern_, default_, level_]" def is_interger_list(expr_list): @@ -699,7 +696,7 @@ def is_interger_list(expr_list): else: return evaluation.message("FirstPosition", "level", level) - return self.apply( + return self.eval( expr, pattern, evaluation, @@ -736,7 +733,7 @@ class Last(Builtin): } summary_text = "last element of a list or expression" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "Last[expr_]" if isinstance(expr, Atom): @@ -782,7 +779,7 @@ class Length(Builtin): summary_text = "number of elements in a list or expression" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "Length[expr_]" if isinstance(expr, Atom): @@ -844,7 +841,7 @@ class Most(Builtin): summary_text = "remove the last element" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "Most[expr_]" if isinstance(expr, Atom): @@ -968,7 +965,7 @@ class Part(Builtin): attributes = A_N_HOLD_REST | A_PROTECTED | A_READ_PROTECTED summary_text = "get/set any part of an expression" - def apply_makeboxes(self, list, i, f, evaluation): + def eval_makeboxes(self, list, i, f, evaluation): """MakeBoxes[Part[list_, i___], f:StandardForm|TraditionalForm|OutputForm|InputForm]""" @@ -982,7 +979,7 @@ def apply_makeboxes(self, list, i, f, evaluation): result = RowBox(list, *indices) return result - def apply(self, list, i, evaluation): + def eval(self, list, i, evaluation): "Part[list_, i___]" if list is SymbolFailed: @@ -1067,11 +1064,11 @@ def pick(items, sel): else: return r[0] - def apply(self, items, sel, evaluation): + def eval(self, items, sel, evaluation): "Pick[items_, sel_]" return self._do(items, sel, lambda s: s is SymbolTrue, evaluation) - def apply_pattern(self, items, sel, pattern, evaluation): + def eval_pattern(self, items, sel, pattern, evaluation): "Pick[items_, sel_, pattern_]" from mathics.builtin.patterns import Matcher @@ -1114,12 +1111,12 @@ class Position(Builtin): } summary_text = "positions of matching elements" - def apply_invalidlevel(self, patt, expr, ls, evaluation, options={}): + def eval_invalidlevel(self, patt, expr, ls, evaluation, options={}): "Position[expr_, patt_, ls_, OptionsPattern[Position]]" return evaluation.message("Position", "level", ls) - def apply_level(self, expr, patt, ls, evaluation, options={}): + def eval_level(self, expr, patt, ls, evaluation, options={}): """Position[expr_, patt_, Optional[Pattern[ls, _?LevelQ], {0, DirectedInfinity[1]}], OptionsPattern[Position]]""" @@ -1175,7 +1172,7 @@ class Prepend(Builtin): summary_text = "add an element at the beginning" - def apply(self, expr, item, evaluation): + def eval(self, expr, item, evaluation): "Prepend[expr_, item_]" if isinstance(expr, Atom): @@ -1239,7 +1236,7 @@ class PrependTo(Builtin): } summary_text = "add an element at the beginning of an stored list or expression" - def apply(self, s, item, evaluation): + def eval(self, s, item, evaluation): "PrependTo[s_, item_]" resolved_s = s.evaluate(evaluation) if s == resolved_s: @@ -1310,7 +1307,7 @@ class ReplacePart(Builtin): } summary_text = "replace elements at given positions" - def apply(self, expr, replacements, evaluation): + def eval(self, expr, replacements, evaluation): "ReplacePart[expr_, {replacements___}]" new_expr = expr.copy() @@ -1377,7 +1374,7 @@ class Rest(Builtin): } summary_text = "remove the first element" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "Rest[expr_]" if isinstance(expr, Atom): @@ -1419,7 +1416,7 @@ class Select(Builtin): summary_text = "pick elements according to a criterion" - def apply(self, items, expr, evaluation): + def eval(self, items, expr, evaluation): "Select[items_, expr_]" if isinstance(items, Atom): @@ -1542,7 +1539,7 @@ class Take(Builtin): } summary_text = "pick a range of elements" - def apply(self, items, seqs, evaluation): + def eval(self, items, seqs, evaluation): "Take[items_, seqs___]" seqs = seqs.get_sequence() @@ -1564,7 +1561,9 @@ class UpTo(Builtin):
'Upto'[$n$] -
is a symbolic specification that represents up to $n$ objects or positions. If $n$ objects or positions are available, all are used. If fewer are available, only those available are used. +
is a symbolic specification that represents up to $n$ objects or \ + positions. If $n$ objects or positions are available, all are used. \ + If fewer are available, only those available are used.
""" From ba18e844f34d18ee46c03b9a52da04a691f6a86d Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 21 Dec 2022 03:43:20 -0500 Subject: [PATCH 048/121] Make ByteArray more WMA compatible... Start to adjust for Numpy 1.24 --- mathics/builtin/binary/bytearray.py | 8 ++++---- mathics/builtin/drawing/image.py | 6 +++--- mathics/builtin/drawing/image_internals.py | 6 +++--- mathics/builtin/list/eol.py | 6 +++--- mathics/core/atoms.py | 4 ++-- setup.py | 6 ++---- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/mathics/builtin/binary/bytearray.py b/mathics/builtin/binary/bytearray.py index aa8f6e4a5..975e3457c 100644 --- a/mathics/builtin/binary/bytearray.py +++ b/mathics/builtin/binary/bytearray.py @@ -25,15 +25,15 @@ class ByteArray(Builtin):
>> A=ByteArray[{1, 25, 3}] - = ByteArray["ARkD"] + = ByteArray[<3>] >> A[[2]] = 25 >> Normal[A] = {1, 25, 3} >> ToString[A] - = ByteArray["ARkD"] + = ByteArray[<3>] >> ByteArray["ARkD"] - = ByteArray["ARkD"] + = ByteArray[<3>] >> B=ByteArray["asy"] : The first argument in Bytearray[asy] should be a B64 enconded string or a vector of integers. = $Failed @@ -57,7 +57,7 @@ def eval_str(self, string, evaluation): def eval_to_str(self, baa, evaluation): "ToString[ByteArray[baa_ByteArrayAtom]]" - return String('ByteArray["' + baa.__str__() + '"]') + return String("ByteArray[" + baa.__str__() + "]") def eval_normal(self, baa, evaluation): "System`Normal[ByteArray[baa_ByteArrayAtom]]" diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index c13ef3fda..44a12133e 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -1582,9 +1582,9 @@ def _linearize(a): u = numpy.unique(a) n = len(u) - lower = numpy.ndarray(a.shape, dtype=numpy.int) + lower = numpy.ndarray(a.shape, dtype=int) lower.fill(0) - upper = numpy.ndarray(a.shape, dtype=numpy.int) + upper = numpy.ndarray(a.shape, dtype=int) upper.fill(n - 1) h = numpy.sort(u) @@ -1713,7 +1713,7 @@ def apply(self, image, stype, evaluation): elif stype == "Bit16": pixels = pixels_as_uint(pixels) elif stype == "Bit": - pixels = pixels.astype(numpy.int) + pixels = pixels.astype(int) else: return evaluation.message("ImageData", "pixelfmt", stype) return from_python(numpy_to_matrix(pixels)) diff --git a/mathics/builtin/drawing/image_internals.py b/mathics/builtin/drawing/image_internals.py index c60ed0bce..3039e866a 100644 --- a/mathics/builtin/drawing/image_internals.py +++ b/mathics/builtin/drawing/image_internals.py @@ -63,7 +63,7 @@ def pixels_as_float(pixels): return pixels.astype(numpy.float32) / 255.0 elif dtype == numpy.uint16: return pixels.astype(numpy.float32) / 65535.0 - elif dtype == bool: + elif dtype is bool: return pixels.astype(numpy.float32) else: raise NotImplementedError @@ -78,7 +78,7 @@ def pixels_as_ubyte(pixels): return pixels elif dtype == numpy.uint16: return (pixels / 256).astype(numpy.uint8) - elif dtype == numpy.bool: + elif dtype is bool: return pixels.astype(numpy.uint8) * 255 else: raise NotImplementedError @@ -93,7 +93,7 @@ def pixels_as_uint(pixels): return pixels.astype(numpy.uint16) * 256 elif dtype == numpy.uint16: return pixels - elif dtype == numpy.bool: + elif dtype is bool: return pixels.astype(numpy.uint8) * 65535 else: raise NotImplementedError diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index 3dfc6e2b9..423aaacde 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -986,7 +986,7 @@ def eval(self, list, i, evaluation): return indices = i.get_sequence() # How to deal with ByteArrays - if list.get_head_name() == "System`ByteArray": + if list.get_head() == SymbolByteArray: list = list.evaluate(evaluation) if len(indices) > 1: print( @@ -995,8 +995,8 @@ def eval(self, list, i, evaluation): ) return idx = indices[0] - if idx.get_head_name() == "System`Integer": - idx = idx.get_int_value() + if isinstance(idx, Integer): + idx = idx.value if idx == 0: return SymbolByteArray data = list.elements[0].value diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index 7b7fcfeee..92cba3ff2 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -559,10 +559,10 @@ def __hash__(self): return self.hash def __str__(self) -> str: - return base64.b64encode(self.value).decode("utf8") + return f"<{len(self.value)}>" def atom_to_boxes(self, f, evaluation) -> "String": - res = String('""' + self.__str__() + '""') + res = String(self.__str__()) return res def do_copy(self) -> "ByteArrayAtom": diff --git a/setup.py b/setup.py index 6eaf1ad68..c0162a8ae 100644 --- a/setup.py +++ b/setup.py @@ -47,17 +47,15 @@ elif sys.version_info[:2] == (3, 6): INSTALL_REQUIRES += [ "recordclass", - "numpy<1.24", + "numpy", "llvmlite<0.37", "sympy>=1.8,<1.9", ] if is_PyPy: print("Mathics does not support PyPy Python 3.6" % sys.version_info[:2]) sys.exit(-1) -elif sys.version_info[:2] == (3, 7): - INSTALL_REQUIRES += ["numpy<1.22", "llvmlite", "sympy>=1.8, < 1.11"] else: - INSTALL_REQUIRES += ["numpy<1.24", "llvmlite", "sympy>=1.8, < 1.11"] + INSTALL_REQUIRES += ["numpy<=1.24", "llvmlite", "sympy>=1.8, < 1.11"] if not is_PyPy: INSTALL_REQUIRES += ["recordclass"] From 16c6d3246427934599f753674e58805a9198fb7c Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 23 Dec 2022 16:50:23 -0500 Subject: [PATCH 049/121] Move builtin.grawing.image_internals to eval.image and isort imports --- mathics/builtin/drawing/image.py | 18 +++++++++--------- .../image_internals.py => eval/image.py} | 0 2 files changed, 9 insertions(+), 9 deletions(-) rename mathics/{builtin/drawing/image_internals.py => eval/image.py} (100%) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 44a12133e..efd1da5bf 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -24,15 +24,6 @@ colorspaces as known_colorspaces, convert_color, ) -from mathics.builtin.drawing.image_internals import ( - convolve, - matrix_to_numpy, - numpy_flip, - numpy_to_matrix, - pixels_as_float, - pixels_as_ubyte, - pixels_as_uint, -) from mathics.core.atoms import ( Atom, Integer, @@ -48,6 +39,15 @@ from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull, SymbolTrue from mathics.core.systemsymbols import SymbolRule, SymbolSimplify +from mathics.eval.image import ( + convolve, + matrix_to_numpy, + numpy_flip, + numpy_to_matrix, + pixels_as_float, + pixels_as_ubyte, + pixels_as_uint, +) SymbolColorQuantize = Symbol("ColorQuantize") SymbolImage = Symbol("Image") diff --git a/mathics/builtin/drawing/image_internals.py b/mathics/eval/image.py similarity index 100% rename from mathics/builtin/drawing/image_internals.py rename to mathics/eval/image.py From bf0c14d36afb44fd74cea327b01b802aabcecbfd Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 23 Dec 2022 17:11:56 -0500 Subject: [PATCH 050/121] Correct use of numpy bool type... remove a Builtin-specific "no-doc" module variable. Since this has been moved, it is no longer subject to scrutiny. Funny how beter modularization can *simplify* workarounds. --- mathics/eval/image.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/mathics/eval/image.py b/mathics/eval/image.py index 3039e866a..c5aecb418 100644 --- a/mathics/eval/image.py +++ b/mathics/eval/image.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- -"""helper functions for images """ - -# Signals to Mathics doc processing not to include this module in its documentation. -no_doc = True - +helper functions for images +""" import numpy @@ -63,7 +60,7 @@ def pixels_as_float(pixels): return pixels.astype(numpy.float32) / 255.0 elif dtype == numpy.uint16: return pixels.astype(numpy.float32) / 65535.0 - elif dtype is bool: + elif dtype is numpy.dtype(bool): return pixels.astype(numpy.float32) else: raise NotImplementedError @@ -78,7 +75,7 @@ def pixels_as_ubyte(pixels): return pixels elif dtype == numpy.uint16: return (pixels / 256).astype(numpy.uint8) - elif dtype is bool: + elif dtype is numpy.dtype(bool): return pixels.astype(numpy.uint8) * 255 else: raise NotImplementedError @@ -93,7 +90,7 @@ def pixels_as_uint(pixels): return pixels.astype(numpy.uint16) * 256 elif dtype == numpy.uint16: return pixels - elif dtype is bool: + elif dtype is numpy.dtype(bool): return pixels.astype(numpy.uint8) * 65535 else: raise NotImplementedError From a0c9e55f088b645acdde7362a8de039bf419b04f Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 23 Dec 2022 17:57:33 -0500 Subject: [PATCH 051/121] Fold in mmatera's comments --- mathics/builtin/binary/bytearray.py | 2 +- mathics/builtin/list/eol.py | 2 +- mathics/core/atoms.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mathics/builtin/binary/bytearray.py b/mathics/builtin/binary/bytearray.py index 975e3457c..cce80a221 100644 --- a/mathics/builtin/binary/bytearray.py +++ b/mathics/builtin/binary/bytearray.py @@ -57,7 +57,7 @@ def eval_str(self, string, evaluation): def eval_to_str(self, baa, evaluation): "ToString[ByteArray[baa_ByteArrayAtom]]" - return String("ByteArray[" + baa.__str__() + "]") + return String(f"ByteArray[<{len(baa.value)}>]") def eval_normal(self, baa, evaluation): "System`Normal[ByteArray[baa_ByteArrayAtom]]" diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index 423aaacde..6407dd4de 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -986,7 +986,7 @@ def eval(self, list, i, evaluation): return indices = i.get_sequence() # How to deal with ByteArrays - if list.get_head() == SymbolByteArray: + if list.get_head() is SymbolByteArray: list = list.evaluate(evaluation) if len(indices) > 1: print( diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index 92cba3ff2..ebce7af5d 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -559,10 +559,10 @@ def __hash__(self): return self.hash def __str__(self) -> str: - return f"<{len(self.value)}>" + return base64.b64encode(self.value).decode("utf8") def atom_to_boxes(self, f, evaluation) -> "String": - res = String(self.__str__()) + res = String(f"<{len(self.value)}>") return res def do_copy(self) -> "ByteArrayAtom": From 2e42f1d7562aea45e0a6be19d9dad5acd01655de Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 23 Dec 2022 18:13:59 -0500 Subject: [PATCH 052/121] Note need for Form refactoring --- mathics/core/atoms.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index ebce7af5d..7e88608d2 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -561,6 +561,12 @@ def __hash__(self): def __str__(self) -> str: return base64.b64encode(self.value).decode("utf8") + # FIXME: the below does not use the "f" parameter to + # change behavior between FullForm and OutputForm + # Below we have the OutputForm behavior. + # A refactoring should be done so that this routine + # is removed and the form makes decisions, rather than + # have this routine know everything about all forms. def atom_to_boxes(self, f, evaluation) -> "String": res = String(f"<{len(self.value)}>") return res From 405b8d1714b641599997a35f7cfd529fb1584519 Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 21 Dec 2022 05:29:26 -0300 Subject: [PATCH 053/121] docstring urls for vectors, numbers and matrices --- mathics/builtin/matrices/constrmatrix.py | 4 ++ mathics/builtin/matrices/partmatrix.py | 4 ++ mathics/builtin/numbers/algebra.py | 50 +++++++++++++++++++ mathics/builtin/numbers/calculus.py | 41 +++++++++++++++ mathics/builtin/numbers/diffeqns.py | 4 ++ mathics/builtin/numbers/exp.py | 11 ++++ mathics/builtin/numbers/hyperbolic.py | 23 +++++++++ mathics/builtin/numbers/integer.py | 27 ++++++++++ mathics/builtin/numbers/linalg.py | 47 +++++++++++++++++ mathics/builtin/numbers/numbertheory.py | 22 ++++++++ mathics/builtin/numbers/trig.py | 30 +++++++++++ mathics/builtin/vectors/constructing.py | 2 + .../vectors/vector_space_operations.py | 12 +++++ 13 files changed, 277 insertions(+) diff --git a/mathics/builtin/matrices/constrmatrix.py b/mathics/builtin/matrices/constrmatrix.py index d8a7ae761..61ed5de29 100644 --- a/mathics/builtin/matrices/constrmatrix.py +++ b/mathics/builtin/matrices/constrmatrix.py @@ -13,6 +13,8 @@ class DiagonalMatrix(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DiagonalMatrix.html +
'DiagonalMatrix[$list$]'
gives a matrix with the values in $list$ on its diagonal and zeroes elsewhere. @@ -44,6 +46,8 @@ def apply(self, list, evaluation): class IdentityMatrix(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/IdentityMatrix.html +
'IdentityMatrix[$n$]'
gives the identity matrix with $n$ rows and columns. diff --git a/mathics/builtin/matrices/partmatrix.py b/mathics/builtin/matrices/partmatrix.py index 863ac11c7..8b112f004 100644 --- a/mathics/builtin/matrices/partmatrix.py +++ b/mathics/builtin/matrices/partmatrix.py @@ -13,6 +13,8 @@ class Diagonal(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Diagonal.html +
'Diagonal[$m$]'
gives a list with the values in the diagonal of the matrix $m$. @@ -58,6 +60,8 @@ def apply(self, expr, diag, evaluation): class MatrixQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/MatrixQ.html +
'MatrixQ[$m$]'
gives 'True' if $m$ is a list of equal-length lists. diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index c37760e7c..0e46a6269 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -384,6 +384,8 @@ def get_exponents_sorted(expr, var) -> list: class Apart(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Apart.html +
'Apart[$expr$]'
writes $expr$ as a sum of individual fractions. @@ -444,6 +446,8 @@ def apply(self, expr, var, evaluation): class Cancel(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Cancel.html +
'Cancel[$expr$]'
cancels out common factors in numerators and denominators. @@ -498,6 +502,9 @@ def combine_exprs(exprs): class Coefficient(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/Coefficient.html +
'Coefficient[expr, form]'
returns the coefficient of $form$ in the polynomial $expr$. @@ -815,6 +822,9 @@ def split_coeff_pow(term) -> Tuple[Optional[list], Optional[list]]: class CoefficientArrays(_CoefficientHandler): """ + + :WMA link:https://reference.wolfram.com/language/ref/CoefficientArrays.html +
'CoefficientArrays[$polys$, $vars$]'
returns a list of arrays of coefficients of the variables $vars$ in the polynomial $poly$. @@ -915,6 +925,8 @@ def apply_list(self, polys, varlist, evaluation, options): class CoefficientList(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/CoefficientList.html +
'CoefficientList[poly, var]'
returns a list of coefficients of powers of $var$ in $poly$, starting with power 0. @@ -1041,6 +1053,9 @@ def _nth(poly, dims, exponents): class Collect(_CoefficientHandler): """ + + :WMA link:https://reference.wolfram.com/language/ref/Collect.html +
'Collect[$expr$, $x$]'
Expands $expr$ and collect together terms having the same power of $x$. @@ -1084,6 +1099,9 @@ def apply_var_filter(self, expr, varlist, filt, evaluation): class Denominator(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/Denominator.html +
'Denominator[$expr$]'
gives the denominator in $expr$. @@ -1145,6 +1163,8 @@ def convert_options(self, options, evaluation): class Expand(_Expand): """ + :WMA link:https://reference.wolfram.com/language/ref/Expand.html +
'Expand[$expr$]'
expands out positive integer powers and products of sums in $expr$, as well as trigonometric identities. @@ -1238,6 +1258,8 @@ def apply(self, expr, evaluation, options): class ExpandAll(_Expand): """ + :WMA link:https://reference.wolfram.com/language/ref/ExpandAll.html +
'ExpandAll[$expr$]'
expands out negative integer powers and products of sums in $expr$. @@ -1296,6 +1318,8 @@ def apply(self, expr, evaluation, options): class ExpandDenominator(_Expand): """ + :WMA link:https://reference.wolfram.com/language/ref/ExpandDenominator.html +
'ExpandDenominator[$expr$]'
expands out negative integer powers and products of sums in $expr$. @@ -1327,6 +1351,8 @@ def apply(self, expr, evaluation, options): class Exponent(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Exponent.html +
'Exponent[expr, form]'
returns the maximum power with which $form$ appears in the expanded form of $expr$. @@ -1390,6 +1416,8 @@ def apply(self, expr, form, h, evaluation): class Factor(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Factor.html +
'Factor[$expr$]'
factors the polynomial expression $expr$. @@ -1440,6 +1468,8 @@ def apply(self, expr, evaluation): class FactorTermsList(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FactorTermsList.html +
'FactorTermsList[poly]'
returns a list of 2 elements. @@ -1549,6 +1579,9 @@ def apply_list(self, expr, vars, evaluation): # FullSimplify class Simplify(Builtin): r""" + + :WMA link:https://reference.wolfram.com/language/ref/Simplify.html +
'Simplify[$expr$]'
simplifies $expr$. @@ -1717,6 +1750,8 @@ def _default_complexity_function(x): class FullSimplify(Simplify): """ + :WMA link:https://reference.wolfram.com/language/ref/FullSimplify.html +
'FullSimplify[$expr$]'
simplifies $expr$ using an extended set of simplification rules. @@ -1740,6 +1775,9 @@ class FullSimplify(Simplify): class MinimalPolynomial(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/MinimalPolynomial.html +
'MinimalPolynomial[s, x]'
gives the minimal polynomial in $x$ for which the algebraic number $s$ is a root. @@ -1796,6 +1834,8 @@ def apply(self, s, x, evaluation): class Numerator(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Numerator.html +
'Numerator[$expr$]'
gives the numerator in $expr$. @@ -1824,6 +1864,9 @@ def apply(self, expr, evaluation): class PolynomialQ(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/PolynomialQ.html +
'PolynomialQ[expr, var]'
returns True if $expr$ is a polynomial in $var$, and returns False otherwise. @@ -1911,6 +1954,8 @@ def apply(self, expr, v, evaluation): class PowerExpand(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/PowerExpand.html +
'PowerExpand[$expr$]'
expands out powers of the form '(x^y)^z' and '(x*y)^z' in $expr$. @@ -1942,6 +1987,9 @@ class PowerExpand(Builtin): class Together(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/Together.html +
'Together[$expr$]'
writes sums of fractions in $expr$ together. @@ -1978,6 +2026,8 @@ def apply(self, expr, evaluation): class Variables(Builtin): # This builtin is incomplete. See the failing test case below. """ + :WMA link:https://reference.wolfram.com/language/ref/Variables.html +
'Variables[$expr$]'
gives a list of the variables that appear in the polynomial $expr$. diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index 4c0e00c54..cfc64f6a6 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -106,6 +106,8 @@ # Maybe this class should be in a module "mathics.builtin.domains" or something like that class Complexes(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Complexes.html +
'Complexes'
the domain of complex numbers, as in $x$ in Complexes. @@ -117,6 +119,9 @@ class Complexes(Builtin): class D(SympyFunction): """ + :Derivative:https://en.wikipedia.org/wiki/Derivative\ + (:WMA link:https://reference.wolfram.com/language/ref/D.html) +
'D[$f$, $x$]'
gives the partial derivative of $f$ with respect to $x$. @@ -371,6 +376,9 @@ def apply_wrong(self, expr, x, other, evaluation): class Derivative(PostfixOperator, SympyFunction): """ + + :WMA link:https://reference.wolfram.com/language/ref/Derivative.html +
'Derivative[$n$][$f$]'
represents the $n$th derivative of the function $f$. @@ -523,6 +531,8 @@ def to_sympy(self, expr, **kwargs): class DiscreteLimit(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DiscreteLimit.html +
'DiscreteLimit[$f$, $k$->Infinity]'
gives the limit of the sequence $f$ as $k$ tends to infinity. @@ -577,6 +587,8 @@ def apply(self, f, n, n0, evaluation, options={}): class _BaseFinder(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/_BaseFinder.html + This class is the basis class for FindRoot, FindMinimum and FindMaximum. """ @@ -713,6 +725,8 @@ def apply_with_x_tuple(self, f, xtuple, evaluation, options): class FindMaximum(_BaseFinder): r""" + :WMA link:https://reference.wolfram.com/language/ref/FindMaximum.html +
'FindMaximum[$f$, {$x$, $x0$}]'
searches for a numerical maximum of $f$, starting from '$x$=$x0$'. @@ -757,6 +771,8 @@ class FindMaximum(_BaseFinder): class FindMinimum(_BaseFinder): r""" + :WMA link:https://reference.wolfram.com/language/ref/FindMinimum.html +
'FindMinimum[$f$, {$x$, $x0$}]'
searches for a numerical minimum of $f$, starting from '$x$=$x0$'. @@ -806,6 +822,8 @@ class FindMinimum(_BaseFinder): class FindRoot(_BaseFinder): r""" + :WMA link:https://reference.wolfram.com/language/ref/FindRoot.html +
'FindRoot[$f$, {$x$, $x0$}]'
searches for a numerical root of $f$, starting from '$x$=$x0$'. @@ -895,6 +913,9 @@ class FindRoot(_BaseFinder): # Move to mathics.builtin.domains... class Integers(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/Integers.html +
'Integers'
the domain of integer numbers, as in $x$ in Integers. @@ -912,6 +933,8 @@ class Integers(Builtin): class Integrate(SympyFunction): r""" + :WMA link:https://reference.wolfram.com/language/ref/Integrate.html +
'Integrate[$f$, $x$]'
integrates $f$ with respect to $x$. The result does not contain the additive integration constant. @@ -1187,6 +1210,9 @@ def apply_D(self, func, domain, var, evaluation, options): class Limit(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/Limit.html +
'Limit[$expr$, $x$->$x0$]'
gives the limit of $expr$ as $x$ approaches $x0$. @@ -1267,6 +1293,8 @@ def apply(self, expr, x, x0, evaluation, options={}): class NIntegrate(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/NIntegrate.html +
'NIntegrate[$expr$, $interval$]'
returns a numeric approximation to the definite integral of $expr$ with limits $interval$ and with a precision of $prec$ digits. @@ -1540,6 +1568,9 @@ def apply_D(self, func, domain, var, evaluation, options): class O_(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/O_.html +
'O[$x$]^n'
Represents a term of order $x^n$. @@ -1564,6 +1595,8 @@ class O_(Builtin): class Reals(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Reals.html +
'Reals'
is the domain real numbers, as in $x$ in Reals. @@ -1579,6 +1612,9 @@ class Reals(Builtin): class Root(SympyFunction): """ + + :WMA link:https://reference.wolfram.com/language/ref/Root.html +
'Root[$f$, $i$]'
represents the i-th complex root of the polynomial $f$ @@ -1657,6 +1693,8 @@ def to_sympy(self, expr, **kwargs): class Series(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Series.html +
'Series[$f$, {$x$, $x0$, $n$}]'
Represents the series expansion around '$x$=$x0$' up to order $n$. @@ -1716,6 +1754,9 @@ def apply_multivariate_series(self, f, varspec, evaluation): class SeriesData(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/SeriesData.html +
'SeriesData[...]'
Represents a series expansion diff --git a/mathics/builtin/numbers/diffeqns.py b/mathics/builtin/numbers/diffeqns.py index 33760aa24..4c3752b2c 100644 --- a/mathics/builtin/numbers/diffeqns.py +++ b/mathics/builtin/numbers/diffeqns.py @@ -15,6 +15,8 @@ class DSolve(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DSolve.html +
'DSolve[$eq$, $y$[$x$], $x$]'
solves a differential equation for the function $y$[$x$]. @@ -211,6 +213,8 @@ def apply(self, eqn, y, x, evaluation): class C(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/C.html +
'C'[$n$]
represents the $n$th constant in a solution to a differential equation. diff --git a/mathics/builtin/numbers/exp.py b/mathics/builtin/numbers/exp.py index 734f4afaf..cd6412218 100644 --- a/mathics/builtin/numbers/exp.py +++ b/mathics/builtin/numbers/exp.py @@ -159,6 +159,9 @@ def converted_operands(): class Exp(_MPMathFunction): """ + + :WMA link:https://reference.wolfram.com/language/ref/Exp.html +
'Exp[$z$]'
returns the exponential function of $z$. @@ -190,6 +193,8 @@ def from_sympy(self, sympy_name, elements): class Log(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Log.html +
'Log[$z$]'
returns the natural logarithm of $z$. @@ -246,6 +251,8 @@ def get_mpmath_function(self, args): class Log2(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Log2.html +
'Log2[$z$]'
returns the base-2 logarithm of $z$. @@ -269,6 +276,8 @@ class Log2(Builtin): class Log10(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Log10.html +
'Log10[$z$]'
returns the base-10 logarithm of $z$. @@ -292,6 +301,8 @@ class Log10(Builtin): class LogisticSigmoid(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/LogisticSigmoid.html +
'LogisticSigmoid[$z$]'
returns the logistic sigmoid of $z$. diff --git a/mathics/builtin/numbers/hyperbolic.py b/mathics/builtin/numbers/hyperbolic.py index ffb764389..bdae4742e 100644 --- a/mathics/builtin/numbers/hyperbolic.py +++ b/mathics/builtin/numbers/hyperbolic.py @@ -25,6 +25,8 @@ class ArcCosh(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcCosh.html +
'ArcCosh[$z$]'
returns the inverse hyperbolic cosine of $z$. @@ -52,6 +54,8 @@ class ArcCosh(_MPMathFunction): class ArcCoth(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcCoth.html +
'ArcCoth[$z$]'
returns the inverse hyperbolic cotangent of $z$. @@ -83,6 +87,8 @@ class ArcCoth(_MPMathFunction): class ArcCsch(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcCsch.html +
'ArcCsch[$z$]'
returns the inverse hyperbolic cosecant of $z$. @@ -115,6 +121,8 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]: class ArcSech(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcSech.html +
'ArcSech[$z$]'
returns the inverse hyperbolic secant of $z$. @@ -149,6 +157,8 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]: class ArcSinh(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcSinh.html +
'ArcSinh[$z$]'
returns the inverse hyperbolic sine of $z$. @@ -174,6 +184,8 @@ class ArcSinh(_MPMathFunction): class ArcTanh(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcTanh.html +
'ArcTanh[$z$]'
returns the inverse hyperbolic tangent of $z$. @@ -205,6 +217,8 @@ class ArcTanh(_MPMathFunction): class Cosh(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Cosh.html +
'Cosh[$z$]'
returns the hyperbolic cosine of $z$. @@ -226,6 +240,8 @@ class Cosh(_MPMathFunction): class Coth(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Coth.html +
'Coth[$z$]'
returns the hyperbolic cotangent of $z$. @@ -249,6 +265,7 @@ class Coth(_MPMathFunction): class Gudermannian(Builtin): """ + :Gudermannian function: https://en.wikipedia.org/wiki/Gudermannian_function (:WMA: https://reference.wolfram.com/language/ref/Gudermannian.html, :MathWorld: https://mathworld.wolfram.com/Gudermannian.html)
'Gudermannian[$z$]' @@ -325,6 +342,8 @@ class InverseGudermannian(Builtin): class Sech(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Sech.html +
'Sech[$z$]'
returns the hyperbolic secant of $z$. @@ -352,6 +371,8 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]: class Sinh(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Sinh.html +
'Sinh[$z$]'
returns the hyperbolic sine of $z$. @@ -372,6 +393,8 @@ class Sinh(_MPMathFunction): class Tanh(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Tanh.html +
'Tanh[$z$]'
returns the hyperbolic tangent of $z$. diff --git a/mathics/builtin/numbers/integer.py b/mathics/builtin/numbers/integer.py index 11592162d..404c9ce0f 100644 --- a/mathics/builtin/numbers/integer.py +++ b/mathics/builtin/numbers/integer.py @@ -60,6 +60,10 @@ def _valid_base(self, b, evaluation): class BitLength(Builtin): """ + + + :WMA link:https://reference.wolfram.com/language/ref/BitLength.html +
'BitLength[$x$]'
gives the number of bits needed to represent the integer $x$. $x$'s sign is ignored. @@ -88,6 +92,8 @@ def apply(self, n, evaluation): class Ceiling(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Ceiling.html +
'Ceiling[$x$]'
gives the smallest integer greater than or equal to $x$. @@ -117,6 +123,10 @@ def apply(self, x, evaluation): class DigitCount(_IntBaseBuiltin): """ + + + :WMA link:https://reference.wolfram.com/language/ref/DigitCount.html +
'DigitCount[$n$, $b$, $d$]'
returns the number of times digit $d$ occurs in the base $b$ representation of $n$. @@ -171,6 +181,8 @@ def apply_n_b(self, n, b, evaluation): class Floor(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Floor.html +
'Floor[$x$]'
gives the greatest integer less than or equal to $x$. @@ -218,6 +230,10 @@ def apply_real(self, x, evaluation): class FromDigits(Builtin): """ + + + :WMA link:https://reference.wolfram.com/language/ref/FromDigits.html +
'FromDigits[$l$]'
returns the integer corresponding to the decimal representation given by $l$. $l$ can be a list of @@ -303,6 +319,8 @@ def apply(self, l, b, evaluation): class IntegerString(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/IntegerString.html +
'IntegerString[$n$]'
returns the decimal representation of integer $x$ as string. $x$'s sign is ignored. @@ -382,6 +400,8 @@ def apply_n_b_length(self, n, b, length, evaluation): class IntegerDigits(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/IntegerDigits.html +
'IntegerDigits[$n$]'
returns a list of the base-10 digits in the integer $n$. @@ -464,6 +484,8 @@ def apply(self, n, base, evaluation, nr_elements=None): class IntegerDigits(_IntBaseBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/IntegerDigits.html +
'IntegerDigits[$n$]'
returns the decimal representation of integer $x$ as list of digits. $x$'s sign is ignored. @@ -532,6 +554,11 @@ def apply_n_b_length(self, n, b, length, evaluation): class IntegerReverse(_IntBaseBuiltin): """ + + + + :WMA link:https://reference.wolfram.com/language/ref/IntegerReverse.html +
'IntegerReverse[$n$]'
returns the integer that has the reverse decimal representation of $x$ without sign. diff --git a/mathics/builtin/numbers/linalg.py b/mathics/builtin/numbers/linalg.py index c080055a1..f0446eb7b 100644 --- a/mathics/builtin/numbers/linalg.py +++ b/mathics/builtin/numbers/linalg.py @@ -25,6 +25,8 @@ class DesignMatrix(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DesignMatrix.html +
'DesignMatrix[$m$, $f$, $x$]'
returns the design matrix for a linear model $f$ in the variables $x$. @@ -47,6 +49,9 @@ class DesignMatrix(Builtin): class Det(Builtin): """ + :Matrix Determinant: https://en.wikipedia.org/wiki/Determinant \ + (:WMA link:https://reference.wolfram.com/language/ref/Det.html) +
'Det[$m$]'
computes the determinant of the matrix $m$. @@ -74,6 +79,9 @@ def apply(self, m, evaluation): class Eigensystem(Builtin): """ + :Matrix Eigenvalues: https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors \ + (:WMA link:https://reference.wolfram.com/language/ref/Eigensystem.html) +
'Eigensystem[$m$]'
returns the list '{Eigenvalues[$m$], Eigenvectors[$m$]}'. @@ -89,6 +97,10 @@ class Eigensystem(Builtin): class Eigenvalues(Builtin): """ + :Matrix Eigenvalues: https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors \ + (:WMA link:https://reference.wolfram.com/language/ref/Eigenvalues.html) + +
'Eigenvalues[$m$]'
computes the eigenvalues of the matrix $m$. @@ -184,6 +196,9 @@ def apply(self, m, evaluation, options={}) -> Expression: class Eigenvectors(Builtin): """ + :Matrix Eigenvalues: https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors \ + (:WMA link:https://reference.wolfram.com/language/ref/Eigenvectors.html) +
'Eigenvectors[$m$]'
computes the eigenvectors of the matrix $m$. @@ -258,6 +273,8 @@ def apply(self, m, evaluation): class FittedModel(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FittedModel.html +
'FittedModel[...]'
Result of a linear fit @@ -278,6 +295,8 @@ class FittedModel(Builtin): class Inverse(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Inverse.html +
'Inverse[$m$]'
computes the inverse of the matrix $m$. @@ -322,6 +341,8 @@ def apply(self, m, evaluation): class LeastSquares(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/LeastSquares.html +
'LeastSquares[$m$, $b$]'
computes the least squares solution to $m$ $x$ = $b$, finding @@ -378,6 +399,8 @@ def apply(self, m, b, evaluation): class LinearModelFit(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/LinearModelFit.html +
'LinearModelFit[$m$, $f$, $x$]'
fits a linear model $f$ in the variables $x$ to the dataset $m$. @@ -457,6 +480,8 @@ class LinearModelFit(Builtin): class LinearSolve(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/LinearSolve.html +
'LinearSolve[$matrix$, $right$]'
solves the linear equation system '$matrix$ . $x$ = $right$' @@ -530,6 +555,8 @@ def apply(self, m, b, evaluation): class MatrixExp(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/MatrixExp.html +
'MatrixExp[$m$]'
computes the exponential of the matrix $m$. @@ -572,6 +599,8 @@ def apply(self, m, evaluation): class MatrixPower(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/MatrixPower.html +
'MatrixPower[$m$, $n$]'
computes the $n$th power of a matrix $m$. @@ -619,6 +648,8 @@ def apply(self, m, power, evaluation): class MatrixRank(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/MatrixRank.html +
'MatrixRank[$matrix$]'
returns the rank of $matrix$. @@ -653,6 +684,9 @@ def apply(self, m, evaluation): class NullSpace(Builtin): """ + :Kernel (null space):https://en.wikipedia.org/wiki/Kernel_(linear_algebra) \ + (:WMA link:https://reference.wolfram.com/language/ref/NullSpace.html) +
'NullSpace[$matrix$]'
returns a list of vectors that span the nullspace of $matrix$. @@ -692,6 +726,8 @@ def apply(self, m, evaluation): class PseudoInverse(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/PseudoInverse.html +
'PseudoInverse[$m$]'
computes the Moore-Penrose pseudoinverse of the matrix $m$. @@ -729,6 +765,9 @@ def apply(self, m, evaluation): class QRDecomposition(Builtin): """ + :QR Decomposition:https://en.wikipedia.org/wiki/QR_decomposition \ + (:WMA link:https://reference.wolfram.com/language/ref/QRDecomposition.html) +
'QRDecomposition[$m$]'
computes the QR decomposition of the matrix $m$. @@ -764,6 +803,8 @@ def apply(self, m, evaluation): class RowReduce(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/RowReduce.html +
'RowReduce[$matrix$]'
returns the reduced row-echelon form of $matrix$. @@ -801,6 +842,9 @@ def apply(self, m, evaluation): class SingularValueDecomposition(Builtin): """ + :Singular Value Decomposition:https://en.wikipedia.org/wiki/Singular_value_decomposition \ + (:WMA link:https://reference.wolfram.com/language/ref/SingularValueDecomposition.html) +
'SingularValueDecomposition[$m$]'
calculates the singular value decomposition for the matrix $m$. @@ -860,6 +904,9 @@ def apply(self, m, evaluation): class Tr(Builtin): """ + :Matrix trace:https://en.wikipedia.org/wiki/Trace_(linear_algebra) \ + (:WMA link:https://reference.wolfram.com/language/ref/Tr.html) +
'Tr[$m$]'
computes the trace of the matrix $m$. diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index b5ece3907..b6937c7f2 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -74,6 +74,8 @@ def apply_2(self, x, n, evaluation): class Divisors(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Divisors.html +
'Divisors[$n$]'
returns a list of the integers that divide $n$. @@ -189,6 +191,8 @@ def apply(self, n, evaluation): class FactorInteger(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FactorInteger.html +
'FactorInteger[$n$]'
returns the factorization of $n$ as a list of factors and exponents. @@ -255,6 +259,8 @@ def _fractional_part(self, n, expr, evaluation): class FractionalPart(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FractionalPart.html +
'FractionalPart[$n$]'
finds the fractional part of $n$. @@ -307,6 +313,8 @@ def apply_2(self, n, evaluation): class FromContinuedFraction(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/FromContinuedFraction.html +
'FromContinuedFraction[$list$]'
reconstructs a number from the list of its continued fraction terms. @@ -333,6 +341,8 @@ def apply_1(self, expr, evaluation): class MantissaExponent(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/MantissaExponent.html +
'MantissaExponent[$n$]'
finds a list containing the mantissa and exponent of a given number $n$. @@ -460,6 +470,8 @@ def apply_2(self, n, evaluation): class NextPrime(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/NextPrime.html +
'NextPrime[$n$]'
gives the next prime after $n$. @@ -529,6 +541,8 @@ def to_int_value(x): class PartitionsP(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/PartitionsP.html +
'PartitionsP[$n$]'
return the number $p$($n$) of unrestricted partitions of the integer $n$. @@ -549,6 +563,8 @@ def apply(self, n, evaluation): class Prime(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Prime.html +
'Prime[$n$]'
'Prime'[{$n0$, $n1$, ...}] @@ -590,6 +606,8 @@ def to_sympy(self, expr, **kwargs): class PrimePi(SympyFunction): """ + :Prime numbers:https://reference.wolfram.com/language/ref/PrimePi.html +
'PrimePi[$x$]'
gives the number of primes less than or equal to $x$. @@ -627,6 +645,8 @@ def apply(self, n, evaluation): class PrimePowerQ(Builtin): """ + :Prime numbers:https://reference.wolfram.com/language/ref/PrimePowerQ.html +
'PrimePowerQ[$n$]'
returns 'True' if $n$ is a power of a prime number. @@ -684,6 +704,8 @@ def apply(self, n, evaluation): class RandomPrime(Builtin): """ + :Prime numbers:https://reference.wolfram.com/language/ref/RandomPrime.html +
'RandomPrime[{$imin$, $imax}]'
gives a random prime between $imin$ and $imax$. diff --git a/mathics/builtin/numbers/trig.py b/mathics/builtin/numbers/trig.py index f939d827a..7ec78a259 100644 --- a/mathics/builtin/numbers/trig.py +++ b/mathics/builtin/numbers/trig.py @@ -168,6 +168,8 @@ def converted_operands(): class AnglePath(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/AnglePath.html +
'AnglePath[{$phi1$, $phi2$, ...}]'
returns the points formed by a turtle starting at {0, 0} and angled at 0 degrees going through @@ -329,6 +331,8 @@ def _fold(self, state, steps, math): class ArcCos(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcCos.html +
'ArcCos[$z$]'
returns the inverse cosine of $z$. @@ -356,6 +360,8 @@ class ArcCos(_MPMathFunction): class ArcCot(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcCot.html +
'ArcCot[$z$]'
returns the inverse cotangent of $z$. @@ -381,6 +387,8 @@ class ArcCot(_MPMathFunction): class ArcCsc(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcCsc.html +
'ArcCsc[$z$]'
returns the inverse cosecant of $z$. @@ -412,6 +420,8 @@ def to_sympy(self, expr, **kwargs): class ArcSec(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcSec.html +
'ArcSec[$z$]'
returns the inverse secant of $z$. @@ -444,6 +454,8 @@ def to_sympy(self, expr, **kwargs): class ArcSin(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcSin.html +
'ArcSin[$z$]'
returns the inverse sine of $z$. @@ -470,6 +482,8 @@ class ArcSin(_MPMathFunction): class ArcTan(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/ArcTan.html +
'ArcTan[$z$]'
returns the inverse tangent of $z$. @@ -520,6 +534,8 @@ class ArcTan(_MPMathFunction): class Cos(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Cos.html +
'Cos[$z$]'
returns the cosine of $z$. @@ -548,6 +564,8 @@ class Cos(_MPMathFunction): class Cot(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Cot.html +
'Cot[$z$]'
returns the cotangent of $z$. @@ -572,6 +590,8 @@ class Cot(_MPMathFunction): class Csc(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Csc.html +
'Csc[$z$]'
returns the cosecant of $z$. @@ -604,6 +624,8 @@ def to_sympy(self, expr, **kwargs): class Haversine(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Haversine.html +
'Haversine[$z$]'
returns the haversine function of $z$. @@ -622,6 +644,8 @@ class Haversine(_MPMathFunction): class InverseHaversine(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/InverseHaversine.html +
'InverseHaversine[$z$]'
returns the inverse haversine function of $z$. @@ -640,6 +664,8 @@ class InverseHaversine(_MPMathFunction): class Sec(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Sec.html +
'Sec[$z$]'
returns the secant of $z$. @@ -670,6 +696,8 @@ def to_sympy(self, expr, **kwargs): class Sin(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Sin.html +
'Sin[$z$]'
returns the sine of $z$. @@ -706,6 +734,8 @@ class Sin(_MPMathFunction): class Tan(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Tan.html +
'Tan[$z$]'
returns the tangent of $z$. diff --git a/mathics/builtin/vectors/constructing.py b/mathics/builtin/vectors/constructing.py index 8521cae3d..267aa379e 100644 --- a/mathics/builtin/vectors/constructing.py +++ b/mathics/builtin/vectors/constructing.py @@ -13,6 +13,8 @@ class AngleVector(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/AngleVector.html +
'AngleVector[$phi$]'
returns the point at angle $phi$ on the unit circle. diff --git a/mathics/builtin/vectors/vector_space_operations.py b/mathics/builtin/vectors/vector_space_operations.py index de8231a82..eb8059a44 100644 --- a/mathics/builtin/vectors/vector_space_operations.py +++ b/mathics/builtin/vectors/vector_space_operations.py @@ -71,6 +71,8 @@ def eval(self, mi: ListExpression, evaluation: Evaluation): class Normalize(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/KroneckerProduct.html +
'Normalize[$v$]'
calculates the normalized vector $v$. @@ -101,6 +103,8 @@ class Normalize(Builtin): class Projection(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Projection.html +
'Projection[$u$, $v$]'
gives the projection of the vector $u$ onto $v$ @@ -160,6 +164,12 @@ def eval(self, u: ListExpression, v: ListExpression, evaluation): class UnitVector(Builtin): """ + + :Unit vector: + https://en.wikipedia.org/wiki/Unit_vector ( + :WMA: + https://reference.wolfram.com/language/ref/UnitVector.html) +
'UnitVector[$n$, $k$]'
returns the $n$-dimensional unit vector with a 1 in position $k$. @@ -205,6 +215,8 @@ def item(i): class VectorAngle(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/VectorAngle.html +
'VectorAngle[$u$, $v$]'
gives the angles between vectors $u$ and $v$ From 7d580d1b71d2f1a2b0377fc8f4aed8cb57cc385e Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 21 Dec 2022 19:12:28 -0300 Subject: [PATCH 054/121] fixing Tiago's comments --- mathics/builtin/numbers/calculus.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index cfc64f6a6..5d2b7271f 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -587,8 +587,6 @@ def apply(self, f, n, n0, evaluation, options={}): class _BaseFinder(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/_BaseFinder.html - This class is the basis class for FindRoot, FindMinimum and FindMaximum. """ @@ -1569,7 +1567,7 @@ def apply_D(self, func, domain, var, evaluation, options): class O_(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/O_.html + :WMA link:https://reference.wolfram.com/language/ref/O.html
'O[$x$]^n' From 3edfde299cea8f5f5c1dfa7d72da86f7dba1f171 Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 23 Dec 2022 22:36:13 -0500 Subject: [PATCH 055/121] Split long lines, isort dd indentation, ... apply -> eval --- mathics/builtin/matrices/constrmatrix.py | 9 +- mathics/builtin/matrices/partmatrix.py | 10 +- mathics/builtin/numbers/algebra.py | 165 +++++++------ mathics/builtin/numbers/calculus.py | 75 +++--- mathics/builtin/numbers/integer.py | 300 ++++++++--------------- mathics/core/systemsymbols.py | 6 + 6 files changed, 254 insertions(+), 311 deletions(-) diff --git a/mathics/builtin/matrices/constrmatrix.py b/mathics/builtin/matrices/constrmatrix.py index 61ed5de29..e0ac29b12 100644 --- a/mathics/builtin/matrices/constrmatrix.py +++ b/mathics/builtin/matrices/constrmatrix.py @@ -13,11 +13,13 @@ class DiagonalMatrix(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/DiagonalMatrix.html + :WMA link: + https://reference.wolfram.com/language/ref/DiagonalMatrix.html
'DiagonalMatrix[$list$]' -
gives a matrix with the values in $list$ on its diagonal and zeroes elsewhere. +
gives a matrix with the values in $list$ on its diagonal and \ + zeroes elsewhere.
>> DiagonalMatrix[{1, 2, 3}] @@ -46,7 +48,8 @@ def apply(self, list, evaluation): class IdentityMatrix(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/IdentityMatrix.html + :WMA link: + https://reference.wolfram.com/language/ref/IdentityMatrix.html
'IdentityMatrix[$n$]' diff --git a/mathics/builtin/matrices/partmatrix.py b/mathics/builtin/matrices/partmatrix.py index 8b112f004..a4009520d 100644 --- a/mathics/builtin/matrices/partmatrix.py +++ b/mathics/builtin/matrices/partmatrix.py @@ -13,13 +13,16 @@ class Diagonal(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/Diagonal.html + :WMA link: + https://reference.wolfram.com/language/ref/Diagonal.html
'Diagonal[$m$]'
gives a list with the values in the diagonal of the matrix $m$. +
'Diagonal[$m$, $k$]' -
gives a list with the values in the $k$ diagonal of the matrix $m$. +
gives a list with the values in the $k$ diagonal of the \ + matrix $m$.
>> Diagonal[{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}] @@ -67,7 +70,8 @@ class MatrixQ(Builtin):
gives 'True' if $m$ is a list of equal-length lists.
'MatrixQ[$m$, $f$]' -
gives 'True' only if '$f$[$x$]' returns 'True' for when applied to element $x$ of the matrix $m$. +
gives 'True' only if '$f$[$x$]' returns 'True' for when applied to \ + element $x$ of the matrix $m$.
>> MatrixQ[{{1, 3}, {4.0, 3/2}}, NumberQ] diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index 0e46a6269..a539f7d8c 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -15,20 +15,14 @@ from typing import Optional, Tuple, Union -from mathics.algorithm.simplify import default_complexity_function +import sympy +from mathics.algorithm.simplify import default_complexity_function from mathics.builtin.base import Builtin from mathics.builtin.inference import evaluate_predicate from mathics.builtin.options import options_to_rules from mathics.builtin.scoping import dynamic_scoping - -from mathics.core.atoms import ( - Integer, - Integer0, - Integer1, - RationalOneHalf, - Number, -) +from mathics.core.atoms import Integer, Integer0, Integer1, Number, RationalOneHalf from mathics.core.attributes import A_LISTABLE, A_PROTECTED from mathics.core.convert.python import from_bool from mathics.core.convert.sympy import from_sympy, sympy_symbol_prefix @@ -48,13 +42,15 @@ SymbolTimes, SymbolTrue, ) - from mathics.core.systemsymbols import ( - SymbolAutomatic, SymbolAlternatives, SymbolAssumptions, + SymbolAutomatic, SymbolComplexInfinity, SymbolCos, + SymbolCosh, + SymbolCot, + SymbolCoth, SymbolDirectedInfinity, SymbolEqual, SymbolIndeterminate, @@ -62,20 +58,12 @@ SymbolRule, SymbolRuleDelayed, SymbolSin, + SymbolSinh, SymbolTable, + SymbolTanh, ) -import sympy - -SymbolSinh = Symbol("Sinh") -SymbolCosh = Symbol("Cosh") -SymbolTan = Symbol("Tan") -SymbolTanh = Symbol("Tanh") -SymbolCot = Symbol("Cot") -SymbolCoth = Symbol("Coth") - - def sympy_factor(expr_sympy): try: result = sympy.together(expr_sympy) @@ -427,7 +415,7 @@ class Apart(Builtin): } summary_text = "partial fraction decomposition" - def apply(self, expr, var, evaluation): + def eval(self, expr, var, evaluation): "Apart[expr_, var_Symbol]" expr_sympy = expr.to_sympy() @@ -466,7 +454,7 @@ class Cancel(Builtin): attributes = A_LISTABLE | A_PROTECTED summary_text = "cancel common factors in rational expressions" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "Cancel[expr_]" return cancel(expr) @@ -569,15 +557,15 @@ class Coefficient(Builtin): summary_text = "coefficient of a monomial in a polynomial expression" - def apply_noform(self, expr, evaluation): + def eval_noform(self, expr, evaluation): "Coefficient[expr_]" return evaluation.message("Coefficient", "argtu") - def apply(self, expr, form, evaluation): + def eval(self, expr, form, evaluation): "Coefficient[expr_, form_]" return _coefficient(self.__class__.__name__, expr, form, Integer1, evaluation) - def apply_n(self, expr, form, n, evaluation): + def eval_n(self, expr, form, n, evaluation): "Coefficient[expr_, form_, n_]" return _coefficient(self.__class__.__name__, expr, form, n, evaluation) @@ -853,7 +841,7 @@ class CoefficientArrays(_CoefficientHandler): "array of coefficients associated with a polynomial in many variables" ) - def apply_list(self, polys, varlist, evaluation, options): + def eval_list(self, polys, varlist, evaluation, options): "%(name)s[polys_, varlist_, OptionsPattern[]]" from mathics.algorithm.parts import walk_parts @@ -969,11 +957,11 @@ class CoefficientList(Builtin): } summary_text = "list of coefficients defining a polynomial" - def apply_noform(self, expr, evaluation): + def eval_noform(self, expr, evaluation): "CoefficientList[expr_]" return evaluation.message("CoefficientList", "argtu") - def apply(self, expr, form, evaluation): + def eval(self, expr, form, evaluation): "CoefficientList[expr_, form_]" vars = [form] if not form.has_form("List", None) else [v for v in form.elements] @@ -1059,9 +1047,11 @@ class Collect(_CoefficientHandler):
'Collect[$expr$, $x$]'
Expands $expr$ and collect together terms having the same power of $x$. +
'Collect[$expr$, {$x_1$, $x_2$, ...}]' -
Expands $expr$ and collect together terms having the same powers of +
Expands $expr$ and collect together terms having the same powers of \ $x_1$, $x_2$, .... +
'Collect[$expr$, {$x_1$, $x_2$, ...}, $filter$]'
After collect the terms, applies $filter$ to each coefficient.
@@ -1083,7 +1073,7 @@ class Collect(_CoefficientHandler): } summary_text = "collect terms with a variable at the same power" - def apply_var_filter(self, expr, varlist, filt, evaluation): + def eval_var_filter(self, expr, varlist, filt, evaluation): """Collect[expr_, varlist_, filt_]""" if filt is Symbol("Identity"): filt = None @@ -1100,7 +1090,8 @@ def apply_var_filter(self, expr, varlist, filt, evaluation): class Denominator(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/Denominator.html + :WMA link: + https://reference.wolfram.com/language/ref/Denominator.html
'Denominator[$expr$]' @@ -1118,7 +1109,7 @@ class Denominator(Builtin): attributes = A_LISTABLE | A_PROTECTED summary_text = "denominator of an expression" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "Denominator[expr_]" sympy_expr = expr.to_sympy() @@ -1167,7 +1158,9 @@ class Expand(_Expand):
'Expand[$expr$]' -
expands out positive integer powers and products of sums in $expr$, as well as trigonometric identities. +
expands out positive integer powers and products of sums in $expr$, as \ + well as trigonometric identities. +
Expand[$expr$, $target$]
just expands those parts involving $target$.
@@ -1229,7 +1222,7 @@ class Expand(_Expand): summary_text = "expand out products and powers" - def apply_patt(self, expr, target, evaluation, options): + def eval_patt(self, expr, target, evaluation, options): "Expand[expr_, target_, OptionsPattern[Expand]]" if target.get_head_name() in ("System`Rule", "System`DelayedRule"): @@ -1246,7 +1239,7 @@ def apply_patt(self, expr, target, evaluation, options): kwargs["evaluation"] = evaluation return expand(expr, True, False, **kwargs) - def apply(self, expr, evaluation, options): + def eval(self, expr, evaluation, options): "Expand[expr_, OptionsPattern[Expand]]" kwargs = self.convert_options(options, evaluation) @@ -1258,11 +1251,13 @@ def apply(self, expr, evaluation, options): class ExpandAll(_Expand): """ - :WMA link:https://reference.wolfram.com/language/ref/ExpandAll.html + :WMA link: + https://reference.wolfram.com/language/ref/ExpandAll.html
'ExpandAll[$expr$]'
expands out negative integer powers and products of sums in $expr$. +
'ExpandAll[$expr$, $target$]'
just expands those parts involving $target$.
@@ -1291,7 +1286,7 @@ class ExpandAll(_Expand): summary_text = "expand products and powers, including negative integer powers" - def apply_patt(self, expr, target, evaluation, options): + def eval_patt(self, expr, target, evaluation, options): "ExpandAll[expr_, target_, OptionsPattern[Expand]]" if target.get_head_name() in ("System`Rule", "System`DelayedRule"): optname = target.elements[0].get_name() @@ -1307,7 +1302,7 @@ def apply_patt(self, expr, target, evaluation, options): kwargs["evaluation"] = evaluation return expand(expr, numer=True, denom=True, deep=True, **kwargs) - def apply(self, expr, evaluation, options): + def eval(self, expr, evaluation, options): "ExpandAll[expr_, OptionsPattern[ExpandAll]]" kwargs = self.convert_options(options, evaluation) @@ -1318,7 +1313,8 @@ def apply(self, expr, evaluation, options): class ExpandDenominator(_Expand): """ - :WMA link:https://reference.wolfram.com/language/ref/ExpandDenominator.html + :WMA link: + https://reference.wolfram.com/language/ref/ExpandDenominator.html
'ExpandDenominator[$expr$]' @@ -1340,7 +1336,7 @@ class ExpandDenominator(_Expand): summary_text = "expand just the denominator of a rational expression" - def apply(self, expr, evaluation, options): + def eval(self, expr, evaluation, options): "ExpandDenominator[expr_, OptionsPattern[ExpandDenominator]]" kwargs = self.convert_options(options, evaluation) @@ -1355,7 +1351,9 @@ class Exponent(Builtin):
'Exponent[expr, form]' -
returns the maximum power with which $form$ appears in the expanded form of $expr$. +
returns the maximum power with which $form$ appears in the expanded \ + form of $expr$. +
'Exponent[expr, form, h]'
applies $h$ to the set of exponents with which $form$ appears in $expr$.
@@ -1394,11 +1392,11 @@ class Exponent(Builtin): } summary_text = "maximum power in which a form appears in a polynomial" - def apply_novar(self, expr, evaluation): + def eval_novar(self, expr, evaluation): "Exponent[expr_]" return evaluation.message("Exponent", "argtu", Integer1) - def apply(self, expr, form, h, evaluation): + def eval(self, expr, form, h, evaluation): "Exponent[expr_, form_, h_]" if expr == Integer0: return Expression(SymbolDirectedInfinity, Integer(-1)) @@ -1453,7 +1451,7 @@ class Factor(Builtin): attributes = A_LISTABLE | A_PROTECTED summary_text = "factor sums into product and powers" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "Factor[expr_]" expr_sympy = expr.to_sympy() @@ -1468,19 +1466,21 @@ def apply(self, expr, evaluation): class FactorTermsList(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/FactorTermsList.html + :WMA link: + https://reference.wolfram.com/language/ref/FactorTermsList.html
'FactorTermsList[poly]'
returns a list of 2 elements. The first element is the numerical factor in $poly$. - The second one is the remaining of the polynomial with numerical factor removed + The second one is the remaining of the polynomial with numerical factor removed. +
'FactorTermsList[poly, {x1, x2, ...}]'
returns a list of factors in $poly$. - The first element is the numerical factor in $poly$. - The next ones are factors that are independent of variables lists which - are created by removing each variable $xi$ from right to left. - The last one is the remaining of polynomial after dividing $poly$ to all previous factors + The first element is the numerical factor in $poly$. \ + The next ones are factors that are independent of variables lists which \ + are created by removing each variable $xi$ from right to left. \ + The last one is the remaining of polynomial after dividing $poly$ to all previous factors.
>> FactorTermsList[2 x^2 - 2] @@ -1508,7 +1508,7 @@ class FactorTermsList(Builtin): } summary_text = "a polynomial as a list of factors" - def apply_list(self, expr, vars, evaluation): + def eval_list(self, expr, vars, evaluation): "FactorTermsList[expr_, vars_List]" if expr == Integer0: return ListExpression(Integer1, Integer0) @@ -1580,11 +1580,13 @@ def apply_list(self, expr, vars, evaluation): class Simplify(Builtin): r""" - :WMA link:https://reference.wolfram.com/language/ref/Simplify.html + :WMA link: + https://reference.wolfram.com/language/ref/Simplify.html
'Simplify[$expr$]'
simplifies $expr$. +
'Simplify[$expr$, $assump$]'
simplifies $expr$ assuming $assump$ instead of $Assumptions$.
@@ -1612,12 +1614,12 @@ class Simplify(Builtin): = 1 >> $Assumptions={}; - The option $ComplexityFunction$ allows to control the way in which the evaluator decides if one - expression is simpler than another. For example, by default, 'Simplify' tries to avoid - expressions involving numbers with many digits: + The option $ComplexityFunction$ allows to control the way in which the \ + evaluator decides if one expression is simpler than another. For example, \ + by default, 'Simplify' tries to avoid expressions involving numbers with many digits: >> Simplify[20 Log[2]] = 20 Log[2] - This behaviour can be modified by setting 'LeafCount' as the 'ComplexityFunction' + This behaviour can be modified by setting 'LeafCount' as the 'ComplexityFunction': >> Simplify[20 Log[2], ComplexityFunction->LeafCount] = Log[1048576] """ @@ -1632,14 +1634,14 @@ class Simplify(Builtin): } summary_text = "apply transformations to simplify an expression" - def apply_with_assumptions(self, expr, assum, evaluation, options={}): + def eval_with_assumptions(self, expr, assum, evaluation, options={}): "%(name)s[expr_, assum_, OptionsPattern[]]" # If the second argument is a rule, it means that # it should be taken as an option. if assum.get_head() in (SymbolRule, SymbolRuleDelayed): options[assum.elements[0].get_name()] = assum.elements[1] - return self.apply(expr, evaluation, options) + return self.eval(expr, evaluation, options) # If the option "Assumptions" was passed, then merge with assum: assumptions_list = options.pop("System`Assumptions") @@ -1662,17 +1664,17 @@ def apply_with_assumptions(self, expr, assum, evaluation, options={}): evaluation, ) - def apply_power_of_zero(self, b, evaluation): + def eval_power_of_zero(self, b, evaluation): "%(name)s[0^b_]" - if self.apply(Expression(SymbolLess, Integer0, b), evaluation) is SymbolTrue: + if self.eval(Expression(SymbolLess, Integer0, b), evaluation) is SymbolTrue: return Integer0 - if self.apply(Expression(SymbolLess, b, Integer0), evaluation) is SymbolTrue: + if self.eval(Expression(SymbolLess, b, Integer0), evaluation) is SymbolTrue: return Symbol(SymbolComplexInfinity) - if self.apply(Expression(SymbolEqual, b, Integer0), evaluation) is SymbolTrue: + if self.eval(Expression(SymbolEqual, b, Integer0), evaluation) is SymbolTrue: return Symbol(SymbolIndeterminate) return Expression(SymbolPower, Integer0, b) - def apply(self, expr, evaluation, options={}): + def eval(self, expr, evaluation, options={}): "%(name)s[expr_, OptionsPattern[]]" # If System`Assumptions is in the options, # rebuild the expression without this option, and evaluate it @@ -1750,7 +1752,8 @@ def _default_complexity_function(x): class FullSimplify(Simplify): """ - :WMA link:https://reference.wolfram.com/language/ref/FullSimplify.html + :WMA link: + https://reference.wolfram.com/language/ref/FullSimplify.html
'FullSimplify[$expr$]' @@ -1776,11 +1779,13 @@ class FullSimplify(Simplify): class MinimalPolynomial(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/MinimalPolynomial.html + :WMA link: + https://reference.wolfram.com/language/ref/MinimalPolynomial.html
'MinimalPolynomial[s, x]' -
gives the minimal polynomial in $x$ for which the algebraic number $s$ is a root. +
gives the minimal polynomial in $x$ for which the algebraic \ + number $s$ is a root.
>> MinimalPolynomial[7, x] @@ -1811,12 +1816,12 @@ class MinimalPolynomial(Builtin): } summary_text = "minimal polynomial for a general algebraic number" - def apply_novar(self, s, evaluation): + def eval_novar(self, s, evaluation): "MinimalPolynomial[s_]" x = Symbol("#1") - return self.apply(s, x, evaluation) + return self.eval(s, x, evaluation) - def apply(self, s, x, evaluation): + def eval(self, s, x, evaluation): "MinimalPolynomial[s_, x_]" variables = find_all_vars(s) if len(variables) > 0: @@ -1834,7 +1839,8 @@ def apply(self, s, x, evaluation): class Numerator(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/Numerator.html + :WMA link: + https://reference.wolfram.com/language/ref/Numerator.html
'Numerator[$expr$]' @@ -1852,7 +1858,7 @@ class Numerator(Builtin): attributes = A_LISTABLE | A_PROTECTED summary_text = "numerator of an expression" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "Numerator[expr_]" sympy_expr = expr.to_sympy() @@ -1865,7 +1871,8 @@ def apply(self, expr, evaluation): class PolynomialQ(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/PolynomialQ.html + :WMA link: + https://reference.wolfram.com/language/ref/PolynomialQ.html
'PolynomialQ[expr, var]' @@ -1926,7 +1933,7 @@ class PolynomialQ(Builtin): } summary_text = "test if the expression is a polynomial in a variable" - def apply(self, expr, v, evaluation): + def eval(self, expr, v, evaluation): "PolynomialQ[expr_, v___]" if expr is SymbolNull: return SymbolTrue @@ -1954,7 +1961,8 @@ def apply(self, expr, v, evaluation): class PowerExpand(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/PowerExpand.html + :WMA link: + https://reference.wolfram.com/language/ref/PowerExpand.html
'PowerExpand[$expr$]' @@ -1988,7 +1996,8 @@ class PowerExpand(Builtin): class Together(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/Together.html + :WMA link: + https://reference.wolfram.com/language/ref/Together.html
'Together[$expr$]' @@ -2011,7 +2020,7 @@ class Together(Builtin): attributes = A_LISTABLE | A_PROTECTED summary_text = "put over a common denominator" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "Together[expr_]" expr_sympy = expr.to_sympy() @@ -2045,7 +2054,7 @@ class Variables(Builtin): """ summary_text = "list of variables in a polynomial" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "Variables[expr_]" variables = find_all_vars(expr) diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index 5d2b7271f..f46a71489 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -3,33 +3,31 @@ """ Calculus -Originally called infinitesimal calculus or "the calculus of infinitesimals", is the mathematical study of continuous change, in the same way that geometry is the study of shape and algebra is the study of generalizations of arithmetic operations. +Originally called infinitesimal calculus or "the calculus of infinitesimals", \ +is the mathematical study of continuous change, in the same way that geometry \ +is the study of shape and algebra is the study of generalizations of arithmetic operations. """ -import numpy as np from itertools import product from typing import Optional +import numpy as np +import sympy from mathics.algorithm.integrators import ( - apply_D_to_Integral, _fubini, _internal_adaptative_simpsons_rule, + apply_D_to_Integral, decompose_domain, ) - - from mathics.algorithm.series import ( build_series, + series_derivative, series_plus_series, series_times_series, - series_derivative, ) - - from mathics.builtin.base import Builtin, PostfixOperator, SympyFunction from mathics.builtin.scoping import dynamic_scoping - from mathics.core.atoms import ( Atom, Integer, @@ -50,18 +48,15 @@ A_PROTECTED, A_READ_PROTECTED, ) - from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.convert.function import expression_to_callable_and_args from mathics.core.convert.python import from_python -from mathics.core.convert.sympy import sympy_symbol_prefix, SympyExpression, from_sympy +from mathics.core.convert.sympy import SympyExpression, from_sympy, sympy_symbol_prefix from mathics.core.evaluation import Evaluation -from mathics.eval.nevaluator import eval_N from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.number import dps, machine_epsilon from mathics.core.rules import Pattern - from mathics.core.symbols import ( BaseElement, Symbol, @@ -72,7 +67,6 @@ SymbolTimes, SymbolTrue, ) - from mathics.core.systemsymbols import ( SymbolAnd, SymbolAutomatic, @@ -93,10 +87,8 @@ SymbolSimplify, SymbolUndefined, ) - from mathics.eval.makeboxes import format_element - -import sympy +from mathics.eval.nevaluator import eval_N # These should be used in lower-level formatting SymbolDifferentialD = Symbol("System`DifferentialD") @@ -120,7 +112,7 @@ class Complexes(Builtin): class D(SympyFunction): """ :Derivative:https://en.wikipedia.org/wiki/Derivative\ - (:WMA link:https://reference.wolfram.com/language/ref/D.html) + (:WMA:https://reference.wolfram.com/language/ref/D.html)
'D[$f$, $x$]' @@ -377,7 +369,8 @@ def apply_wrong(self, expr, x, other, evaluation): class Derivative(PostfixOperator, SympyFunction): """ - :WMA link:https://reference.wolfram.com/language/ref/Derivative.html + :WMA link: + https://reference.wolfram.com/language/ref/Derivative.html
'Derivative[$n$][$f$]' @@ -531,7 +524,8 @@ def to_sympy(self, expr, **kwargs): class DiscreteLimit(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/DiscreteLimit.html + :WMA link: + https://reference.wolfram.com/language/ref/DiscreteLimit.html
'DiscreteLimit[$f$, $k$->Infinity]' @@ -730,7 +724,8 @@ class FindMaximum(_BaseFinder):
searches for a numerical maximum of $f$, starting from '$x$=$x0$'.
- 'FindMaximum' by default uses Newton\'s method, so the function of interest should have a first derivative. + 'FindMaximum' by default uses Newton\'s method, so the function of \ + interest should have a first derivative. >> FindMaximum[-(x-3)^2+2., {x, 1}] : Encountered a gradient that is effectively zero. The result returned may not be a maximum; it may be a minimum or a saddle point. @@ -769,14 +764,16 @@ class FindMaximum(_BaseFinder): class FindMinimum(_BaseFinder): r""" - :WMA link:https://reference.wolfram.com/language/ref/FindMinimum.html + :WMA link: + https://reference.wolfram.com/language/ref/FindMinimum.html
'FindMinimum[$f$, {$x$, $x0$}]'
searches for a numerical minimum of $f$, starting from '$x$=$x0$'.
- 'FindMinimum' by default uses Newton\'s method, so the function of interest should have a first derivative. + 'FindMinimum' by default uses Newton\'s method, so the function of \ + interest should have a first derivative. >> FindMinimum[(x-3)^2+2., {x, 1}] @@ -830,7 +827,8 @@ class FindRoot(_BaseFinder):
tries to solve the equation '$lhs$ == $rhs$'.
- 'FindRoot' by default uses Newton\'s method, so the function of interest should have a first derivative. + 'FindRoot' by default uses Newton\'s method, so the function of interest \ + should have a first derivative. >> FindRoot[Cos[x], {x, 1}] = {x -> 1.5708} @@ -887,8 +885,8 @@ class FindRoot(_BaseFinder): try: from mathics.algorithm.optimizers import ( - native_findroot_methods, native_findroot_messages, + native_findroot_methods, ) methods.update(native_findroot_methods) @@ -931,11 +929,13 @@ class Integers(Builtin): class Integrate(SympyFunction): r""" - :WMA link:https://reference.wolfram.com/language/ref/Integrate.html + :WMA link: + https://reference.wolfram.com/language/ref/Integrate.html
'Integrate[$f$, $x$]' -
integrates $f$ with respect to $x$. The result does not contain the additive integration constant. +
integrates $f$ with respect to $x$. The result does not contain the \ + additive integration constant.
'Integrate[$f$, {$x$, $a$, $b$}]'
computes the definite integral of $f$ with respect to $x$ from $a$ to $b$. @@ -1358,8 +1358,8 @@ class NIntegrate(Builtin): try: # builtin integrators from mathics.algorithm.integrators import ( - integrator_methods, integrator_messages, + integrator_methods, ) methods.update(integrator_methods) @@ -1370,8 +1370,8 @@ class NIntegrate(Builtin): try: # scipy integrators from mathics.builtin.scipy_utils.integrators import ( - scipy_nintegrate_methods, scipy_nintegrate_messages, + scipy_nintegrate_methods, ) methods.update(scipy_nintegrate_methods) @@ -1572,7 +1572,8 @@ class O_(Builtin):
'O[$x$]^n'
Represents a term of order $x^n$. -
O[x]^n is generated to represent omitted higher order terms in power series. +
O[x]^n is generated to represent omitted higher order terms in \ + power series.
>> Series[1/(1-x),{x,0,2}] @@ -1614,8 +1615,8 @@ class Root(SympyFunction): :WMA link:https://reference.wolfram.com/language/ref/Root.html
-
'Root[$f$, $i$]' -
represents the i-th complex root of the polynomial $f$ +
'Root[$f$, $i$]' +
represents the i-th complex root of the polynomial $f$.
>> Root[#1 ^ 2 - 1&, 1] @@ -1757,7 +1758,7 @@ class SeriesData(Builtin):
'SeriesData[...]' -
Represents a series expansion +
Represents a series expansion.
Sum of two series: @@ -2097,7 +2098,13 @@ def apply_makeboxes(self, x, x0, data, nmin, nmax, den, form, evaluation): class Solve(Builtin): """ - :Equation solving: https://en.wikipedia.org/wiki/Equation_solving (:SymPy: https://docs.sympy.org/latest/modules/solvers/solvers.html#module-sympy.solvers, :WMA: https://reference.wolfram.com/language/ref/Solve.html) + :Equation solving: + https://en.wikipedia.org/wiki/Equation_solving ( + :SymPy: + https://docs.sympy.org/latest/modules/solvers/solvers.html#module-sympy.solvers, \ + :WMA: + https://reference.wolfram.com/language/ref/Solve.html) +
'Solve[$equation$, $vars$]'
attempts to solve $equation$ for the variables $vars$. diff --git a/mathics/builtin/numbers/integer.py b/mathics/builtin/numbers/integer.py index 404c9ce0f..6b73c8a4a 100644 --- a/mathics/builtin/numbers/integer.py +++ b/mathics/builtin/numbers/integer.py @@ -4,25 +4,28 @@ Integer Functions """ - -import sympy import string +import sympy from mathics.builtin.base import Builtin, SympyFunction from mathics.core.atoms import Integer, Integer0, String -from mathics.core.attributes import ( - A_LISTABLE, - A_NUMERIC_FUNCTION, - A_PROTECTED, -) -from mathics.core.convert.sympy import from_sympy +from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED from mathics.core.convert.expression import to_mathics_list +from mathics.core.convert.sympy import from_sympy from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import SymbolPlus, SymbolTimes +def _pad(symbols, length, fill): # pads "symbols" to length "length" using "fill" + pad_length = length - len(symbols) + if pad_length <= 0: + return symbols[-pad_length:] + else: + return fill * pad_length + symbols + + def _reversed_digits( number, base ): # yield digits for number in base "base" in reverse order @@ -36,21 +39,13 @@ def _reversed_digits( number = rest -def _pad(symbols, length, fill): # pads "symbols" to length "length" using "fill" - pad_length = length - len(symbols) - if pad_length <= 0: - return symbols[-pad_length:] - else: - return fill * pad_length + symbols - - class _IntBaseBuiltin(Builtin): messages = { "basf": "Base `` must be an integer greater than 1.", } - def _valid_base(self, b, evaluation): - base = b.get_int_value() + def _valid_base(self, b: Integer, evaluation): + base = b.value if base < 2: evaluation.message(self.get_name(), "basf", base) return False @@ -82,9 +77,9 @@ class BitLength(Builtin): attributes = A_LISTABLE | A_PROTECTED summary_text = "length of the binary representation" - def apply(self, n, evaluation): + def eval(self, n, evaluation): "BitLength[n_Integer]" - n = n.get_int_value() + n = n.value if n < 0: n = -1 - n return Integer(n.bit_length()) @@ -113,7 +108,7 @@ class Ceiling(SympyFunction): rules = {"Ceiling[x_, a_]": "Ceiling[x / a] * a"} summary_text = "closest larger integer" - def apply(self, x, evaluation): + def eval(self, x, evaluation): "Ceiling[x_]" x = x.to_sympy() if x is None: @@ -123,8 +118,6 @@ def apply(self, x, evaluation): class DigitCount(_IntBaseBuiltin): """ - - :WMA link:https://reference.wolfram.com/language/ref/DigitCount.html
@@ -153,7 +146,7 @@ class DigitCount(_IntBaseBuiltin): "DigitCount[n_Integer]": "DigitCount[n, 10]", } - def apply_n_b_d(self, n, b, d, evaluation): + def eval_n_b_d(self, n, b, d, evaluation): "DigitCount[n_Integer, b_Integer, d_Integer]" base = self._valid_base(b, evaluation) if not base: @@ -167,7 +160,7 @@ def apply_n_b_d(self, n, b, d, evaluation): ) ) - def apply_n_b(self, n, b, evaluation): + def eval_n_b(self, n, b, evaluation): "DigitCount[n_Integer, b_Integer]" base = self._valid_base(b, evaluation) if not base: @@ -221,7 +214,7 @@ class Floor(SympyFunction): sympy_name = "floor" summary_text = "closest smaller integer" - def apply_real(self, x, evaluation): + def eval_real(self, x, evaluation): "Floor[x_]" x = x.to_sympy() if x is not None: @@ -230,8 +223,6 @@ def apply_real(self, x, evaluation): class FromDigits(Builtin): """ - - :WMA link:https://reference.wolfram.com/language/ref/FromDigits.html
@@ -298,7 +289,7 @@ def _parse_string(s, b): return value - def apply(self, l, b, evaluation): + def eval(self, l, b, evaluation): "FromDigits[l_, b_]" if l.get_head_name() == "System`List": value = Integer0 @@ -317,6 +308,89 @@ def apply(self, l, b, evaluation): evaluation.message("FromDigits", "nlst") +class IntegerDigits(_IntBaseBuiltin): + """ + :WMA link: + https://reference.wolfram.com/language/ref/IntegerDigits.html + +
+
'IntegerDigits[$n$]' +
returns the decimal representation of integer $x$ as list of digits. \ + $x$'s sign is ignored. + +
'IntegerDigits[$n$, $b$]' +
returns the base $b$ representation of integer $x$ as list of digits. \ + $x$'s sign is ignored. + +
'IntegerDigits[$n$, $b$, $length$]' +
returns a list of length $length$. If the number is too short, the \ + list gets padded with 0 on the left. If the number is too long, the \ + $length$ least significant digits are returned. +
+ + >> IntegerDigits[76543] + = {7, 6, 5, 4, 3} + + The same thing specifying base 10 explicitly: + >> IntegerDigits[76543, 10] + = {7, 6, 5, 4, 3} + + The sign is discarded: + >> IntegerDigits[-76543] + = {7, 6, 5, 4, 3} + + Just the last 3 digits: + >> IntegerDigits[76543, 10, 3] + = {5, 4, 3} + + A geeky way to relate Christmas with Halloween is to note that \ + Dec(imal) 25 is Oct(al) 31 + >> IntegerDigits[25, 8] + = {3, 1} + """ + + _padding = [Integer0] + rules = { + "IntegerDigits[n_Integer]": "IntegerDigits[n, 10]", + } + + summary_text = "list digits of an integer" + + def eval_n_b(self, n, b, evaluation): + "IntegerDigits[n_Integer, b_Integer]" + base = self._valid_base(b, evaluation) + return ( + ListExpression( + *[ + Integer(d) + for d in reversed(list(_reversed_digits(n.get_int_value(), base))) + ] + ) + if base + else None + ) + + def eval_n_b_length(self, n, b, length, evaluation): + "IntegerDigits[n_Integer, b_Integer, length_Integer]" + base = self._valid_base(b, evaluation) + return ( + ListExpression( + *_pad( + [ + Integer(d) + for d in reversed( + list(_reversed_digits(n.get_int_value(), base)) + ) + ], + length.get_int_value(), + self._padding, + ) + ) + if base + else None + ) + + class IntegerString(Builtin): """ :WMA link:https://reference.wolfram.com/language/ref/IntegerString.html @@ -384,179 +458,19 @@ def _symbols(self, n, b, evaluation): reversed([list_of_symbols[r] for r in _reversed_digits(n, b)]) ) - def apply_n(self, n, b, evaluation): + def eval_n(self, n, b, evaluation): "IntegerString[n_Integer, b_Integer]" s = self._symbols(n.get_int_value(), b.get_int_value(), evaluation) return String(s) if s else None - def apply_n_b_length(self, n, b, length, evaluation): + def eval_n_b_length(self, n, b, length, evaluation): "IntegerString[n_Integer, b_Integer, length_Integer]" s = self._symbols(n.get_int_value(), b.get_int_value(), evaluation) return String(_pad(s, length.get_int_value(), "0")) if s else None -# This class is duplicated. Check what is the right one, or merge... - - -class IntegerDigits(Builtin): - """ - :WMA link:https://reference.wolfram.com/language/ref/IntegerDigits.html - -
-
'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 = A_LISTABLE | A_PROTECTED - - 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]", - } - - summary_text = "digits of an integer in any base" - - 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(SymbolIntegerDigits, 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 ListExpression() - - # Note: above we checked that n and b are Integers, so we can use x.value. - digits = convert_int_to_digit_list(n.value, base.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 to_mathics_list(*digits, element_conversion_fn=Integer) - - -class IntegerDigits(_IntBaseBuiltin): - """ - :WMA link:https://reference.wolfram.com/language/ref/IntegerDigits.html - -
-
'IntegerDigits[$n$]' -
returns the decimal representation of integer $x$ as list of digits. $x$'s sign is ignored. -
'IntegerDigits[$n$, $b$]' -
returns the base $b$ representation of integer $x$ as list of digits. $x$'s sign is ignored. -
'IntegerDigits[$n$, $b$, $length$]' -
returns a list of length $length$. If the number is too short, the list gets padded with 0 on the left. If the number is too long, the $length$ least significant digits are returned. -
- - >> IntegerDigits[12345] - = {1, 2, 3, 4, 5} - >> IntegerDigits[-500] - = {5, 0, 0} - >> IntegerDigits[12345, 10, 8] - = {0, 0, 0, 1, 2, 3, 4, 5} - >> IntegerDigits[12345, 10, 3] - = {3, 4, 5} - >> IntegerDigits[11, 2] - = {1, 0, 1, 1} - >> IntegerDigits[123, 8] - = {1, 7, 3} - >> IntegerDigits[98765, 20] - = {12, 6, 18, 5} - """ - - _padding = [Integer0] - rules = { - "IntegerDigits[n_Integer]": "IntegerDigits[n, 10]", - } - summary_text = "list of digits of a number" - - def apply_n_b(self, n, b, evaluation): - "IntegerDigits[n_Integer, b_Integer]" - base = self._valid_base(b, evaluation) - return ( - ListExpression( - *[ - Integer(d) - for d in reversed(list(_reversed_digits(n.get_int_value(), base))) - ] - ) - if base - else None - ) - - def apply_n_b_length(self, n, b, length, evaluation): - "IntegerDigits[n_Integer, b_Integer, length_Integer]" - base = self._valid_base(b, evaluation) - return ( - ListExpression( - *_pad( - [ - Integer(d) - for d in reversed( - list(_reversed_digits(n.get_int_value(), base)) - ) - ], - length.get_int_value(), - self._padding, - ) - ) - if base - else None - ) - - class IntegerReverse(_IntBaseBuiltin): """ - - - :WMA link:https://reference.wolfram.com/language/ref/IntegerReverse.html
@@ -579,12 +493,12 @@ class IntegerReverse(_IntBaseBuiltin): "IntegerReverse[n_Integer]": "IntegerReverse[n, 10]", } - def apply_n_b(self, n, b, evaluation): + def eval_n_b(self, n, b, evaluation): "IntegerReverse[n_Integer, b_Integer]" base = self._valid_base(b, evaluation) if not base: return value = 0 - for digit in _reversed_digits(n.get_int_value(), base): + for digit in _reversed_digits(n.value, base): value = value * base + digit return Integer(value) diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 475c6a552..37383dc5b 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -56,6 +56,9 @@ SymbolContextPath = Symbol("System`$ContextPath") SymbolContinue = Symbol("System`Continue") SymbolCos = Symbol("System`Cos") +SymbolCosh = Symbol("System`Cosh") +SymbolCot = Symbol("System`Cot") +SymbolCoth = Symbol("System`Coth") SymbolD = Symbol("System`D") SymbolDefinition = Symbol("System`Definition") SymbolDerivative = Symbol("System`Derivative") @@ -179,6 +182,7 @@ SymbolSign = Symbol("System`Sign") SymbolSimplify = Symbol("System`Simplify") SymbolSin = Symbol("System`Sin") +SymbolSinh = Symbol("System`Sinh") SymbolSlot = Symbol("System`Slot") SymbolSqrt = Symbol("System'Sqrt") SymbolSqrtBox = Symbol("System`SqrtBox") @@ -193,6 +197,8 @@ SymbolSubsuperscriptBox = Symbol("System`SubsuperscriptBox") SymbolSuperscriptBox = Symbol("System`SuperscriptBox") SymbolTable = Symbol("System`Table") +SymbolTan = Symbol("System`Tan") +SymbolTanh = Symbol("System`Tanh") SymbolTeXForm = Symbol("System`TeXForm") SymbolThrow = Symbol("System`Throw") SymbolToString = Symbol("System`ToString") From dd06009300eb359f86fe29625e43be226dd84e99 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 20 Dec 2022 13:45:12 -0500 Subject: [PATCH 056/121] Remove @lru_cache in favor of homegrown cache We need __new__ anyway to compute hash values. In some cases we can canonicalize object by value, e.g. for Rational by reducing the value. Document better what is going on here. --- mathics/core/atoms.py | 210 +++++++++++++++++++++++++++--------------- 1 file changed, 136 insertions(+), 74 deletions(-) diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index 7e88608d2..6daa267ca 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -37,11 +37,36 @@ class Number(Atom, ImmutableValueMixin, NumericOperators): being: Integer, Rational, Real, Complex. """ + def __getnewargs__(self): + """ + __getnewargs__ is used in pickle loading to ensure __new__ is + called with the right value. + + Most of the time a number takes one argument - its value + When there is a kind of number, like Rational, or Complex, + that has more than one argument, it should define this method + accordingly. + """ + return (self._value,) + def __str__(self) -> str: return str(self.value) - def is_numeric(self, evaluation=None) -> bool: - return True + # FIXME: can we refactor or subclass objects to remove pattern_sort? + def get_sort_key(self, pattern_sort=False) -> tuple: + """ + get_sort_key is used in Expression evaluation to determine how to + order its list of elements. The tuple returned contains + rank orders for different level as is found in say + Python version release numberso or Python package version numbers. + + This is the default routine for Number. Subclasses of Number like + Complex may need to define this differently. + """ + if pattern_sort: + return super().get_sort_key(True) + else: + return (0, 0, self._value, 0, 1) @property def is_literal(self) -> bool: @@ -51,8 +76,25 @@ def is_literal(self) -> bool: """ return True + def is_numeric(self, evaluation=None) -> bool: + return True + + def to_mpmath(self): + """ + Convert self._value to an mnpath number. + + This is the default implementation for Number. + There are kinds of numbers, like Rational, or Complex, that + need to work differently than this default, and they will + change the implementation accordingly. + """ + return mpmath.mpf(self._value) + @property - def value(self) -> bool: + def value(self): + """ + Returns this number's value. + """ return self._value @@ -101,27 +143,35 @@ class Integer(Number): value: int class_head_name = "System`Integer" - # Collection of Integers defined so far. + # Dictionary of Integer constant values defined so far. # We use this for object uniqueness. + # The key is the Integer's Python `int` value, and the + # dictionary's value is the corresponding Mathics Integer object. _integers = {} - # We use __new__ here to unsure that two Integer's that have the same value - # return the same object. + # We use __new__ here to ensure that two Integer's that have the same value + # return the same object, and to set an object hash value. + # Consider also @lru_cache, and mechanisms for limiting and + # clearing the cache and the object store which might be useful in implementing + # Builtin Share[]. def __new__(cls, value) -> "Integer": n = int(value) - key = (cls, n) - self = cls._integers.get(key) + self = cls._integers.get(value) if self is None: self = super().__new__(cls) self._value = n # Cache object so we don't allocate again. - self._integers[key] = self + self._integers[value] = self # Set a value for self.__hash__() once so that every time - # it is used this is fast. - self.hash = hash(key) + # it is used this is fast. Note that in contrast to the + # cached object key, the hash key needs to be unique across all + # Python objects, so we include the class in the + # event that different objects have the same Python value + self.hash = hash((cls, n)) + return self def __eq__(self, other) -> bool: @@ -145,6 +195,8 @@ def __gt__(self, other) -> bool: else super().__gt__(other) ) + # __hash__ is defined so that we can store Number-derived objects + # in a set or dictionary. def __hash__(self): return self.hash @@ -188,9 +240,6 @@ def make_boxes(self, form) -> "String": 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 @@ -211,21 +260,12 @@ def sameQ(self, other) -> bool: """Mathics SameQ""" return isinstance(other, Integer) and self._value == other.value - def get_sort_key(self, pattern_sort=False) -> tuple: - 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 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) @@ -304,11 +344,6 @@ def __ne__(self, other) -> bool: def atom_to_boxes(self, f, evaluation): return self.make_boxes(f.get_name()) - def get_sort_key(self, pattern_sort=False) -> tuple: - 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) @@ -326,8 +361,10 @@ class MachineReal(Real): Stored internally as a python float. """ - # Collection of MachineReals defined so far. + # Dictionary of MachineReal constant values defined so far. # We use this for object uniqueness. + # The key is the MachineReal's Python `float` value, and the + # dictionary's value is the corresponding Mathics MachineReal object. _machine_reals = {} def __new__(cls, value) -> "MachineReal": @@ -335,23 +372,25 @@ def __new__(cls, value) -> "MachineReal": if math.isinf(n) or math.isnan(n): raise OverflowError - key = (cls, n) - self = cls._machine_reals.get(key) + self = cls._machine_reals.get(n) if self is None: self = Number.__new__(cls) self._value = n # Cache object so we don't allocate again. - self._machine_reals[key] = self + self._machine_reals[n] = self # Set a value for self.__hash__() once so that every time - # it is used this is fast. - self.hash = hash(key) - return self + # it is used this is fast. Note that in contrast to the + # cached object key, the hash key needs to be unique across all + # Python objects, so we include the class in the + # event that different objects have the same Python value + self.hash = hash((cls, n)) - def __getnewargs__(self): - return (self.value,) + return self + # __hash__ is defined so that we can store Number-derived objects + # in a set or dictionary. def __hash__(self): return self.hash @@ -424,13 +463,6 @@ def to_python(self, *args, **kwargs) -> float: def to_sympy(self, *args, **kwargs): return sympy.Float(self.value) - def to_mpmath(self): - return mpmath.mpf(self.value) - - @property - def value(self) -> bool: - return self._value - MachineReal0 = MachineReal(0) @@ -444,32 +476,35 @@ class PrecisionReal(Real): Note: Plays nicely with the mpmath.mpf (float) type. """ - # Collection of MachineReals defined so far. + # Dictionary of PrecisionReal constant values defined so far. # We use this for object uniqueness. + # The key is the PrecisionReal's sympy.Float, and the + # dictionary's value is the corresponding Mathics PrecisionReal object. _precision_reals = {} value: sympy.Float def __new__(cls, value) -> "PrecisionReal": n = sympy.Float(value) - key = (cls, n) - self = cls._precision_reals.get(key) + self = cls._precision_reals.get(n) if self is None: self = Number.__new__(cls) self._value = n # Cache object so we don't allocate again. - self._precision_reals[key] = self + self._precision_reals[n] = self # Set a value for self.__hash__() once so that every time - # it is used this is fast. - self.hash = hash(key) + # it is used this is fast. Note that in contrast to the + # cached object key, the hash key needs to be unique across all + # Python objects, so we include the class in the + # event that different objects have the same Python value. + self.hash = hash((cls, n)) return self - def __getnewargs__(self): - return (self.value,) - + # __hash__ is defined so that we can store Number-derived objects + # in a set or dictionary. def __hash__(self): return self.hash @@ -527,20 +562,16 @@ def to_python(self, *args, **kwargs): def to_sympy(self, *args, **kwargs): return self.value - def to_mpmath(self): - return mpmath.mpf(self.value) - - @property - def value(self): - return self._value - class ByteArrayAtom(Atom, ImmutableValueMixin): value: str class_head_name = "System`ByteArrayAtom" - # We use __new__ here to unsure that two ByteArrayAtom's that have the same value - # return the same object. + # We use __new__ here to ensure that two ByteArrayAtom's that have the same value + # return the same object, and to set an object hash value. + # Consider also @lru_cache, and mechanisms for limiting and + # clearing the cache and the object store which might be useful in implementing + # Builtin Share[]. def __new__(cls, value): self = super().__new__(cls) if type(value) in (bytes, bytearray): @@ -629,8 +660,18 @@ class Complex(Number): real: Type[Number] imag: Type[Number] + # Dictionary of Complex constant values defined so far. + # We use this for object uniqueness. + # The key is the Complex value's real and imaginary parts as a tuple, + # dictionary's value is the corresponding Mathics Complex object. + _complex_numbers = {} + + # We use __new__ here to ensure that two Integer's that have the same value + # return the same object, and to set an object hash value. + # Consider also @lru_cache, and mechanisms for limiting and + # clearing the cache and the object store which might be useful in implementing + # Builtin Share[]. 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 imag is SymbolInfinity: @@ -646,14 +687,26 @@ def __new__(cls, real, imag): if isinstance(imag, MachineReal) and not isinstance(real, MachineReal): real = real.round() - self.real = real - self.imag = imag + value = (real, imag) + self = cls._complex_numbers.get(value) + if self is None: - # Set a value for self.__hash__() once so that every time - # it is used this is fast. - self.hash = hash(("Complex", real, imag)) + self = super().__new__(cls) + self.real = real + self.imag = imag + + self._value = value + + # Cache object so we don't allocate again. + self._complex_numbers[value] = self + + # Set a value for self.__hash__() once so that every time + # it is used this is fast. Note that in contrast to the + # cached object key, the hash key needs to be unique across all + # Python objects, so we include the class in the + # event that different objects have the same Python value + self.hash = hash((cls, value)) - self._value = (self.real, self.imag) return self def __hash__(self): @@ -684,7 +737,14 @@ def default_format(self, evaluation, form) -> str: self.imag.default_format(evaluation, form), ) + # Note we can def get_sort_key(self, pattern_sort=False) -> tuple: + """ + get_sort_key is used in Expression evaluation to determine how to + order its list of elements. The tuple returned contains + rank orders for different level as is found in say + Python version release numberso or Python package version numbers. + """ if pattern_sort: return super().get_sort_key(True) else: @@ -775,8 +835,11 @@ class Rational(Number): # Collection of integers defined so far. _rationals = {} - # We use __new__ here to unsure that two Rationals's that have the same value - # return the same object. + # We use __new__ here to ensure that two Rationals's that have the same value + # return the same object, and to set an object hash value. + # Consider also @lru_cache, and mechanisms for limiting and + # clearing the cache and the object store which might be useful in implementing + # Builtin Share[]. def __new__(cls, numerator, denominator=1) -> "Rational": value = sympy.Rational(numerator, denominator) @@ -795,6 +858,8 @@ def __new__(cls, numerator, denominator=1) -> "Rational": self.hash = hash(key) return self + # __hash__ is defined so that we can store Number-derived objects + # in a set or dictionary. def __hash__(self): return self.hash @@ -806,9 +871,6 @@ def atom_to_boxes(self, f, evaluation): 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) From 9002a2264c4489fe5cec246d6e60524cb147598c Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 21 Dec 2022 06:03:55 -0300 Subject: [PATCH 057/121] docstr url for files_io, intfns, list and trace --- mathics/builtin/files_io/files.py | 56 ++++++++++++ mathics/builtin/files_io/filesystem.py | 103 ++++++++++++++++++++++- mathics/builtin/files_io/importexport.py | 31 +++++++ mathics/builtin/intfns/combinatorial.py | 13 +++ mathics/builtin/intfns/divlike.py | 22 +++++ mathics/builtin/intfns/misc.py | 2 + mathics/builtin/intfns/recurrence.py | 11 +++ mathics/builtin/list/associations.py | 12 +++ mathics/builtin/list/constructing.py | 18 ++++ mathics/builtin/list/rearrange.py | 28 ++++++ mathics/builtin/lists.py | 50 +++++++++++ mathics/builtin/trace.py | 12 +++ 12 files changed, 355 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index 4facf71da..e208fc0a9 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -73,6 +73,8 @@ class Input_(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/Input_.html +
'$Input'
is the name of the stream from which input is currently being read. @@ -181,6 +183,8 @@ def apply_path(self, path, evaluation, options): class Character(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Character.html +
'Character'
is a data type for 'Read'. @@ -192,6 +196,8 @@ class Character(Builtin): class Close(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Close.html +
'Close[$stream$]'
closes an input or output stream. @@ -238,6 +244,8 @@ def apply(self, channel, evaluation): class EndOfFile(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/EndOfFile.html +
'EndOfFile'
is returned by 'Read' when the end of an input stream is reached. @@ -249,6 +257,8 @@ class EndOfFile(Builtin): class Expression_(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Expression.html +
'Expression'
is a data type for 'Read'. @@ -263,6 +273,8 @@ class Expression_(Builtin): class FilePrint(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FilePrint.html +
'FilePrint[$file$]'
prints the raw contents of $file$. @@ -347,6 +359,8 @@ def apply(self, path, evaluation, options): class Number_(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Number.html +
'Number'
is a data type for 'Read'. @@ -359,6 +373,8 @@ class Number_(Builtin): class Get(PrefixOperator): r""" + :WMA link:https://reference.wolfram.com/language/ref/Get.html +
'<<$name$'
reads a file and evaluates each expression, returning only the last one. @@ -460,6 +476,8 @@ def apply_default(self, filename, evaluation): class InputFileName_(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$InputFileName.html +
'$InputFileName'
is the name of the file from which input is currently being read. @@ -480,6 +498,8 @@ def evaluate(self, evaluation): class InputStream(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/InputStream.html +
'InputStream[$name$, $n$]'
represents an input stream for functions such as 'Read' or 'Find'. @@ -498,6 +518,8 @@ class InputStream(Builtin): class OpenRead(_OpenAction): """ + :WMA link:https://reference.wolfram.com/language/ref/OpenRead.html +
'OpenRead["file"]'
opens a file and returns an 'InputStream'. @@ -537,6 +559,8 @@ class OpenRead(_OpenAction): class OpenWrite(_OpenAction): """ + :WMA link:https://reference.wolfram.com/language/ref/OpenWrite.html +
'OpenWrite["file"]'
opens a file and returns an OutputStream. @@ -560,6 +584,8 @@ class OpenWrite(_OpenAction): class OpenAppend(_OpenAction): """ + :WMA link:https://reference.wolfram.com/language/ref/OpenAppend.html +
'OpenAppend["file"]'
opens a file and returns an OutputStream to which writes are appended. @@ -586,6 +612,8 @@ class OpenAppend(_OpenAction): class Put(BinaryOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/Put.html +
'$expr$ >> $filename$'
write $expr$ to a file. @@ -681,6 +709,8 @@ def apply_default(self, exprs, filename, evaluation): class PutAppend(BinaryOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/PutAppend.html +
'$expr$ >>> $filename$'
append $expr$ to a file. @@ -771,6 +801,8 @@ def apply_default(self, exprs, filename, evaluation): class Read(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Read.html +
'Read[$stream$]'
reads the input stream and returns one expression. @@ -1164,6 +1196,8 @@ def apply_nostream(self, arg1, arg2, evaluation): class ReadList(Read): """ + :WMA link:https://reference.wolfram.com/language/ref/ReadList.html +
'ReadList["$file$"]'
Reads all the expressions until the end of file. @@ -1286,6 +1320,8 @@ def apply_m(self, channel, types, m, evaluation, options): class StreamPosition(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StreamPosition.html +
'StreamPosition[$stream$]'
returns the current position in a stream as an integer. @@ -1325,6 +1361,8 @@ def apply_default(self, stream, evaluation): class SetStreamPosition(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/SetStreamPosition.html +
'SetStreamPosition[$stream$, $n$]'
sets the current position in a stream. @@ -1405,6 +1443,8 @@ def apply_default(self, stream, evaluation): class Skip(Read): """ + :WMA link:https://reference.wolfram.com/language/ref/Skip.html +
'Skip[$stream$, $type$]'
skips ahead in an input steream by one object of the specified $type$. @@ -1479,6 +1519,8 @@ def apply(self, name, n, types, m, evaluation, options): class Find(Read): """ + :WMA link:https://reference.wolfram.com/language/ref/Find.html +
'Find[$stream$, $text$]'
find the first line in $stream$ that contains $text$. @@ -1554,6 +1596,8 @@ def apply(self, name, n, text, evaluation, options): class OutputStream(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/OutputStream.html +
'OutputStream[$name$, $n$]'
represents an output stream. @@ -1569,6 +1613,8 @@ class OutputStream(Builtin): class StringToStream(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/StringToStream.html +
'StringToStream[$string$]'
converts a $string$ to an open input stream. @@ -1601,6 +1647,8 @@ def apply(self, string, evaluation): class Streams(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Streams.html +
'Streams[]'
returns a list of all open streams. @@ -1653,6 +1701,8 @@ def apply_name(self, name, evaluation): class Record(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Record.html +
'Record'
is a data type for 'Read'. @@ -1664,6 +1714,8 @@ class Record(Builtin): class Word(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Word.html +
'Word'
is a data type for 'Read'. @@ -1675,6 +1727,8 @@ class Word(Builtin): class Write(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Write.html +
'Write[$channel$, $expr1$, $expr2$, ...]'
writes the expressions to the output channel followed by a newline. @@ -1721,6 +1775,8 @@ def apply(self, channel, expr, evaluation): class WriteString(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/WriteString.html +
'WriteString[$stream$, $str1, $str2$, ... ]'
writes the strings to the output stream. diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py index 8391319ff..8a6fa9ff5 100644 --- a/mathics/builtin/files_io/filesystem.py +++ b/mathics/builtin/files_io/filesystem.py @@ -63,6 +63,8 @@ class AbsoluteFileName(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/AbsoluteFileName.html +
'AbsoluteFileName["$name$"]'
returns the absolute version of the given filename. @@ -103,14 +105,16 @@ def eval(self, name, evaluation): return String(osp.abspath(result)) -class BaseDirectory(Predefined): +class BaseDirectory_(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$BaseDirectory.html +
-
'$UserBaseDirectory' +
'$BaseDirectory'
returns the folder where user configurations are stored.
- >> $RootDirectory + >> $BaseDirectory = ... """ @@ -124,6 +128,8 @@ def evaluate(self, evaluation): class CopyDirectory(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/CopyDirectory.html +
'CopyDirectory["$dir1$", "$dir2$"]'
copies directory $dir1$ to $dir2$. @@ -172,6 +178,8 @@ def eval(self, dirs, evaluation): class CopyFile(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/CopyFile.html +
'CopyFile["$file1$", "$file2$"]'
copies $file1$ to $file2$. @@ -231,6 +239,8 @@ def eval(self, source, dest, evaluation): class CreateDirectory(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/CreateDirectory.html +
'CreateDirectory["$dir$"]'
creates a directory called $dir$. @@ -293,6 +303,8 @@ def eval_empty(self, evaluation, options): class CreateFile(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/CreateFile.html +
'CreateFile["filename"]'
Creates a file named "filename" temporary file, but do not open it. @@ -329,6 +341,8 @@ def eval(self, filename, evaluation, **options): class CreateTemporary(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/CreateTemporary.html +
'CreateTemporary[]'
Creates a temporary file, but do not open it. @@ -348,6 +362,8 @@ def eval_0(self, evaluation): class DeleteDirectory(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DeleteDirectory.html +
'DeleteDirectory["$dir$"]'
deletes a directory called $dir$. @@ -410,6 +426,8 @@ def eval(self, dirname, evaluation, options): class DeleteFile(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DeleteFile.html +
'Delete["$file$"]'
deletes $file$. @@ -475,6 +493,8 @@ def eval(self, filename, evaluation): class Directory(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Directory.html +
'Directory[]'
returns the current working directory. @@ -494,6 +514,8 @@ def eval(self, evaluation): class DirectoryName(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DirectoryName.html +
'DirectoryName["$name$"]'
extracts the directory name from a filename. @@ -566,6 +588,8 @@ def eval(self, name, evaluation, options): class DirectoryStack(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DirectoryStack.html +
'DirectoryStack[]'
returns the directory stack. @@ -585,6 +609,8 @@ def eval(self, evaluation): class DirectoryQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DirectoryQ.html +
'DirectoryQ["$name$"]'
returns 'True' if the directory called $name$ exists and 'False' otherwise. @@ -627,6 +653,8 @@ def eval(self, pathname, evaluation): class ExpandFileName(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ExpandFileName.html +
'ExpandFileName["$name$"]'
expands $name$ to an absolute filename for your system. @@ -658,6 +686,8 @@ def eval(self, name, evaluation): class File(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/File.html +
'File["$file$"]'
is a symbolic representation of an element in the local file system. @@ -669,6 +699,8 @@ class File(Builtin): class FileBaseName(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileBaseName.html +
'FileBaseName["$file$"]'
gives the base name for the specified file name. @@ -702,6 +734,8 @@ def eval(self, filename, evaluation, options): class FileByteCount(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileByteCount.html +
'FileByteCount[$file$]'
returns the number of bytes in $file$. @@ -746,6 +780,8 @@ def eval(self, filename, evaluation): class FileDate(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileDate.html +
'FileDate[$file$, $types$]'
returns the time and date at which the file was last modified. @@ -848,6 +884,8 @@ def eval_default(self, path, evaluation): class FileExistsQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileExistsQ.html +
'FileExistsQ["$file$"]'
returns 'True' if $file$ exists and 'False' otherwise. @@ -883,6 +921,8 @@ def eval(self, filename, evaluation): class FileExtension(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileExtension.html +
'FileExtension["$file$"]'
gives the extension for the specified file name. @@ -915,6 +955,8 @@ def eval(self, filename, evaluation, options): class FileHash(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileHash.html +
'FileHash[$file$]'
returns an integer hash for the given $file$. @@ -988,6 +1030,8 @@ def eval(self, filename, hashtype, format, evaluation): class FileInformation(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileInformation.html +
'FileInformation["$file$"]'
returns information about $file$. @@ -1010,6 +1054,8 @@ class FileInformation(Builtin): class FileNameDepth(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileNameDepth.html +
'FileNameDepth["$name$"]'
gives the number of path parts in the given filename. @@ -1040,6 +1086,8 @@ class FileNameDepth(Builtin): class FileNameJoin(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileNameJoin.html +
'FileNameJoin[{"$dir_1$", "$dir_2$", ...}]'
joins the $dir_i$ together into one path. @@ -1110,6 +1158,8 @@ def eval(self, pathlist, evaluation, options): class FileType(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileType.html +
'FileType["$file$"]'
gives the type of a file, a string. This is typically 'File', 'Directory' or 'None'. @@ -1154,6 +1204,8 @@ def eval(self, filename, evaluation): class FindFile(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileFind.html +
'FindFile[$name$]'
searches '$Path' for the given filename. @@ -1199,6 +1251,8 @@ def eval(self, name, evaluation): class FileNames(Builtin): r""" + :WMA link:https://reference.wolfram.com/language/ref/FileNames.html +
'FileNames[]'
Returns a list with the filenames in the current working folder. @@ -1222,6 +1276,7 @@ class FileNames(Builtin): >> FileNames["*.m", "formats", Infinity]//Length = ... """ + # >> FileNames[]//Length # = 2 fmtmaps = {Symbol("System`All"): "*"} @@ -1337,6 +1392,8 @@ def eval_3(self, forms, paths, n, evaluation, **options): class FileNameSplit(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileNameSplit.html +
'FileNameSplit["$filenames$"]'
splits a $filename$ into a list of parts. @@ -1397,6 +1454,8 @@ def eval(self, filename, evaluation, options): class FileNameTake(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileNameTake.html +
'FileNameTake["$file$"]'
returns the last path element in the file name $name$. @@ -1441,6 +1500,8 @@ def eval_n(self, filename, n, evaluation, options): class FindList(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FindList.html +
'FindList[$file$, $text$]'
returns a list of all lines in $file$ that contain $text$. @@ -1551,6 +1612,8 @@ def eval(self, filename, text, n, evaluation, options): class HomeDirectory(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/HomeDirectory.html +
'$HomeDirectory'
returns the users HOME directory. @@ -1570,6 +1633,8 @@ def evaluate(self, evaluation): class InitialDirectory(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$InitialDirectory.html +
'$InitialDirectory'
returns the directory from which \\Mathics was started. @@ -1589,6 +1654,8 @@ def evaluate(self, evaluation): class InstallationDirectory(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/InstallationDirectory.html +
'$InstallationDirectory'
returns the top-level directory in which \\Mathics was installed. @@ -1608,6 +1675,8 @@ def evaluate(self, evaluation): class Needs(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Needs.html +
'Needs["context`"]'
loads the specified context if not already in '$Packages'. @@ -1745,6 +1814,8 @@ def eval(self, context, evaluation): class OperatingSystem(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/OperatingSystem.html +
'$OperatingSystem'
gives the type of operating system running Mathics. @@ -1771,6 +1842,8 @@ def evaluate(self, evaluation): class ParentDirectory(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ParentDirectory.html +
'ParentDirectory[]'
returns the parent of the current working directory. @@ -1808,6 +1881,8 @@ def eval(self, path, evaluation): class Path(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/Path.html +
'$Path'
returns the list of directories to search when looking for a file. @@ -1827,6 +1902,8 @@ def evaluate(self, evaluation): class PathnameSeparator(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$PathnameSeparator.html +
'$PathnameSeparator'
returns a string for the seperator in paths. @@ -1845,6 +1922,8 @@ def evaluate(self, evaluation): class RenameDirectory(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/RenameDirectory.html +
'RenameDirectory["$dir1$", "$dir2$"]'
renames directory $dir1$ to $dir2$. @@ -1893,6 +1972,8 @@ def eval(self, dirs, evaluation): class RenameFile(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/RenameFile.html +
'RenameFile["$file1$", "$file2$"]'
renames $file1$ to $file2$. @@ -1952,6 +2033,8 @@ def eval(self, source, dest, evaluation): class ResetDirectory(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ResetDirectory.html +
'ResetDirectory[]'
pops a directory from the directory stack and returns it. @@ -1980,6 +2063,8 @@ def eval(self, evaluation): class RootDirectory(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$RootDirectory.html +
'$RootDirectory'
returns the system root directory. @@ -1999,6 +2084,8 @@ def evaluate(self, evaluation): class SetDirectory(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/SetDirectory.html +
'SetDirectory[$dir$]'
sets the current working directory to $dir$. @@ -2047,6 +2134,8 @@ def eval(self, path, evaluation): class SetFileDate(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/SetFileDate.html +
'SetFileDate["$file$"]'
set the file access and modification dates of $file$ to the current date. @@ -2190,6 +2279,8 @@ def eval_2arg(self, filename, datelist, evaluation): class TemporaryDirectory(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$TemporaryDirectory.html +
'$TemporaryDirectory'
returns the directory used for temporary files. @@ -2208,6 +2299,8 @@ def evaluate(self, evaluation): class ToFileName(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ToFileName.html +
'ToFileName[{"$dir_1$", "$dir_2$", ...}]'
joins the $dir_i$ together into one path. @@ -2235,6 +2328,8 @@ class ToFileName(Builtin): class UserBaseDirectory(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/UserBaseDirectory.html +
'$UserBaseDirectory'
returns the folder where user configurations are stored. @@ -2254,6 +2349,8 @@ def evaluate(self, evaluation): class URLSave(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/URLSave.html +
'URLSave["url"]'
Save "url" in a temporary file. diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index a8397aed3..aad4fdda3 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -950,6 +950,8 @@ def _importer_exporter_options( class ImportFormats(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$ImportFormats.html +
'$ImportFormats'
returns a list of file formats supported by Import. @@ -968,6 +970,8 @@ def evaluate(self, evaluation): class ExportFormats(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$ExportFormats.html +
'$ExportFormats'
returns a list of file formats supported by Export. @@ -986,6 +990,8 @@ def evaluate(self, evaluation): class ConverterDumpsExtensionMappings(Predefined): """ + :internal native symbol: +
'$extensionMappings'
Returns a list of associations between file extensions and file types. @@ -1003,6 +1009,8 @@ def evaluate(self, evaluation): class ConverterDumpsFormatMappings(Predefined): """ + :internal native symbol: +
'$formatMappings'
Returns a list of associations between file extensions and file types. @@ -1011,6 +1019,8 @@ class ConverterDumpsFormatMappings(Predefined): summary_text = "associations between file extensions and file types" context = "System`ConvertersDump`" + # TODO: Check why this does not follows the convention of + # starting words in identifiers with caps. name = "$formatMappings" attributes = A_NO_ATTRIBUTES @@ -1020,6 +1030,8 @@ def evaluate(self, evaluation): class RegisterImport(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/RegisterImport.html +
'RegisterImport["$format$", $defaultFunction$]'
register '$defaultFunction$' as the default function used when importing from a file of type '"$format$"'. @@ -1151,6 +1163,8 @@ def apply(self, formatname, function, posts, evaluation, options): class RegisterExport(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/RegisterExport.html +
'RegisterExport["$format$", $func$]'
register '$func$' as the default function used when exporting from a file of type '"$format$"'. @@ -1208,6 +1222,8 @@ def apply(self, formatname, function, evaluation, options): class URLFetch(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/URLFetch.html +
'URLFetch[$URL$]'
Returns the content of $URL$ as a string. @@ -1292,6 +1308,8 @@ def determine_filetype(): class Import(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Import.html +
'Import["$file$"]'
imports data from a file. @@ -1573,6 +1591,8 @@ def get_results(tmp_function, findfile): class ImportString(Import): """ + :WMA link:https://reference.wolfram.com/language/ref/ImportString.html +
'ImportString["$data$", "$format$"]'
imports data in the specified format from a string. @@ -1676,6 +1696,8 @@ def determine_filetype(): class Export(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Export.html +
'Export["$file$.$ext$", $expr$]'
exports $expr$ to a file, using the extension $ext$ to determine the format. @@ -1895,6 +1917,8 @@ def _infer_form(self, filename, evaluation): class ExportString(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ExportString.html +
'ExportString[$expr$, $form$]'
exports $expr$ to a string, in the format $form$. @@ -2077,6 +2101,8 @@ def apply_elements(self, expr, elems, evaluation, **options): class FileFormat(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/FileFormat.html +
'FileFormat["$name$"]'
attempts to determine what format 'Import' should use to import specified file. @@ -2187,6 +2213,8 @@ def apply(self, filename, evaluation): class B64Encode(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/B64Encode.html +
'System`Convert`B64Dump`B64Encode[$expr$]'
Encodes $expr$ in Base64 coding @@ -2223,6 +2251,7 @@ def apply(self, expr, evaluation): class B64Decode(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/B64Decode.html
'System`Convert`B64Dump`B64Decode[$string$]'
Decode $string$ in Base64 coding to an expression. @@ -2258,6 +2287,8 @@ def apply(self, expr, evaluation): class ConvertCommonDumpRemoveLinearSyntax(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ConvertCommonDumpRemoveLinearSyntax.html +
'System`Convert`CommonDump`RemoveLinearSyntax[$something$]'
Keine anung... Undocumented in wma diff --git a/mathics/builtin/intfns/combinatorial.py b/mathics/builtin/intfns/combinatorial.py index 85403f8be..9bbe84c85 100644 --- a/mathics/builtin/intfns/combinatorial.py +++ b/mathics/builtin/intfns/combinatorial.py @@ -84,6 +84,7 @@ class _NoBoolVector(Exception): class Binomial(_MPMathFunction): """ + :Binomial Coefficient: https://en.wikipedia.org/wiki/Binomial_coefficient (:SymPy: https://docs.sympy.org/latest/modules/functions/combinatorial.html#binomial, :WMA: https://reference.wolfram.com/language/ref/Binomial.html)
@@ -188,6 +189,8 @@ def _compute(self, n, c_ff, c_ft, c_tf, c_tt): class MatchingDissimilarity(_BooleanDissimilarity): """ + :WMA link:https://reference.wolfram.com/language/ref/MatchingDissimilarity.html +
'MatchingDissimilarity[$u$, $v$]'
returns the Matching dissimilarity between the two boolean 1-D lists $u$ and $v$, which is defined as (c_tf + c_ft) / $n$, where $n$ is len($u$) and c_ij is the number of occurrences of $u$[$k$]=$i$ and $v$[k]=$j$ for $k$ < $n$. @@ -242,6 +245,8 @@ def apply(self, values, evaluation): class RogersTanimotoDissimilarity(_BooleanDissimilarity): """ + :WMA link:https://reference.wolfram.com/language/ref/RogersTanimotoDissimilarity.html +
'RogersTanimotoDissimilarity[$u$, $v$]'
returns the Rogers-Tanimoto dissimilarity between the two boolean 1-D lists $u$ and $v$, @@ -262,6 +267,8 @@ def _compute(self, n, c_ff, c_ft, c_tf, c_tt): class RussellRaoDissimilarity(_BooleanDissimilarity): """ + :WMA link:https://reference.wolfram.com/language/ref/RusselRaoDissimilarity.html +
'RussellRaoDissimilarity[$u$, $v$]'
returns the Russell-Rao dissimilarity between the two boolean 1-D lists $u$ and $v$, @@ -281,6 +288,8 @@ def _compute(self, n, c_ff, c_ft, c_tf, c_tt): class SokalSneathDissimilarity(_BooleanDissimilarity): """ + :WMA link:https://reference.wolfram.com/language/ref/SokalSneathDissimilarity.html +
'SokalSneathDissimilarity[$u$, $v$]'
returns the Sokal-Sneath dissimilarity between the two boolean 1-D lists $u$ and $v$, @@ -300,6 +309,8 @@ def _compute(self, n, c_ff, c_ft, c_tf, c_tt): class Subsets(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Subsets.html +
'Subsets[$list$]'
finds a list of all possible subsets of $list$. @@ -556,6 +567,8 @@ def apply_atom_pattern(self, list, n, spec, evaluation): class YuleDissimilarity(_BooleanDissimilarity): """ + :WMA link:https://reference.wolfram.com/language/ref/YuleDissimilarity.html +
'YuleDissimilarity[$u$, $v$]'
returns the Yule dissimilarity between the two boolean 1-D lists $u$ and $v$, which is defined as R / (c_tt * c_ff + R / 2) where n is len($u$), c_ij is the number of occurrences of $u$[k]=i and $v$[k]=j for $k$<$n$, and $R$ = 2 * c_tf * c_ft. diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index 47e35ede5..0e707177c 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -34,6 +34,8 @@ class CompositeQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/CompositeQ.html +
'CompositeQ[$n$]'
returns True if $n$ is a composite number @@ -58,6 +60,8 @@ def apply(self, n, evaluation): class CoprimeQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/CoprimeQ.html +
'CoprimeQ[$x$, $y$]'
tests whether $x$ and $y$ are coprime by computing their greatest common divisor. @@ -104,6 +108,8 @@ def apply(self, args, evaluation): class Divisible(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Divisible.html +
'Divisible[$n$, $m$]'
returns 'True' if $n$ is divisible by $m$, and 'False' otherwise. @@ -131,6 +137,8 @@ class Divisible(Builtin): class EvenQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/EvenQ.html +
'EvenQ[$x$]'
returns 'True' if $x$ is even, and 'False' otherwise. @@ -154,6 +162,8 @@ def test(self, n): class GCD(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/GCD.html +
'GCD[$n1$, $n2$, ...]'
computes the greatest common divisor of the given integers. @@ -189,6 +199,8 @@ def apply(self, ns, evaluation): class LCM(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/LCM.html +
'LCM[$n1$, $n2$, ...]'
computes the least common multiple of the given integers. @@ -218,6 +230,8 @@ def apply(self, ns: List[Integer], evaluation): class Mod(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Mod.html +
'Mod[$x$, $m$]'
returns $x$ modulo $m$. @@ -287,6 +301,8 @@ def apply_k_n(self, k: Integer, n: Integer, evaluation): class OddQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/OddQ.html +
'OddQ[$x$]'
returns 'True' if $x$ is odd, and 'False' otherwise. @@ -358,6 +374,8 @@ def apply(self, a: Integer, b: Integer, m: Integer, evaluation): class PrimeQ(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/PrimeQ.html +
'PrimeQ[$n$]'
returns 'True' if $n$ is a prime number. @@ -410,6 +428,8 @@ def apply(self, n, evaluation): class Quotient(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Quotient.html +
'Quotient[m, n]'
computes the integer quotient of $m$ and $n$. @@ -448,6 +468,8 @@ def apply(self, m: Integer, n: Integer, evaluation): class QuotientRemainder(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/QuotientRemainder.html +
'QuotientRemainder[m, n]'
computes a list of the quotient and remainder from division of $m$ by $n$. diff --git a/mathics/builtin/intfns/misc.py b/mathics/builtin/intfns/misc.py index efca6b257..fb6c3254f 100644 --- a/mathics/builtin/intfns/misc.py +++ b/mathics/builtin/intfns/misc.py @@ -10,6 +10,8 @@ class BernoulliB(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/BernoulliB.html +
'BernoulliB[$n$]'
represents the Bernouilli number B_$n$. diff --git a/mathics/builtin/intfns/recurrence.py b/mathics/builtin/intfns/recurrence.py index 0a666f95c..d9927b35b 100644 --- a/mathics/builtin/intfns/recurrence.py +++ b/mathics/builtin/intfns/recurrence.py @@ -23,6 +23,8 @@ class Fibonacci(_MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Fibonacci.html +
'Fibonacci[$n$]'
computes the $n$th Fibonacci number. @@ -47,6 +49,9 @@ class Fibonacci(_MPMathFunction): class HarmonicNumber(_MPMathFunction): """ + :Harmonic Number:https://en.wikipedia.org/wiki/Harmonic_number \( + :WMA link:https://reference.wolfram.com/language/ref/HarmonicNumber.html) +
'HarmonicNumber[n]'
returns the $n$th harmonic number. @@ -73,6 +78,9 @@ class HarmonicNumber(_MPMathFunction): # Note: WL allows StirlingS1[{2, 4, 6}, 2], but we don't (yet). class StirlingS1(Builtin): """ + :Stirling numbers of first kind:https://en.wikipedia.org/wiki/Stirling_numbers_of_the_first_kind \ + (:WMA link:https://reference.wolfram.com/language/ref/StirlingS1.html) +
'StirlingS1[$n$, $m$]'
gives the Stirling number of the first kind $ _n^m$. @@ -101,6 +109,9 @@ def apply(self, n, m, evaluation): class StirlingS2(Builtin): """ + :Stirling numbers of first kind:https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind \ + (:WMA link:https://reference.wolfram.com/language/ref/StirlingS2.html) +
'StirlingS2[$n$, $m$]'
gives the Stirling number of the second kind _n^m. diff --git a/mathics/builtin/list/associations.py b/mathics/builtin/list/associations.py index 439014472..9a10127ff 100644 --- a/mathics/builtin/list/associations.py +++ b/mathics/builtin/list/associations.py @@ -27,6 +27,8 @@ class Association(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Association.html +
'Association[$key1$ -> $val1$, $key2$ -> $val2$, ...]'
'<|$key1$ -> $val1$, $key2$ -> $val2$, ...|>' @@ -163,6 +165,8 @@ def find_key(exprs, rules_dictionary: dict = {}): class AssociationQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/AssociationQ.html +
'AssociationQ[$expr$]'
return True if $expr$ is a valid Association object, and False otherwise. @@ -194,6 +198,8 @@ def validate(elements): class Keys(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Keys.html +
'Keys[<|$key1$ -> $val1$, $key2$ -> $val2$, ...|>]'
return a list of the keys $keyi$ in an association. @@ -288,6 +294,8 @@ def get_keys(expr): class Lookup(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Lookup.html +
Lookup[$assoc$, $key$]
looks up the value associated with $key$ in the association $assoc$, or Missing[$KeyAbsent$]. @@ -305,6 +313,8 @@ class Lookup(Builtin): class Missing(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Missing.html +
'Missing[]'
represents a data that is misssing. @@ -318,6 +328,8 @@ class Missing(Builtin): class Values(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Values.html +
'Values[<|$key1$ -> $val1$, $key2$ -> $val2$, ...|>]'
return a list of the values $vali$ in an association. diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index 9cb5c264e..dcfc576c5 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -33,6 +33,8 @@ class Array(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Array.html +
'Array[$f$, $n$]'
returns the $n$-element list '{$f$[1], ..., $f$[$n$]}'. @@ -120,6 +122,8 @@ def rec(rest_dims, current): class ConstantArray(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ConstantArray.html +
'ConstantArray[$expr$, $n$]'
returns a list of $n$ copies of $expr$. @@ -140,6 +144,8 @@ class ConstantArray(Builtin): class Normal(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Normal.html +
'Normal[expr_]'
Brings especial expressions to a normal expression from different especial forms. @@ -160,6 +166,8 @@ def apply_general(self, expr, evaluation): class Range(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Range.html +
'Range[$n$]'
returns a list of integers from 1 to $n$. @@ -212,6 +220,8 @@ def apply(self, imin, imax, di, evaluation): class Permutations(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Permutations.html +
'Permutations[$list$]'
gives all possible orderings of the items in $list$. @@ -288,6 +298,8 @@ def apply_n(self, li, n, evaluation): class Reap(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Reap.html +
'Reap[$expr$]'
gives the result of evaluating $expr$, together with all values sown during this evaluation. Values sown with different tags are given in different lists. @@ -371,6 +383,8 @@ def listener(e, tag): class Sow(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Sow.html +
'Sow[$e$]'
sends the value $e$ to the innermost 'Reap'. @@ -400,6 +414,8 @@ def apply(self, e, tags, evaluation): class Table(_IterationFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Table.html +
'Table[$expr$, $n$]'
generates a list of $n$ copies of $expr$. @@ -455,6 +471,8 @@ def get_result(self, elements) -> ListExpression: class Tuples(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Tuples.html +
'Tuples[$list$, $n$]'
returns a list of all $n$-tuples of elements in $list$. diff --git a/mathics/builtin/list/rearrange.py b/mathics/builtin/list/rearrange.py index a2331cd13..42a65bfdb 100644 --- a/mathics/builtin/list/rearrange.py +++ b/mathics/builtin/list/rearrange.py @@ -291,6 +291,8 @@ def from_python(self): class Catenate(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Catenate.html +
'Catenate[{$l1$, $l2$, ...}]'
concatenates the lists $l1$, $l2$, ... @@ -328,6 +330,8 @@ def parts(): class Complement(_SetOperation): """ + :WMA link:https://reference.wolfram.com/language/ref/Complement.html +
'Complement[$all$, $e1$, $e2$, ...]'
returns an expression containing the elements in the set $all$ that are not in any of $e1$, $e2$, etc. @@ -373,6 +377,8 @@ def _elementwise(self, a, b, sameQ: Callable[..., bool]): class DeleteDuplicates(_GatherOperation): """ + :WMA link:https://reference.wolfram.com/language/ref/DeleteDuplicates.html +
'DeleteDuplicates[$list$]'
deletes duplicates from $list$. @@ -402,6 +408,8 @@ class DeleteDuplicates(_GatherOperation): class Gather(_GatherOperation): """ + :WMA link:https://reference.wolfram.com/language/ref/Gather.html +
'Gather[$list$, $test$]'
gathers elements of $list$ into sub lists of items that are the same according to $test$. @@ -425,6 +433,8 @@ class Gather(_GatherOperation): class GatherBy(_GatherOperation): """ + :WMA link:https://reference.wolfram.com/language/ref/GatherBy.html +
'GatherBy[$list$, $f$]'
gathers elements of $list$ into sub lists of items whose image under $f$ identical. @@ -469,6 +479,8 @@ def apply(self, values, func, evaluation): class Join(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Join.html +
'Join[$l1$, $l2$]'
concatenates the lists $l1$ and $l2$. @@ -527,6 +539,8 @@ def apply(self, lists, evaluation): class Partition(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Partition.html +
'Partition[$list$, $n$]'
partitions $list$ into sublists of length $n$. @@ -589,6 +603,8 @@ def apply(self, li, n, d, evaluation): class Reverse(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Reverse.html +
'Reverse[$expr$]'
reverses the order of $expr$'s items (on the top level) @@ -692,6 +708,8 @@ def riffle_lists(items, seps): class Riffle(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Riffle.html +
'Riffle[$list$, $x$]'
inserts a copy of $x$ between each element of $list$. @@ -737,6 +755,8 @@ def apply(self, list, sep, evaluation): class RotateLeft(_Rotate): """ + :WMA link:https://reference.wolfram.com/language/ref/RotateLeft.html +
'RotateLeft[$expr$]'
rotates the items of $expr$' by one item to the left. @@ -764,6 +784,8 @@ class RotateLeft(_Rotate): class RotateRight(_Rotate): """ + :WMA link:https://reference.wolfram.com/language/ref/RotateRight.html +
'RotateRight[$expr$]'
rotates the items of $expr$' by one item to the right. @@ -791,6 +813,8 @@ class RotateRight(_Rotate): class Tally(_GatherOperation): """ + :WMA link:https://reference.wolfram.com/language/ref/Tally.html +
'Tally[$list$]'
counts and returns the number of occurences of objects and returns the result as a list of pairs {object, count}. @@ -813,6 +837,8 @@ class Tally(_GatherOperation): class Union(_SetOperation): """ + :WMA link:https://reference.wolfram.com/language/ref/Union.html +
'Union[$a$, $b$, ...]'
gives the union of the given set or sets. The resulting list will be sorted and each element will only occur once. @@ -850,6 +876,8 @@ def _elementwise(self, a, b, sameQ: Callable[..., bool]): class Intersection(_SetOperation): """ + :WMA link:https://reference.wolfram.com/language/ref/Intersection.html +
'Intersection[$a$, $b$, ...]'
gives the intersection of the sets. The resulting list will be sorted and each element will only occur once. diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index cf6aeeb42..f811fe1cf 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -106,6 +106,8 @@ class All(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/All.html +
'All'
is a possible option value for 'Span', 'Quiet', 'Part' and related functions. 'All' specifies all parts at a particular level. @@ -117,6 +119,8 @@ class All(Predefined): class ContainsOnly(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ContainsOnly.html +
'ContainsOnly[$list1$, $list2$]'
yields True if $list1$ contains only elements that appear in $list2$. @@ -212,6 +216,8 @@ def eval_msg(self, e1, e2, evaluation, options={}): class Delete(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Delete.html +
'Delete[$expr$, $i$]'
deletes the element at position $i$ in $expr$. The position is counted from the end if $i$ is negative. @@ -356,6 +362,8 @@ def eval(self, expr, positions, evaluation): class Failure(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Failure.html +
Failure[$tag$, $assoc$]
represents a failure of a type indicated by $tag$, with details given by the association $assoc$. @@ -374,6 +382,8 @@ class Failure(Builtin): class Key(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Key.html +
Key[$key$]
represents a key used to access a value in an association. @@ -390,6 +400,8 @@ class Key(Builtin): class Level(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Level.html +
'Level[$expr$, $levelspec$]'
gives a list of all subexpressions of $expr$ at the @@ -467,6 +479,8 @@ def callback(level): class LevelQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/LevelQ.html +
'LevelQ[$expr$]'
tests whether $expr$ is a valid level specification. @@ -494,6 +508,8 @@ def test(self, ls): class List(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/List.html +
'List[$e1$, $e2$, ..., $ei$]'
'{$e1$, $e2$, ..., $ei$}' @@ -531,6 +547,8 @@ def eval_makeboxes(self, items, f, evaluation): class ListQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/ListQ.html +
'ListQ[$expr$]'
tests whether $expr$ is a 'List'. @@ -552,6 +570,8 @@ def test(self, expr): class NotListQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/NotListQ.html +
'NotListQ[$expr$]'
returns true if $expr$ is not a list. @@ -597,6 +617,8 @@ def list_boxes(items, f, evaluation, open=None, close=None): class None_(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/None.html +
'None'
is a possible value for 'Span' and 'Quiet'. @@ -609,6 +631,8 @@ class None_(Predefined): class Split(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Split.html +
'Split[$list$]'
splits $list$ into collections of consecutive identical elements. @@ -679,6 +703,8 @@ def eval(self, mlist, test, evaluation): class SplitBy(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/SplitBy.html +
'SplitBy[$list$, $f$]'
splits $list$ into collections of consecutive elements @@ -747,6 +773,8 @@ def eval_multiple(self, mlist, funcs, evaluation): class LeafCount(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/LeafCount.html +
'LeafCount[$expr$]'
returns the total number of indivisible subexpressions in $expr$. @@ -1016,6 +1044,8 @@ def eval_multi(self, expr, first, sequ, evaluation): class Insert(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Insert.html +
'Insert[$list$, $elem$, $n$]'
inserts $elem$ at position $n$ in $list$. When $n$ is negative, the position is counted from the end. @@ -1052,6 +1082,8 @@ def get_tuples(items): class IntersectingQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/IntersectingQ.html +
'IntersectingQ[$a$, $b$]'
gives True if there are any common elements in $a and $b, or False if $a and $b are disjoint. @@ -1064,6 +1096,8 @@ class IntersectingQ(Builtin): class DisjointQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/DisjointQ.html +
'DisjointQ[$a$, $b$]'
gives True if $a and $b are disjoint, or False if $a and $b have any common elements. @@ -1218,6 +1252,8 @@ def _get_n(self, n, heap): class TakeLargestBy(_RankedTakeLargest): """ + :WMA link:https://reference.wolfram.com/language/ref/TakeLargestBy.html +
'TakeLargestBy[$list$, $f$, $n$]'
returns the a sorted list of the $n$ largest items in $list$ @@ -1242,6 +1278,8 @@ def eval(self, element, f, n, evaluation, options): class TakeSmallestBy(_RankedTakeSmallest): """ + :WMA link:https://reference.wolfram.com/language/ref/TakeSmallestBy.html +
'TakeSmallestBy[$list$, $f$, $n$]'
returns the a sorted list of the $n$ smallest items in $list$ @@ -1455,6 +1493,8 @@ def eval_margin(self, element, n, x, m, evaluation): class PadLeft(_Pad): """ + :WMA link:https://reference.wolfram.com/language/ref/PadLeft.html +
'PadLeft[$list$, $n$]'
pads $list$ to length $n$ by adding 0 on the left. @@ -1491,6 +1531,8 @@ class PadLeft(_Pad): class PadRight(_Pad): """ + :WMA link:https://reference.wolfram.com/language/ref/PadRight.html +
'PadRight[$list$, $n$]'
pads $list$ to length $n$ by adding 0 on the right. @@ -1769,6 +1811,8 @@ def convert_vectors(p): class FindClusters(_Cluster): """ + :WMA link:https://reference.wolfram.com/language/ref/FindClusters.html +
'FindClusters[$list$]'
returns a list of clusters formed from the elements of $list$. The number of cluster is determined @@ -1848,6 +1892,8 @@ def eval_manual_k(self, p, k: Integer, evaluation, options): class ClusteringComponents(_Cluster): """ + :WMA link:https://reference.wolfram.com/language/ref/ClusteringComponents.html +
'ClusteringComponents[$list$]'
forms clusters from $list$ and returns a list of cluster indices, in which each @@ -1895,6 +1941,8 @@ def eval_manual_k(self, p, k: Integer, evaluation, options): class Nearest(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Nearest.html +
'Nearest[$list$, $x$]'
returns the one item in $list$ that is nearest to $x$. @@ -2035,6 +2083,8 @@ def pick(): class SubsetQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/SubsetQ.html +
'SubsetQ[$list1$, $list2$]'
returns True if $list2$ is a subset of $list1$, and False otherwise. diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 35ee8f606..4299c9024 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -61,6 +61,8 @@ class _TraceBase(Builtin): class ClearTrace(Builtin): """ + :trace native symbol: +
'ClearTrace[]'
Clear the statistics collected for Built-in Functions @@ -93,6 +95,8 @@ def apply(self, evaluation): class PrintTrace(_TraceBase): """ + :trace native symbol: +
'PrintTrace[]'
Print statistics collected for Built-in Functions @@ -136,6 +140,8 @@ def apply(self, evaluation, options={}): class TraceBuiltins(_TraceBase): """ + :trace native symbol: +
'TraceBuiltins[$expr$]'
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. @@ -248,6 +254,8 @@ def apply(self, expr, evaluation, options={}): # the class name, but it is already taken by the builtin `TraceBuiltins` class TraceBuiltinsVariable(Builtin): """ + :trace native symbol: +
'$TraceBuiltins'
A Boolean Built-in variable when True collects function evaluation statistics. @@ -310,6 +318,8 @@ def apply_set(self, value, evaluation): class TraceEvaluation(Builtin): """ + :trace native symbol: +
'TraceEvaluation[$expr$]'
Evaluate $expr$ and print each step of the evaluation. @@ -346,6 +356,8 @@ def apply(self, expr, evaluation, options): class TraceEvaluationVariable(Builtin): """ + :trace native symbol: +
'$TraceEvaluation'
A Boolean variable which when set True traces Expression evaluation calls and returns. From 2badaab4a3b0825585cc53c6f69df8846b689501 Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 21 Dec 2022 06:50:50 -0300 Subject: [PATCH 058/121] complete docstr url for fileformats, numeric, atomic, arithfns and assigment --- mathics/builtin/arithfns/basic.py | 16 +++++++ mathics/builtin/arithfns/sums.py | 4 ++ mathics/builtin/arithmetic.py | 44 +++++++++++++++++++ .../builtin/assignments/assign_binaryop.py | 16 +++++++ mathics/builtin/assignments/assignment.py | 12 +++++ mathics/builtin/assignments/types.py | 8 ++++ mathics/builtin/assignments/upvalues.py | 4 ++ mathics/builtin/atomic/atomic.py | 4 ++ mathics/builtin/atomic/numbers.py | 26 +++++++++++ mathics/builtin/atomic/strings.py | 1 + mathics/builtin/fileformats/htmlformat.py | 21 +++++++++ mathics/builtin/fileformats/xmlformat.py | 15 +++++++ mathics/builtin/numeric.py | 8 ++++ 13 files changed, 179 insertions(+) diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index f46860e06..57af6ab5b 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -81,6 +81,8 @@ class CubeRoot(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/CubeRoot.html +
'CubeRoot[$n$]'
finds the real-valued cube root of the given $n$. @@ -143,6 +145,8 @@ def eval(self, n, evaluation): class Divide(BinaryOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/Divide.html +
'Divide[$a$, $b$]'
'$a$ / $b$' @@ -207,6 +211,8 @@ class Divide(BinaryOperator): class Minus(PrefixOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/Minus.html +
'Minus[$expr$]'
is the negation of $expr$. @@ -250,6 +256,8 @@ def eval_int(self, x: Integer, evaluation): class Plus(BinaryOperator, SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Plus.html +
'Plus[$a$, $b$, ...]'
$a$ + $b$ + ... @@ -468,6 +476,8 @@ def append_last(): class Power(BinaryOperator, _MPMathFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Power.html +
'Power[$a$, $b$]'
'$a$ ^ $b$' @@ -632,6 +642,8 @@ def eval_check(self, x, y, evaluation): class Sqrt(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Sqrt.html +
'Sqrt[$expr$]'
returns the square root of $expr$. @@ -673,6 +685,8 @@ class Sqrt(SympyFunction): class Subtract(BinaryOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/Subtract.html +
'Subtract[$a$, $b$]'
$a$ - $b$ @@ -704,6 +718,8 @@ class Subtract(BinaryOperator): class Times(BinaryOperator, SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Times.html +
'Times[$a$, $b$, ...]'
'$a$ * $b$ * ...' diff --git a/mathics/builtin/arithfns/sums.py b/mathics/builtin/arithfns/sums.py index 84a9b5feb..a3a371bd1 100644 --- a/mathics/builtin/arithfns/sums.py +++ b/mathics/builtin/arithfns/sums.py @@ -11,6 +11,8 @@ class Accumulate(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Accumulate.html +
'Accumulate[$list$]'
accumulates the values of $list$, returning a new list. @@ -26,6 +28,8 @@ class Accumulate(Builtin): class Total(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Total.html +
'Total[$list$]'
adds all values in $list$. diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 59fee74d8..894298586 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -230,6 +230,7 @@ def create_infix(items, operator, prec, grouping): class Abs(_MPMathFunction): """ + :Absolute value: https://en.wikipedia.org/wiki/Absolute_value (:SymPy: https://docs.sympy.org/latest/modules/functions/elementary.html#sympy.functions.elementary.complexes.Abs, :WMA: https://reference.wolfram.com/language/ref/Abs)
@@ -265,6 +266,8 @@ class Abs(_MPMathFunction): class Arg(_MPMathFunction): """ + :Arg:https://en.wikipedia.org/wiki/Argument_(complex_analysis) + (:WMA link:https://reference.wolfram.com/language/ref/Arg.html)
'Arg'[$z$, $method_option$]
returns the argument of a complex value $z$. @@ -333,6 +336,8 @@ def apply(self, z, evaluation, options={}): class Assuming(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Assuming.html +
'Assuming[$cond$, $expr$]'
Evaluates $expr$ assuming the conditions $cond$. @@ -369,6 +374,7 @@ def apply_assuming(self, assumptions, expr, evaluation): class Assumptions(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$Assumptions.html
'$Assumptions'
is the default setting for the Assumptions option used in such functions as Simplify, Refine, and Integrate. @@ -390,6 +396,8 @@ class Assumptions(Predefined): class Boole(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Boole.html +
'Boole[expr]'
returns 1 if expr is True and 0 if expr is False. @@ -417,6 +425,8 @@ def apply(self, expr, evaluation): class Complex_(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Complex.html +
'Complex'
is the head of complex numbers. @@ -487,6 +497,8 @@ def apply(self, r, i, evaluation): class ConditionalExpression(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/ConditionalExpression.html +
'ConditionalExpression[$expr$, $cond$]'
returns $expr$ if $cond$ evaluates to $True$, $Undefined$ if $cond$ evaluates to $False$. @@ -569,6 +581,9 @@ def to_sympy(self, expr, **kwargs): class Conjugate(_MPMathFunction): """ + :Complex Conjugate:https://en.wikipedia.org/wiki/Complex_conjugate \ + (:WMA link:https://reference.wolfram.com/language/ref/Assuming.html) +
'Conjugate[$z$]'
returns the complex conjugate of the complex number $z$. @@ -603,6 +618,8 @@ class Conjugate(_MPMathFunction): class DirectedInfinity(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/DirectedInfinity.html +
'DirectedInfinity[$z$]'
represents an infinite multiple of the complex number $z$. @@ -694,6 +711,9 @@ def to_sympy(self, expr, **kwargs): class I(Predefined): """ + :Imaginary unit:https://en.wikipedia.org/wiki/Imaginary_unit \ + (:WMA link:https://reference.wolfram.com/language/ref/I.html) +
'I'
represents the imaginary number 'Sqrt[-1]'. @@ -714,6 +734,8 @@ def evaluate(self, evaluation): class Im(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Im.html +
'Im[$z$]'
returns the imaginary component of the complex number $z$. @@ -752,6 +774,8 @@ def apply(self, number, evaluation): class Integer_(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Integer.html +
'Integer'
is the head of integers. @@ -771,6 +795,8 @@ class Integer_(Builtin): class NumberQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/NumberQ.html +
'NumberQ[$expr$]'
returns 'True' if $expr$ is an explicit number, and 'False' otherwise. @@ -792,6 +818,8 @@ def test(self, expr): class Piecewise(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Piecewise.html +
'Piecewise[{{expr1, cond1}, ...}]'
represents a piecewise function. @@ -885,6 +913,8 @@ def from_sympy(self, sympy_name, args): class PossibleZeroQ(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/PossibleZeroQ.html +
'PossibleZeroQ[$expr$]'
returns 'True' if basic symbolic and numerical methods suggest that expr has value zero, and 'False' otherwise. @@ -952,6 +982,8 @@ def apply(self, expr, evaluation): class Product(_IterationFunction, SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Product.html +
'Product[$expr$, {$i$, $imin$, $imax$}]'
evaluates the discrete product of $expr$ with $i$ ranging from $imin$ to $imax$. @@ -1031,6 +1063,8 @@ def to_sympy(self, expr, **kwargs): class Rational_(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Rational_.html +
'Rational'
is the head of rational numbers. @@ -1062,6 +1096,8 @@ def apply(self, n: Integer, m: Integer, evaluation): class Re(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Re.html +
'Re[$z$]'
returns the real component of the complex number $z$. @@ -1101,6 +1137,8 @@ def apply(self, number, evaluation): class Real_(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Real.html +
'Real'
is the head of real (inexact) numbers. @@ -1175,6 +1213,8 @@ class Real_(Builtin): class RealNumberQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/RealNumberQ.html +
'RealNumberQ[$expr$]'
returns 'True' if $expr$ is an explicit number with no imaginary component. @@ -1200,6 +1240,8 @@ def test(self, expr): class Sign(SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Sign.html +
'Sign[$x$]'
return -1, 0, or 1 depending on whether $x$ is negative, zero, or positive. @@ -1258,6 +1300,8 @@ def apply_error(self, x, seqs, evaluation): class Sum(_IterationFunction, SympyFunction): """ + :WMA link:https://reference.wolfram.com/language/ref/Sum.html +
'Sum[$expr$, {$i$, $imin$, $imax$}]'
evaluates the discrete sum of $expr$ with $i$ ranging from $imin$ to $imax$. diff --git a/mathics/builtin/assignments/assign_binaryop.py b/mathics/builtin/assignments/assign_binaryop.py index e796277ac..a3efcaf85 100644 --- a/mathics/builtin/assignments/assign_binaryop.py +++ b/mathics/builtin/assignments/assign_binaryop.py @@ -24,6 +24,8 @@ class AddTo(BinaryOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/AddTo.html +
'AddTo[$x$, $dx$]'
'$x$ += $dx$' @@ -50,6 +52,8 @@ class AddTo(BinaryOperator): class Decrement(PostfixOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/Decrement.html +
'Decrement[$x$]' @@ -79,6 +83,8 @@ class Decrement(PostfixOperator): class DivideBy(BinaryOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/DivideBy.html +
'DivideBy[$x$, $dx$]'
'$x$ /= $dx$' @@ -105,6 +111,8 @@ class DivideBy(BinaryOperator): class Increment(PostfixOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/Increment.html +
'Increment[$x$]' @@ -142,6 +150,8 @@ class Increment(PostfixOperator): class PreIncrement(PrefixOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/PreIncrement.html +
'PreIncrement[$x$]'
'++$x$' @@ -169,6 +179,8 @@ class PreIncrement(PrefixOperator): class PreDecrement(PrefixOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/PreDecrement.html +
'PreDecrement[$x$]' @@ -196,6 +208,8 @@ class PreDecrement(PrefixOperator): class SubtractFrom(BinaryOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/SubtractFrom.html +
'SubtractFrom[$x$, $dx$]'
'$x$ -= $dx$' @@ -222,6 +236,8 @@ class SubtractFrom(BinaryOperator): class TimesBy(BinaryOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/TimesBy.html +
'TimesBy[$x$, $dx$]'
'$x$ *= $dx$' diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py index 4ce49ebed..6081325e5 100644 --- a/mathics/builtin/assignments/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -26,6 +26,7 @@ class _SetOperator: """ + This is the base class for assignment Builtin operators. Special cases are determined by the head of the expression. Then @@ -59,6 +60,8 @@ def assign(self, lhs, rhs, evaluation, tags=None, upset=False): class Set(BinaryOperator, _SetOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/Set.html +
'Set[$expr$, $value$]' @@ -145,6 +148,8 @@ def apply(self, lhs, rhs, evaluation): class SetDelayed(Set): """ + :WMA link:https://reference.wolfram.com/language/ref/SetDelayed.html +
'SetDelayed[$expr$, $value$]' @@ -223,6 +228,8 @@ def apply(self, lhs, rhs, evaluation): class TagSet(Builtin, _SetOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/TagSet.html +
'TagSet[$f$, $expr$, $value$]' @@ -270,6 +277,8 @@ def apply(self, f, lhs, rhs, evaluation): class TagSetDelayed(TagSet): """ + :WMA link:https://reference.wolfram.com/language/ref/TagSetDelayed.html +
'TagSetDelayed[$f$, $expr$, $value$]' @@ -298,6 +307,9 @@ def apply(self, f, lhs, rhs, 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): """ + + :WMA link:https://reference.wolfram.com/language/ref/LoadModule.html +
'LoadModule[$module$]'
'Load Mathics definitions from the python module $module$ diff --git a/mathics/builtin/assignments/types.py b/mathics/builtin/assignments/types.py index 3d340f989..7518610b8 100644 --- a/mathics/builtin/assignments/types.py +++ b/mathics/builtin/assignments/types.py @@ -14,6 +14,8 @@ class DefaultValues(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/DefaultValues.html +
'DefaultValues[$symbol$]'
gives the list of default values associated with $symbol$. @@ -50,6 +52,8 @@ def apply(self, symbol, evaluation): class Messages(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Messages.html +
'Messages[$symbol$]'
gives the list of messages associated with $symbol$. @@ -77,6 +81,8 @@ def apply(self, symbol, evaluation): class NValues(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/NValues.html +
'NValues[$symbol$]'
gives the list of numerical values associated with $symbol$. @@ -120,6 +126,8 @@ def apply(self, symbol, evaluation): class SubValues(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/SubValues.html +
'SubValues[$symbol$]'
gives the list of subvalues associated with $symbol$. diff --git a/mathics/builtin/assignments/upvalues.py b/mathics/builtin/assignments/upvalues.py index 7cecd29cc..195ab44d7 100644 --- a/mathics/builtin/assignments/upvalues.py +++ b/mathics/builtin/assignments/upvalues.py @@ -15,6 +15,8 @@ class UpSet(BinaryOperator, _SetOperator): """ + :WMA link:https://reference.wolfram.com/language/ref/UpSet.html +
$f$[$x$] ^= $expression$
evaluates $expression$ and assigns it to the value of $f$[$x$], associating the value with $x$. @@ -64,6 +66,8 @@ def apply(self, lhs, rhs, evaluation): class UpSetDelayed(UpSet): """ + :WMA link:https://reference.wolfram.com/language/ref/UpSetDelayed.html +
'UpSetDelayed[$expression$, $value$]' diff --git a/mathics/builtin/atomic/atomic.py b/mathics/builtin/atomic/atomic.py index b03809e98..e8c1cd8cf 100644 --- a/mathics/builtin/atomic/atomic.py +++ b/mathics/builtin/atomic/atomic.py @@ -13,6 +13,8 @@ class AtomQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/AtomQ.html +
'AtomQ[$expr$]'
returns 'True' if $expr$ is an expression which cannot be divided into subexpressions, or 'False' otherwise. @@ -63,6 +65,8 @@ def test(self, expr): class Head(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Head.html +
'Head[$expr$]'
returns the head of the expression or atom $expr$. diff --git a/mathics/builtin/atomic/numbers.py b/mathics/builtin/atomic/numbers.py index aeb303256..94690804b 100644 --- a/mathics/builtin/atomic/numbers.py +++ b/mathics/builtin/atomic/numbers.py @@ -228,6 +228,8 @@ def apply(self, z, evaluation): class ExactNumberQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/ExactNumberQ.html +
'ExactNumberQ[$expr$]'
returns 'True' if $expr$ is an exact number, and 'False' otherwise. @@ -255,6 +257,8 @@ def test(self, expr): class IntegerExponent(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/IntegerExponent.html +
'IntegerExponent[$n$, $b$]'
gives the highest exponent of $b$ that divides $n$. @@ -325,6 +329,8 @@ def apply_two_arg_integers(self, n: Integer, b: Integer, evaluation): class IntegerLength(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/IntegerLength.html +
'IntegerLength[$x$]'
gives the number of digits in the base-10 representation of $x$. @@ -408,6 +414,8 @@ def apply(self, n, b, evaluation): class InexactNumberQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/InexactNumberQ.html +
'InexactNumberQ[$expr$]'
returns 'True' if $expr$ is not an exact number, and 'False' otherwise. @@ -433,6 +441,8 @@ def test(self, expr): class IntegerQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/IntegerQ.html +
'IntegerQ[$expr$]'
returns 'True' if $expr$ is an integer, and 'False' otherwise. @@ -452,6 +462,8 @@ def test(self, expr): class MachineNumberQ(Test): """ + :WMA link:https://reference.wolfram.com/language/ref/MachineNumberQ.html +
'MachineNumberQ[$expr$]'
returns 'True' if $expr$ is a machine-precision real or complex number. @@ -478,6 +490,8 @@ def test(self, expr): class RealDigits(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/RealDigits.html +
'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. @@ -737,6 +751,8 @@ def apply_with_base_length_and_precision(self, n, b, length, p, evaluation): class MaxPrecision(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$MaxPrecision.html +
'$MaxPrecision'
represents the maximum number of digits of precision permitted in abitrary-precision numbers. @@ -791,6 +807,8 @@ class MaxPrecision(Predefined): class MachineEpsilon_(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$MachineEpsilon.html +
'$MachineEpsilon'
is the distance between '1.0' and the next @@ -816,6 +834,8 @@ def evaluate(self, evaluation): class MachinePrecision_(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/$MachinePrecision.html +
'$MachinePrecision'
is the number of decimal digits of precision for machine-precision numbers. @@ -838,6 +858,7 @@ class MachinePrecision_(Predefined): class MachinePrecision(Predefined): """ + :WMA link:https://reference.wolfram.com/language/ref/MachinePrecision.html
'MachinePrecision'
represents the precision of machine precision numbers. @@ -865,6 +886,8 @@ class MachinePrecision(Predefined): class MinPrecision(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/MinPrecision.html +
'$MinPrecision'
represents the minimum number of digits of precision permitted in abitrary-precision numbers. @@ -918,6 +941,8 @@ class MinPrecision(Builtin): class NumericQ(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/NumericQ.html +
'NumericQ[$expr$]'
tests whether $expr$ represents a numeric quantity. @@ -966,6 +991,7 @@ def apply(self, expr, evaluation): class Precision(Builtin): """ + :Precision: https://en.wikipedia.org/wiki/Accuracy_and_precision (WMA :Precision: https://reference.wolfram.com/language/ref/Precision.html)
diff --git a/mathics/builtin/atomic/strings.py b/mathics/builtin/atomic/strings.py index 8e6a6f182..340808fbb 100644 --- a/mathics/builtin/atomic/strings.py +++ b/mathics/builtin/atomic/strings.py @@ -486,6 +486,7 @@ class CharacterEncodings(Predefined): :WMA link: https://reference.wolfram.com/language/ref/$CharacterEncodings.html +
'$CharacterEncodings'
stores the list of available character encodings. diff --git a/mathics/builtin/fileformats/htmlformat.py b/mathics/builtin/fileformats/htmlformat.py index 2f00e277a..bac26a32f 100644 --- a/mathics/builtin/fileformats/htmlformat.py +++ b/mathics/builtin/fileformats/htmlformat.py @@ -157,6 +157,8 @@ def apply(self, text, evaluation): class HTMLGet(_Get): """ + :WMA link:https://reference.wolfram.com/language/ref/HTMLGet.html +
HTMLGet['str']
Parses 'str' as HTML code. @@ -171,6 +173,8 @@ def _parse(self, text): class HTMLGetString(_Get): """ + :WMA link:https://reference.wolfram.com/language/ref/HTMLGetString.html +
'HTML`Parser`HTMLGetString["string"]'
parses HTML code contained in "string". @@ -269,6 +273,9 @@ def traverse(parent): class DataImport(_DataImport): """ + + :WMA link:https://reference.wolfram.com/language/ref/DataImport.html +
'HTML`DataImport["filename"]'
imports data from a HTML file. @@ -287,6 +294,8 @@ class DataImport(_DataImport): class FullDataImport(_DataImport): """ + :internal native: +
'HTML`FullDataImport["filename"]'
imports data from a HTML file. @@ -308,6 +317,8 @@ def _import(self, tree): class HyperlinksImport(_LinksImport): """ + :WMA link:https://reference.wolfram.com/language/ref/HyperlinksImport.html +
'HTML`HyperlinksImport["filename"]'
imports hyperlinks from a HTML file. @@ -329,6 +340,8 @@ def _links(self, tree): class ImageLinksImport(_LinksImport): """ + :WMA link:https://reference.wolfram.com/language/ref/ImageLinksImport.html +
'HTML`ImageLinksImport["filename"]'
imports links to the images included in a HTML file. @@ -349,6 +362,8 @@ def _links(self, tree): class PlaintextImport(_TagImport): """ + :WMA link:https://reference.wolfram.com/language/ref/PlaintextImport.html +
'HTML`PlaintextImport["filename"]'
imports plane text from a HTML file. @@ -372,6 +387,8 @@ def lines(): class SourceImport(_HTMLBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/SourceImport.html +
'HTML`SourceImport["filename"]'
imports source code from a HTML file. @@ -396,6 +413,8 @@ def source(filename): class TitleImport(_TagImport): """ + :WMA link:https://reference.wolfram.com/language/ref/TitleImport.html +
'HTML`TitleImport["filename"]'
imports the title string from a HTML file. @@ -415,6 +434,8 @@ def _import(self, tree): class XMLObjectImport(_HTMLBuiltin): """ + :WMA link:https://reference.wolfram.com/language/ref/XMLObjectImport.html +
'HTML`XMLObjectImport["filename"]'
imports XML objects from a HTML file. diff --git a/mathics/builtin/fileformats/xmlformat.py b/mathics/builtin/fileformats/xmlformat.py index 5fe526630..f5c14dd2c 100644 --- a/mathics/builtin/fileformats/xmlformat.py +++ b/mathics/builtin/fileformats/xmlformat.py @@ -227,6 +227,9 @@ def parse_xml(parse, text, evaluation): class XMLObject(Builtin): """ + + :WMA link:https://reference.wolfram.com/language/ref/XMLObject.html +
'XMLObject["type"]'
represents the head of an XML object in symbolic XML. @@ -238,6 +241,8 @@ class XMLObject(Builtin): class XMLElement(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/XMLElement.html +
'XMLElement[$tag$, {$attr_1$, $val_1$, ...}, {$data$, ...}]'
represents an element in symbolic XML. @@ -265,6 +270,8 @@ def apply(self, text, evaluation): class XMLGet(_Get): """ + :WMA link:https://reference.wolfram.com/language/ref/XMLGet.html +
'XMLGet[...]'
Internal. Document me. @@ -279,6 +286,8 @@ def _parse(self, text): class XMLGetString(_Get): """ + :WMA link:https://reference.wolfram.com/language/ref/XMLGetString.html +
'XML`Parser`XMLGetString["string"]'
parses "string" as XML code, and returns an XMLObject. @@ -302,6 +311,8 @@ def _parse(self, text): class PlaintextImport(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/PlaintextImport.html +
'XML`PlaintextImport["string"]'
parses "string" as XML code, and returns it as plain text. @@ -331,6 +342,8 @@ def lines(): class TagsImport(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/TagsImport.html +
'XML`TagsImport["string"]'
parses "string" as XML code, and returns a list with the tags found. @@ -364,6 +377,8 @@ def apply(self, text, evaluation): class XMLObjectImport(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/XMLObjectImport.html +
'XML`XMLObjectImport["string"]'
parses "string" as XML code, and returns a list of XMLObjects found. diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 1c5fb4d38..0a4cb31a0 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -44,6 +44,8 @@ def chop(expr, delta=10.0 ** (-10.0)): class Chop(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Chop.html +
'Chop[$expr$]'
replaces floating point numbers close to 0 by 0. @@ -84,6 +86,8 @@ def apply(self, expr, delta, evaluation): class N(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/N.html +
'N[$expr$, $prec$]'
evaluates $expr$ numerically with a precision of $prec$ digits. @@ -245,6 +249,8 @@ def apply_N(self, expr, evaluation): class Rationalize(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Rationalize.html +
'Rationalize[$x$]'
converts a real number $x$ to a nearby rational number with small denominator. @@ -407,6 +413,8 @@ class RealValuedNumericQ(Builtin): class Round(Builtin): """ + :WMA link:https://reference.wolfram.com/language/ref/Round.html +
'Round[$expr$]'
rounds $expr$ to the nearest integer. From 7c0c0db8391de1aec9606d636b8c8e1aea8ec4c5 Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 21 Dec 2022 08:09:53 -0300 Subject: [PATCH 059/121] comment out missing urls --- mathics/builtin/files_io/importexport.py | 4 ++-- mathics/builtin/trace.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index aad4fdda3..bc73ab2eb 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -990,7 +990,7 @@ def evaluate(self, evaluation): class ConverterDumpsExtensionMappings(Predefined): """ - :internal native symbol: + ## :internal native symbol:
'$extensionMappings' @@ -1009,7 +1009,7 @@ def evaluate(self, evaluation): class ConverterDumpsFormatMappings(Predefined): """ - :internal native symbol: + ## :internal native symbol:
'$formatMappings' diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 4299c9024..1ef6347ae 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -61,7 +61,7 @@ class _TraceBase(Builtin): class ClearTrace(Builtin): """ - :trace native symbol: + ## :trace native symbol:
'ClearTrace[]' @@ -95,7 +95,7 @@ def apply(self, evaluation): class PrintTrace(_TraceBase): """ - :trace native symbol: + ## :trace native symbol:
'PrintTrace[]' @@ -140,7 +140,7 @@ def apply(self, evaluation, options={}): class TraceBuiltins(_TraceBase): """ - :trace native symbol: + ## :trace native symbol:
'TraceBuiltins[$expr$]' @@ -254,7 +254,7 @@ def apply(self, expr, evaluation, options={}): # the class name, but it is already taken by the builtin `TraceBuiltins` class TraceBuiltinsVariable(Builtin): """ - :trace native symbol: + ## :trace native symbol:
'$TraceBuiltins' @@ -318,7 +318,7 @@ def apply_set(self, value, evaluation): class TraceEvaluation(Builtin): """ - :trace native symbol: + ## :trace native symbol:
'TraceEvaluation[$expr$]' @@ -356,7 +356,7 @@ def apply(self, expr, evaluation, options): class TraceEvaluationVariable(Builtin): """ - :trace native symbol: + ## :trace native symbol:
'$TraceEvaluation' From a9731893d15a0829cae00e159143598c8b70a7eb Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 21 Dec 2022 08:11:22 -0300 Subject: [PATCH 060/121] comment out missing urls --- mathics/builtin/fileformats/htmlformat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/builtin/fileformats/htmlformat.py b/mathics/builtin/fileformats/htmlformat.py index bac26a32f..810f4f8c5 100644 --- a/mathics/builtin/fileformats/htmlformat.py +++ b/mathics/builtin/fileformats/htmlformat.py @@ -294,7 +294,7 @@ class DataImport(_DataImport): class FullDataImport(_DataImport): """ - :internal native: + ## :internal native:
'HTML`FullDataImport["filename"]' From 5b66923e6c7301176f9a789d63f38ae649cd4926 Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 21 Dec 2022 19:33:24 -0300 Subject: [PATCH 061/121] changes from Thiago's comments --- mathics/builtin/arithmetic.py | 6 +++--- mathics/builtin/assignments/assignment.py | 3 +-- mathics/builtin/atomic/strings.py | 7 +++++-- mathics/builtin/fileformats/htmlformat.py | 18 +++++++++--------- mathics/builtin/fileformats/xmlformat.py | 8 ++++---- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 894298586..82bb512c2 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -266,7 +266,7 @@ class Abs(_MPMathFunction): class Arg(_MPMathFunction): """ - :Arg:https://en.wikipedia.org/wiki/Argument_(complex_analysis) + :Arg:https://en.wikipedia.org/wiki/Argument_(complex_analysis) \ (:WMA link:https://reference.wolfram.com/language/ref/Arg.html)
'Arg'[$z$, $method_option$] @@ -582,7 +582,7 @@ def to_sympy(self, expr, **kwargs): class Conjugate(_MPMathFunction): """ :Complex Conjugate:https://en.wikipedia.org/wiki/Complex_conjugate \ - (:WMA link:https://reference.wolfram.com/language/ref/Assuming.html) + (:WMA link:https://reference.wolfram.com/language/ref/Conjugate.html)
'Conjugate[$z$]' @@ -1063,7 +1063,7 @@ def to_sympy(self, expr, **kwargs): class Rational_(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/Rational_.html + :WMA link:https://reference.wolfram.com/language/ref/Rational.html
'Rational' diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py index 6081325e5..7d36acadd 100644 --- a/mathics/builtin/assignments/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -307,8 +307,7 @@ def apply(self, f, lhs, rhs, 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): """ - - :WMA link:https://reference.wolfram.com/language/ref/LoadModule.html + ## :mathics native for pymathics:
'LoadModule[$module$]' diff --git a/mathics/builtin/atomic/strings.py b/mathics/builtin/atomic/strings.py index 340808fbb..eae8397f3 100644 --- a/mathics/builtin/atomic/strings.py +++ b/mathics/builtin/atomic/strings.py @@ -458,9 +458,10 @@ class CharacterEncoding(Predefined): """ :WMA link: - https://reference.wolfram.com/language/ref/CharacterEncoding.html + https://reference.wolfram.com/language/ref/$CharacterEncoding.html +
-
'CharacterEncoding' +
'$CharacterEncoding'
specifies the default raw character encoding to use for input and output when no encoding is explicitly specified. Initially this is set to '$SystemCharacterEncoding'.
@@ -509,6 +510,7 @@ class HexadecimalCharacter(Builtin): :WMA link: https://reference.wolfram.com/language/ref/HexadecimalCharacter.html +
'HexadecimalCharacter'
represents the characters 0-9, a-f and A-F. @@ -528,6 +530,7 @@ class InterpretedBox(PrefixOperator): :WMA link: https://reference.wolfram.com/language/ref/InterpretedBox.html +
'InterpretedBox[$box$]'
is the ad hoc fullform for \! $box$. just for internal use... diff --git a/mathics/builtin/fileformats/htmlformat.py b/mathics/builtin/fileformats/htmlformat.py index 810f4f8c5..a8c33e978 100644 --- a/mathics/builtin/fileformats/htmlformat.py +++ b/mathics/builtin/fileformats/htmlformat.py @@ -157,7 +157,7 @@ def apply(self, text, evaluation): class HTMLGet(_Get): """ - :WMA link:https://reference.wolfram.com/language/ref/HTMLGet.html + ## :native internal:
HTMLGet['str'] @@ -173,7 +173,7 @@ def _parse(self, text): class HTMLGetString(_Get): """ - :WMA link:https://reference.wolfram.com/language/ref/HTMLGetString.html + ## :native internal:
'HTML`Parser`HTMLGetString["string"]' @@ -274,7 +274,7 @@ def traverse(parent): class DataImport(_DataImport): """ - :WMA link:https://reference.wolfram.com/language/ref/DataImport.html + ## :native internal:
'HTML`DataImport["filename"]' @@ -317,7 +317,7 @@ def _import(self, tree): class HyperlinksImport(_LinksImport): """ - :WMA link:https://reference.wolfram.com/language/ref/HyperlinksImport.html + ## :native internal:
'HTML`HyperlinksImport["filename"]' @@ -340,7 +340,7 @@ def _links(self, tree): class ImageLinksImport(_LinksImport): """ - :WMA link:https://reference.wolfram.com/language/ref/ImageLinksImport.html + ## :native internal:
'HTML`ImageLinksImport["filename"]' @@ -362,7 +362,7 @@ def _links(self, tree): class PlaintextImport(_TagImport): """ - :WMA link:https://reference.wolfram.com/language/ref/PlaintextImport.html + ## :native internal:
'HTML`PlaintextImport["filename"]' @@ -387,7 +387,7 @@ def lines(): class SourceImport(_HTMLBuiltin): """ - :WMA link:https://reference.wolfram.com/language/ref/SourceImport.html + ## :native internal:
'HTML`SourceImport["filename"]' @@ -413,7 +413,7 @@ def source(filename): class TitleImport(_TagImport): """ - :WMA link:https://reference.wolfram.com/language/ref/TitleImport.html + ## :native internal:
'HTML`TitleImport["filename"]' @@ -434,7 +434,7 @@ def _import(self, tree): class XMLObjectImport(_HTMLBuiltin): """ - :WMA link:https://reference.wolfram.com/language/ref/XMLObjectImport.html + ## :native internal:
'HTML`XMLObjectImport["filename"]' diff --git a/mathics/builtin/fileformats/xmlformat.py b/mathics/builtin/fileformats/xmlformat.py index f5c14dd2c..9bf2131f0 100644 --- a/mathics/builtin/fileformats/xmlformat.py +++ b/mathics/builtin/fileformats/xmlformat.py @@ -270,7 +270,7 @@ def apply(self, text, evaluation): class XMLGet(_Get): """ - :WMA link:https://reference.wolfram.com/language/ref/XMLGet.html + ## :native internal:
'XMLGet[...]' @@ -286,7 +286,7 @@ def _parse(self, text): class XMLGetString(_Get): """ - :WMA link:https://reference.wolfram.com/language/ref/XMLGetString.html + ## :native internal:
'XML`Parser`XMLGetString["string"]' @@ -342,7 +342,7 @@ def lines(): class TagsImport(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/TagsImport.html + ## :native internal:
'XML`TagsImport["string"]' @@ -377,7 +377,7 @@ def apply(self, text, evaluation): class XMLObjectImport(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/XMLObjectImport.html + ## :native internal:
'XML`XMLObjectImport["string"]' From b3895daf60732815b15665cebcad747fa4a567f7 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 24 Dec 2022 05:39:09 -0500 Subject: [PATCH 062/121] BaseDirectory -> BaseDirectory_ ... and sort dictionary keys --- mathics/system_info.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/mathics/system_info.py b/mathics/system_info.py index 1a5d5d387..b2f145365 100644 --- a/mathics/system_info.py +++ b/mathics/system_info.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- import os -import sys import platform -import mathics.builtin.system as msystem +import sys + +import mathics.builtin.atomic.numbers as numeric import mathics.builtin.datentime as datentime import mathics.builtin.files_io.filesystem as filesystem -import mathics.builtin.atomic.numbers as numeric - +import mathics.builtin.system as msystem from mathics.core.evaluation import Evaluation @@ -21,19 +21,19 @@ def eval(name, needs_head=True): evaluation = Evaluation(defs, output=None) return { + "$BaseDirectory": eval(filesystem.BaseDirectory_), + "$HomeDirectory": eval(filesystem.HomeDirectory), + "$InstallationDirectory": eval(filesystem.InstallationDirectory), "$Machine": sys.platform, "$MachineName": platform.uname().node, "$ProcessID": os.getppid(), "$ProcessorType": platform.machine(), + "$RootDirectory": eval(filesystem.RootDirectory), "$SystemID": sys.platform, - "$UserName": eval(msystem.UserName), "$SystemMemory": eval(msystem.SystemMemory), - "MemoryAvailable[]": eval(msystem.MemoryAvailable, needs_head=False), "$SystemTimeZone": eval(datentime.SystemTimeZone), - "MachinePrecision": eval(numeric.MachinePrecision_), - "$BaseDirectory": eval(filesystem.BaseDirectory), - "$RootDirectory": eval(filesystem.RootDirectory), - "$HomeDirectory": eval(filesystem.HomeDirectory), - "$InstallationDirectory": eval(filesystem.InstallationDirectory), "$TemporaryDirectory": eval(filesystem.TemporaryDirectory), + "$UserName": eval(msystem.UserName), + "MachinePrecision": eval(numeric.MachinePrecision_), + "MemoryAvailable[]": eval(msystem.MemoryAvailable, needs_head=False), } From 57170ae935c3ccabc63095fcb4f77be1dd894f4e Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 24 Dec 2022 07:10:15 -0500 Subject: [PATCH 063/121] dd indent, isort, long lines, apply->eval ... and check links for the first 12 files. --- mathics/builtin/arithfns/basic.py | 43 ++--- mathics/builtin/arithfns/sums.py | 26 +-- .../builtin/assignments/assign_binaryop.py | 39 +++-- mathics/builtin/assignments/types.py | 36 ++-- mathics/builtin/assignments/upvalues.py | 48 ++++- mathics/builtin/atomic/atomic.py | 9 +- mathics/builtin/atomic/numbers.py | 104 ++++++----- mathics/builtin/atomic/strings.py | 11 +- mathics/builtin/atomic/symbols.py | 34 +--- mathics/builtin/fileformats/htmlformat.py | 46 +++-- mathics/builtin/fileformats/xmlformat.py | 42 +++-- mathics/builtin/files_io/files.py | 165 ++++++++++-------- 12 files changed, 346 insertions(+), 257 deletions(-) diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index 57af6ab5b..5900ac3df 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -81,7 +81,8 @@ class CubeRoot(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/CubeRoot.html + :WMA link: + https://reference.wolfram.com/language/ref/CubeRoot.html
'CubeRoot[$n$]' @@ -148,9 +149,9 @@ class Divide(BinaryOperator): :WMA link:https://reference.wolfram.com/language/ref/Divide.html
-
'Divide[$a$, $b$]' -
'$a$ / $b$' -
represents the division of $a$ by $b$. +
'Divide[$a$, $b$]' +
'$a$ / $b$' +
represents the division of $a$ by $b$.
>> 30 / 5 = 6 @@ -214,8 +215,8 @@ class Minus(PrefixOperator): :WMA link:https://reference.wolfram.com/language/ref/Minus.html
-
'Minus[$expr$]' -
is the negation of $expr$. +
'Minus[$expr$]' +
is the negation of $expr$.
>> -a //FullForm @@ -259,9 +260,9 @@ class Plus(BinaryOperator, SympyFunction): :WMA link:https://reference.wolfram.com/language/ref/Plus.html
-
'Plus[$a$, $b$, ...]' -
$a$ + $b$ + ... -
represents the sum of the terms $a$, $b$, ... +
'Plus[$a$, $b$, ...]' +
$a$ + $b$ + ... +
represents the sum of the terms $a$, $b$, ...
>> 1 + 2 @@ -479,9 +480,9 @@ class Power(BinaryOperator, _MPMathFunction): :WMA link:https://reference.wolfram.com/language/ref/Power.html
-
'Power[$a$, $b$]' -
'$a$ ^ $b$' -
represents $a$ raised to the power of $b$. +
'Power[$a$, $b$]' +
'$a$ ^ $b$' +
represents $a$ raised to the power of $b$.
>> 4 ^ (1/2) @@ -645,8 +646,8 @@ class Sqrt(SympyFunction): :WMA link:https://reference.wolfram.com/language/ref/Sqrt.html
-
'Sqrt[$expr$]' -
returns the square root of $expr$. +
'Sqrt[$expr$]' +
returns the square root of $expr$.
>> Sqrt[4] @@ -688,9 +689,9 @@ class Subtract(BinaryOperator): :WMA link:https://reference.wolfram.com/language/ref/Subtract.html
-
'Subtract[$a$, $b$]' -
$a$ - $b$ -
represents the subtraction of $b$ from $a$. +
'Subtract[$a$, $b$]' +
$a$ - $b$ +
represents the subtraction of $b$ from $a$.
>> 5 - 3 @@ -721,10 +722,10 @@ class Times(BinaryOperator, SympyFunction): :WMA link:https://reference.wolfram.com/language/ref/Times.html
-
'Times[$a$, $b$, ...]' -
'$a$ * $b$ * ...' -
'$a$ $b$ ...' -
represents the product of the terms $a$, $b$, ... +
'Times[$a$, $b$, ...]' +
'$a$ * $b$ * ...' +
'$a$ $b$ ...' +
represents the product of the terms $a$, $b$, ...
>> 10 * 2 = 20 diff --git a/mathics/builtin/arithfns/sums.py b/mathics/builtin/arithfns/sums.py index a3a371bd1..79b33ed4f 100644 --- a/mathics/builtin/arithfns/sums.py +++ b/mathics/builtin/arithfns/sums.py @@ -11,11 +11,12 @@ class Accumulate(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/Accumulate.html + :WMA link: + https://reference.wolfram.com/language/ref/Accumulate.html
-
'Accumulate[$list$]' -
accumulates the values of $list$, returning a new list. +
'Accumulate[$list$]' +
accumulates the values of $list$, returning a new list.
>> Accumulate[{1, 2, 3}] @@ -31,14 +32,17 @@ class Total(Builtin): :WMA link:https://reference.wolfram.com/language/ref/Total.html
-
'Total[$list$]' -
adds all values in $list$. -
'Total[$list$, $n$]' -
adds all values up to level $n$. -
'Total[$list$, {$n$}]' -
totals only the values at level {$n$}. -
'Total[$list$, {$n_1$, $n_2$}]' -
totals at levels {$n_1$, $n_2$}. +
'Total[$list$]' +
adds all values in $list$. + +
'Total[$list$, $n$]' +
adds all values up to level $n$. + +
'Total[$list$, {$n$}]' +
totals only the values at level {$n$}. + +
'Total[$list$, {$n_1$, $n_2$}]' +
totals at levels {$n_1$, $n_2$}.
>> Total[{1, 2, 3}] diff --git a/mathics/builtin/assignments/assign_binaryop.py b/mathics/builtin/assignments/assign_binaryop.py index a3efcaf85..32192bd4c 100644 --- a/mathics/builtin/assignments/assign_binaryop.py +++ b/mathics/builtin/assignments/assign_binaryop.py @@ -2,23 +2,21 @@ """ In-place binary assignment operator -There are a number operators and functions that combine assignment with some sort of 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 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. +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.builtin.base import ( - BinaryOperator, - PostfixOperator, - PrefixOperator, -) - +from mathics.builtin.base import BinaryOperator, PostfixOperator, PrefixOperator from mathics.core.attributes import A_HOLD_FIRST, A_PROTECTED, A_READ_PROTECTED @@ -150,12 +148,13 @@ class Increment(PostfixOperator): class PreIncrement(PrefixOperator): """ - :WMA link:https://reference.wolfram.com/language/ref/PreIncrement.html + :WMA link: + https://reference.wolfram.com/language/ref/PreIncrement.html
-
'PreIncrement[$x$]' -
'++$x$' -
increments $x$ by 1, returning the new value of $x$. +
'PreIncrement[$x$]' +
'++$x$' +
increments $x$ by 1, returning the new value of $x$.
'++$a$' is equivalent to '$a$ = $a$ + 1': @@ -179,7 +178,8 @@ class PreIncrement(PrefixOperator): class PreDecrement(PrefixOperator): """ - :WMA link:https://reference.wolfram.com/language/ref/PreDecrement.html + :WMA link: + https://reference.wolfram.com/language/ref/PreDecrement.html
'PreDecrement[$x$]' @@ -208,12 +208,13 @@ class PreDecrement(PrefixOperator): class SubtractFrom(BinaryOperator): """ - :WMA link:https://reference.wolfram.com/language/ref/SubtractFrom.html + :WMA link: + https://reference.wolfram.com/language/ref/SubtractFrom.html
-
'SubtractFrom[$x$, $dx$]' -
'$x$ -= $dx$' -
is equivalent to '$x$ = $x$ - $dx$'. +
'SubtractFrom[$x$, $dx$]' +
'$x$ -= $dx$' +
is equivalent to '$x$ = $x$ - $dx$'.
>> a = 10; diff --git a/mathics/builtin/assignments/types.py b/mathics/builtin/assignments/types.py index 7518610b8..17c946a69 100644 --- a/mathics/builtin/assignments/types.py +++ b/mathics/builtin/assignments/types.py @@ -1,26 +1,32 @@ # -*- 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. +# 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. + +# FIXME: put this inside a Pymathics module. +# We alos should have compatibility modes for for earlier Mathemathica versions. """ Types of Values """ from mathics.builtin.base import Builtin - from mathics.core.assignment import get_symbol_values from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED class DefaultValues(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/DefaultValues.html + :WMA link: + https://reference.wolfram.com/language/ref/DefaultValues.html
'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. + Note: this function is in Mathematica 5 but has been removed from \ + current Mathematica.
>> Default[f, 1] = 4 @@ -52,7 +58,8 @@ def apply(self, symbol, evaluation): class Messages(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/Messages.html + :WMA link: + https://reference.wolfram.com/language/ref/Messages.html
'Messages[$symbol$]' @@ -81,13 +88,14 @@ def apply(self, symbol, evaluation): class NValues(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/NValues.html + ## :WMA link:https://reference.wolfram.com/language/ref/NValues.html
'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. + Note: this function is in Mathematica 5 but has been removed from \ + current Mathematica.
>> NValues[a] @@ -100,14 +108,17 @@ class NValues(Builtin): >> 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' + 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: + Mathics will assign any list of rules to 'NValues'; however, \ + inappropriate rules will never be used: >> NValues[d] = {foo -> bar}; >> NValues[d] = {HoldPattern[foo] :> bar} @@ -126,7 +137,8 @@ def apply(self, symbol, evaluation): class SubValues(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/SubValues.html + :WMA link: + https://reference.wolfram.com/language/ref/SubValues.html
'SubValues[$symbol$]' diff --git a/mathics/builtin/assignments/upvalues.py b/mathics/builtin/assignments/upvalues.py index 195ab44d7..10ad84860 100644 --- a/mathics/builtin/assignments/upvalues.py +++ b/mathics/builtin/assignments/upvalues.py @@ -1,8 +1,17 @@ # -*- coding: utf-8 -*- +""" +UpValue-related assignments +An UpValue is a definition associated with a symbols that does not appear directly its head. + +See +:Associating Definitions with Different Symbols: +https://reference.wolfram.com/language/tutorial/TransformationRulesAndDefinitions.html#6972. +""" from mathics.builtin.assignments.assignment import _SetOperator -from mathics.builtin.base import BinaryOperator +from mathics.builtin.base import Builtin, BinaryOperator +from mathics.core.assignment import get_symbol_values from mathics.core.attributes import ( A_HOLD_ALL, A_HOLD_FIRST, @@ -19,7 +28,8 @@ class UpSet(BinaryOperator, _SetOperator):
$f$[$x$] ^= $expression$ -
evaluates $expression$ and assigns it to the value of $f$[$x$], associating the value with $x$. +
evaluates $expression$ and assigns it to the value of $f$[$x$], \ + associating the value with $x$.
'UpSet' creates an upvalue: @@ -93,10 +103,42 @@ class UpSetDelayed(UpSet): operator = "^:=" summary_text = "set a delayed value and associate the assignment with symbols that occur at level one" - def apply(self, lhs, rhs, evaluation): + def eval(self, lhs, rhs, evaluation): "lhs_ ^:= rhs_" if self.assign(lhs, rhs, evaluation, upset=True): return SymbolNull else: return SymbolFailed + + +# In Mathematica 5, this appears under "Types of Values". +class UpValues(Builtin): + """ + :WMA: https://reference.wolfram.com/language/ref/UpValues.html +
+
'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 = A_HOLD_ALL | A_PROTECTED + summary_text = "give a list of transformation rules corresponding to upvalues defined for a symbol" + + def eval(self, symbol, evaluation): + "UpValues[symbol_]" + + return get_symbol_values(symbol, "UpValues", "up", evaluation) diff --git a/mathics/builtin/atomic/atomic.py b/mathics/builtin/atomic/atomic.py index e8c1cd8cf..85da215f2 100644 --- a/mathics/builtin/atomic/atomic.py +++ b/mathics/builtin/atomic/atomic.py @@ -3,11 +3,7 @@ Atomic Primitives """ -from mathics.builtin.base import ( - Builtin, - Test, -) - +from mathics.builtin.base import Builtin, Test from mathics.core.atoms import Atom @@ -17,7 +13,8 @@ class AtomQ(Test):
'AtomQ[$expr$]' -
returns 'True' if $expr$ is an expression which cannot be divided into subexpressions, or 'False' otherwise. +
returns 'True' if $expr$ is an expression which cannot be divided into \ + subexpressions, or 'False' otherwise. An expression that cannot be divided into subparts is called called an "atom".
diff --git a/mathics/builtin/atomic/numbers.py b/mathics/builtin/atomic/numbers.py index 94690804b..619c1e00b 100644 --- a/mathics/builtin/atomic/numbers.py +++ b/mathics/builtin/atomic/numbers.py @@ -1,26 +1,30 @@ # cython: language_level=3 # -*- coding: utf-8 -*- -# Note: docstring is not flowed in documentation. Line breaks in the docstring will appear in the -# printed output, so be careful not to add them mid-sentence. +# Note: docstring is not flowed in documentation. To avoid line breaks +# in docstrings apparing in the printed output, use \ before the line +# break. """ Representation of Numbers -Integers and Real numbers with any number of digits, automatically tagging numerical preceision when appropriate. +Integers and Real numbers with any number of digits, automatically tagging \ +numerical preceision when appropriate. + +Precision is not "guarded" through the evaluation process. Only integer \ +precision is supported. -Precision is not "guarded" through the evaluation process. Only integer precision is supported. However, things like 'N[Pi, 100]' should work as expected. """ +from functools import lru_cache + import mpmath import sympy -from functools import lru_cache - from mathics.builtin.base import Builtin, Predefined, Test - from mathics.core.atoms import ( + Complex, Integer, Integer0, Integer10, @@ -29,21 +33,12 @@ Number, Rational, Real, - Complex, -) -from mathics.core.attributes import ( - A_LISTABLE, - A_PROTECTED, ) +from mathics.core.attributes import A_LISTABLE, A_PROTECTED from mathics.core.convert.python import from_bool, from_python -from mathics.eval.nevaluator import eval_N from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.number import ( - dps, - machine_precision, - machine_epsilon, -) +from mathics.core.number import dps, machine_epsilon, machine_precision from mathics.core.symbols import Symbol, SymbolDivide from mathics.core.systemsymbols import ( SymbolIndeterminate, @@ -54,6 +49,7 @@ SymbolRealDigits, SymbolRound, ) +from mathics.eval.nevaluator import eval_N SymbolIntegerDigits = Symbol("IntegerDigits") SymbolIntegerExponent = Symbol("IntegerExponent") @@ -149,11 +145,17 @@ def convert_float(x, base, exponents): class Accuracy(Builtin): """ - :Accuracy: https://en.wikipedia.org/wiki/Accuracy_and_precision (WMA :Accuracy: https://reference.wolfram.com/language/ref/Accuracy.html) + + :Accuracy: + https://en.wikipedia.org/wiki/Accuracy_and_precision\ + (WMA + :Accuracy: + https://reference.wolfram.com/language/ref/Accuracy.html)
'Accuracy[$x$]' -
examines the number of significant digits of $expr$ after the decimal point in the number x. +
examines the number of significant digits of $expr$ after the \ + decimal point in the number x.
This is rather a proof-of-concept than a full implementation. @@ -162,7 +164,8 @@ class Accuracy(Builtin): >> Accuracy[3.1416`2] = 1.50298 - Notice that the value is not exactly equal to the obtained in WMA: This is due to the different way in which 'Precision' is handled in SymPy. + Notice that the value is not exactly equal to the obtained in WMA: \ + This is due to the different way in which 'Precision' is handled in SymPy. Accuracy for exact atoms is $Infinity$: >> Accuracy[1] @@ -170,7 +173,8 @@ class Accuracy(Builtin): >> Accuracy[A] = Infinity - For Complex numbers, the accuracy is the smaller of the accuracies of its real and imaginary parts: + For Complex numbers, the accuracy is the smaller of the accuracies of its \ + real and imaginary parts: >> Accuracy[1.00`2 + 2.00`2 I] = 1. @@ -190,7 +194,9 @@ class Accuracy(Builtin): >> Accuracy[{{1, 1.`},{1.``5, 1.``10}}] = 5. - See also :'Precision': /doc/reference-of-built-in-symbols/atomic-elements-of-expressions/representation-of-numbers/precision/. + See also + :'Precision': + /doc/reference-of-built-in-symbols/atomic-elements-of-expressions/representation-of-numbers/precision/. """ summary_text = "find the accuracy of a number" @@ -228,7 +234,9 @@ def apply(self, z, evaluation): class ExactNumberQ(Test): """ - :WMA link:https://reference.wolfram.com/language/ref/ExactNumberQ.html + + :WMA link: + https://reference.wolfram.com/language/ref/ExactNumberQ.html
'ExactNumberQ[$expr$]' @@ -257,7 +265,8 @@ def test(self, expr): class IntegerExponent(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/IntegerExponent.html + :WMA link: + https://reference.wolfram.com/language/ref/IntegerExponent.html
'IntegerExponent[$n$, $b$]' @@ -329,7 +338,8 @@ def apply_two_arg_integers(self, n: Integer, b: Integer, evaluation): class IntegerLength(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/IntegerLength.html + :WMA link: + https://reference.wolfram.com/language/ref/IntegerLength.html
'IntegerLength[$x$]' @@ -414,7 +424,8 @@ def apply(self, n, b, evaluation): class InexactNumberQ(Test): """ - :WMA link:https://reference.wolfram.com/language/ref/InexactNumberQ.html + :WMA link: + https://reference.wolfram.com/language/ref/InexactNumberQ.html
'InexactNumberQ[$expr$]' @@ -490,11 +501,14 @@ def test(self, expr): class RealDigits(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/RealDigits.html + :WMA link: + https://reference.wolfram.com/language/ref/RealDigits.html
'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. +
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$. @@ -606,7 +620,8 @@ def apply_rational_without_base(self, n, evaluation): def apply(self, n, evaluation): "%(name)s[n_]" - # Handling the testcases that throw the error message and return the ouput that doesn't include `base` argument + # Handling the testcases that throw the error message and return the + # output that doesn't include `base` argument if isinstance(n, Symbol) and n.name.startswith("System`"): return evaluation.message("RealDigits", "ndig", n) @@ -755,7 +770,8 @@ class MaxPrecision(Predefined):
'$MaxPrecision' -
represents the maximum number of digits of precision permitted in abitrary-precision numbers. +
represents the maximum number of digits of precision permitted \ + in abitrary-precision numbers.
>> $MaxPrecision @@ -807,12 +823,13 @@ class MaxPrecision(Predefined): class MachineEpsilon_(Predefined): """ - :WMA link:https://reference.wolfram.com/language/ref/$MachineEpsilon.html + :WMA link: + https://reference.wolfram.com/language/ref/$MachineEpsilon.html
'$MachineEpsilon' -
is the distance between '1.0' and the next - nearest representable machine-precision number. +
is the distance between '1.0' and the next \ + nearest representable machine-precision number.
>> $MachineEpsilon @@ -886,11 +903,13 @@ class MachinePrecision(Predefined): class MinPrecision(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/MinPrecision.html + + :WMA link:https://reference.wolfram.com/language/ref/$MinPrecision.html
'$MinPrecision' -
represents the minimum number of digits of precision permitted in abitrary-precision numbers. +
represents the minimum number of digits of precision permitted in \ + abitrary-precision numbers.
>> $MinPrecision @@ -941,7 +960,8 @@ class MinPrecision(Builtin): class NumericQ(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/NumericQ.html + :WMA link: + https://reference.wolfram.com/language/ref/NumericQ.html
'NumericQ[$expr$]' @@ -992,7 +1012,11 @@ def apply(self, expr, evaluation): class Precision(Builtin): """ - :Precision: https://en.wikipedia.org/wiki/Accuracy_and_precision (WMA :Precision: https://reference.wolfram.com/language/ref/Precision.html) + + :Precision: + https://en.wikipedia.org/wiki/Accuracy_and_precision ( + :WMA: + https://reference.wolfram.com/language/ref/Precision.html)
'Precision[$expr$]' @@ -1026,7 +1050,9 @@ class Precision(Builtin): = 5. - See also :'Accuracy': /doc/reference-of-built-in-symbols/atomic-elements-of-expressions/representation-of-numbers/accuracy/. + See also + :'Accuracy': + /doc/reference-of-built-in-symbols/atomic-elements-of-expressions/representation-of-numbers/accuracy/. """ rules = { diff --git a/mathics/builtin/atomic/strings.py b/mathics/builtin/atomic/strings.py index eae8397f3..de4aa411e 100644 --- a/mathics/builtin/atomic/strings.py +++ b/mathics/builtin/atomic/strings.py @@ -462,15 +462,20 @@ class CharacterEncoding(Predefined):
'$CharacterEncoding' -
specifies the default raw character encoding to use for input and output when no encoding is explicitly specified. Initially this is set to '$SystemCharacterEncoding'. +
specifies the default raw character encoding to use for input and \ + output when no encoding is explicitly specified. \ + Initially this is set to '$SystemCharacterEncoding'.
- See the character encoding current is in effect and used in input and output functions functions like 'OpenRead[]': + See the character encoding current is in effect and used in input and \ + output functions functions like 'OpenRead[]': >> $CharacterEncoding = ... - See also :$SystemCharacterEncoding: /doc/reference-of-built-in-symbols/atomic-elements-of-expressions/string-manipulation/$systemcharacterencoding/. + See also + :$SystemCharacterEncoding: + /doc/reference-of-built-in-symbols/atomic-elements-of-expressions/string-manipulation/$systemcharacterencoding/. """ name = "$CharacterEncoding" diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index a4a3249b3..01f1ac0ce 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/atomic/symbols.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Symbolic Handling +Symbol Handling Symbolic data. Every symbol has a unique name, exists in a certain context \ or namespace, and can have a variety of type of values and attributes. @@ -771,38 +771,6 @@ def test(self, expr): return isinstance(expr, Symbol) -# In Mathematica 5, this appears under "Types of Values". -class UpValues(Builtin): - """ - :WMA: https://reference.wolfram.com/language/ref/UpValues.html -
-
'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 = A_HOLD_ALL | A_PROTECTED - summary_text = "give a list of transformation rules corresponding to upvalues defined for a symbol" - - def eval(self, symbol, evaluation): - "UpValues[symbol_]" - - return get_symbol_values(symbol, "UpValues", "up", evaluation) - - class ValueQ(Builtin): """ :WMA: https://reference.wolfram.com/language/ref/ValueQ.html diff --git a/mathics/builtin/fileformats/htmlformat.py b/mathics/builtin/fileformats/htmlformat.py index a8c33e978..7fc1a798a 100644 --- a/mathics/builtin/fileformats/htmlformat.py +++ b/mathics/builtin/fileformats/htmlformat.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- - - """ HTML -Basic implementation for a HTML importer - +Basic implementation for a HTML importer. """ +import platform +import re +from io import BytesIO + from mathics.builtin.base import Builtin, MessageException from mathics.builtin.files_io.files import MathicsOpen from mathics.core.atoms import String @@ -19,10 +20,6 @@ from mathics.core.symbols import Symbol from mathics.core.systemsymbols import SymbolRule -from io import BytesIO -import platform -import re - try: import lxml.html as lhtml except ImportError: @@ -129,7 +126,7 @@ class _TagImport(_HTMLBuiltin): def _import(self, tree): raise NotImplementedError - def apply(self, text, evaluation): + def eval(self, text, evaluation): """%(name)s[text_String]""" tree = parse_html(parse_html_file, text, evaluation) if isinstance(tree, Symbol): # $Failed? @@ -146,7 +143,7 @@ class _Get(_HTMLBuiltin): "prserr": "``.", } - def apply(self, text, evaluation): + def eval(self, text, evaluation): """%(name)s[text_String]""" root = parse_html(self._parse, text, evaluation) if isinstance(root, Symbol): # $Failed? @@ -160,8 +157,8 @@ class HTMLGet(_Get): ## :native internal:
-
HTMLGet['str'] -
Parses 'str' as HTML code. +
HTMLGet['str'] +
Parses 'str' as HTML code.
""" @@ -176,9 +173,10 @@ class HTMLGetString(_Get): ## :native internal:
-
'HTML`Parser`HTMLGetString["string"]' -
parses HTML code contained in "string". +
'HTML`Parser`HTMLGetString["string"]' +
parses HTML code contained in "string".
+ #> Head[HTML`Parser`HTMLGetString[""]] = XMLObject[Document] @@ -297,8 +295,8 @@ class FullDataImport(_DataImport): ## :internal native:
-
'HTML`FullDataImport["filename"]' -
imports data from a HTML file. +
'HTML`FullDataImport["filename"]' +
imports data from a HTML file.
""" @@ -365,8 +363,8 @@ class PlaintextImport(_TagImport): ## :native internal:
-
'HTML`PlaintextImport["filename"]' -
imports plane text from a HTML file. +
'HTML`PlaintextImport["filename"]' +
imports plane text from a HTML file.
>> DeleteDuplicates[StringCases[Import["ExampleData/PrimeMeridian.html"], RegularExpression["Wiki[a-z]+"]]] = {Wikipedia, Wikidata, Wikibase, Wikimedia} @@ -390,8 +388,8 @@ class SourceImport(_HTMLBuiltin): ## :native internal:
-
'HTML`SourceImport["filename"]' -
imports source code from a HTML file. +
'HTML`SourceImport["filename"]' +
imports source code from a HTML file.
>> DeleteDuplicates[StringCases[Import["ExampleData/PrimeMeridian.html", "Source"], RegularExpression[""]]] = {, <tr>, <th>, <td>} @@ -399,7 +397,7 @@ class SourceImport(_HTMLBuiltin): summary_text = "import source code from a HTML file" - def apply(self, text, evaluation): + def eval(self, text, evaluation): """%(name)s[text_String]""" def source(filename): @@ -416,8 +414,8 @@ class TitleImport(_TagImport): ## <url>:native internal:</url> <dl> - <dt>'HTML`TitleImport["filename"]' - <dd> imports the title string from a HTML file. + <dt>'HTML`TitleImport["filename"]' + <dd> imports the title string from a HTML file. </dl> >> Import["ExampleData/PrimeMeridian.html", "Title"] = Prime meridian - Wikipedia @@ -446,7 +444,7 @@ class XMLObjectImport(_HTMLBuiltin): summary_text = "import XML objects from a HTML file" - def apply(self, text, evaluation): + def eval(self, text, evaluation): """%(name)s[text_String]""" xml = to_expression("HTML`Parser`HTMLGet", text).evaluate(evaluation) return ListExpression(Expression(SymbolRule, String("XMLObject"), xml)) diff --git a/mathics/builtin/fileformats/xmlformat.py b/mathics/builtin/fileformats/xmlformat.py index 9bf2131f0..16012d370 100644 --- a/mathics/builtin/fileformats/xmlformat.py +++ b/mathics/builtin/fileformats/xmlformat.py @@ -2,10 +2,15 @@ """ XML + +Basic implementation for an XML importer. """ -from mathics.builtin.base import Builtin +import re +from io import BytesIO + +from mathics.builtin.base import Builtin, MessageException from mathics.builtin.files_io.files import MathicsOpen from mathics.core.atoms import String from mathics.core.convert.expression import to_expression, to_mathics_list @@ -14,11 +19,6 @@ from mathics.core.symbols import Symbol from mathics.core.systemsymbols import SymbolFailed -from mathics.builtin.base import MessageException - -from io import BytesIO -import re - # use lxml, if available, as it has some additional features such as parsing XML # versions, comments and cdata. fallback on python builtin xml parser otherwise. @@ -228,11 +228,13 @@ def parse_xml(parse, text, evaluation): class XMLObject(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/XMLObject.html</url> + <url>: + WMA link: + https://reference.wolfram.com/language/ref/XMLObject.html</url> <dl> - <dt>'XMLObject["type"]' - <dd> represents the head of an XML object in symbolic XML. + <dt>'XMLObject["type"]' + <dd> represents the head of an XML object in symbolic XML. </dl> """ @@ -273,8 +275,8 @@ class XMLGet(_Get): ## <url>:native internal:</url> <dl> - <dt>'XMLGet[...]' - <dd> Internal. Document me. + <dt>'XMLGet[...]' + <dd> Internal. Document me. </dl> """ @@ -311,12 +313,15 @@ def _parse(self, text): class PlaintextImport(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/PlaintextImport.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/PlaintextImport.html</url> <dl> - <dt>'XML`PlaintextImport["string"]' - <dd>parses "string" as XML code, and returns it as plain text. + <dt>'XML`PlaintextImport["string"]' + <dd>parses "string" as XML code, and returns it as plain text. </dl> + >> StringReplace[StringTake[Import["ExampleData/InventionNo1.xml", "Plaintext"],31],FromCharacterCode[10]->"/"] = MuseScore 1.2/2012-09-12/5.7/40 """ @@ -345,9 +350,10 @@ class TagsImport(Builtin): ## <url>:native internal:</url> <dl> - <dt>'XML`TagsImport["string"]' - <dd>parses "string" as XML code, and returns a list with the tags found. + <dt>'XML`TagsImport["string"]' + <dd>parses "string" as XML code, and returns a list with the tags found. </dl> + >> Take[Import["ExampleData/InventionNo1.xml", "Tags"], 10] = {accidental, alter, arpeggiate, articulations, attributes, backup, bar-style, barline, beam, beat-type} """ @@ -380,8 +386,8 @@ class XMLObjectImport(Builtin): ## <url>:native internal:</url> <dl> - <dt>'XML`XMLObjectImport["string"]' - <dd>parses "string" as XML code, and returns a list of XMLObjects found. + <dt>'XML`XMLObjectImport["string"]' + <dd>parses "string" as XML code, and returns a list of XMLObjects found. </dl> >> Part[Import["ExampleData/InventionNo1.xml", "XMLObject"], 2, 3, 1] diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index e208fc0a9..e61909ce2 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -76,7 +76,7 @@ class Input_(Predefined): <url>:WMA link:https://reference.wolfram.com/language/ref/Input_.html</url> <dl> - <dt>'$Input' + <dt>'$Input' <dd>is the name of the stream from which input is currently being read. </dl> @@ -116,7 +116,7 @@ class _OpenAction(Builtin): ), } - def apply_empty(self, evaluation, options): + def eval_empty(self, evaluation, options): "%(name)s[OptionsPattern[]]" if isinstance(self, (OpenWrite, OpenAppend)): @@ -129,12 +129,12 @@ def apply_empty(self, evaluation, options): tmpf = tempfile.NamedTemporaryFile(dir=TMP_DIR, delete=False) path = String(tmpf.name) tmpf.close() - return self.apply_path(path, evaluation, options) + return self.eval_path(path, evaluation, options) else: evaluation.message("OpenRead", "argx") return - def apply_path(self, path, evaluation, options): + def eval_path(self, path, evaluation, options): "%(name)s[path_?NotOptionQ, OptionsPattern[]]" # Options @@ -183,7 +183,8 @@ def apply_path(self, path, evaluation, options): class Character(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Character.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/Character.html</url> <dl> <dt>'Character' @@ -224,7 +225,7 @@ class Close(Builtin): "closex": "`1`.", } - def apply(self, channel, evaluation): + def eval(self, channel, evaluation): "Close[channel_]" if channel.has_form(("InputStream", "OutputStream"), 2): @@ -244,7 +245,8 @@ def apply(self, channel, evaluation): class EndOfFile(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/EndOfFile.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/EndOfFile.html</url> <dl> <dt>'EndOfFile' @@ -264,7 +266,11 @@ class Expression_(Builtin): <dd>is a data type for 'Read'. </dl> - For information about underlying data structure Expression (a kind of M-expression) that is central in evaluation, see: <url>https://mathics-development-guide.readthedocs.io/en/latest/extending/code-overview/ast.html</url> + For information about underlying data structure Expression (a kind of \ + M-expression) that is central in evaluation, see: \ + <url> + :AST, M-Expression, General List same thing: + https://mathics-development-guide.readthedocs.io/en/latest/extending/code-overview/ast.html</url>. """ summary_text = "WL expression" @@ -273,7 +279,8 @@ class Expression_(Builtin): class FilePrint(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/FilePrint.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/FilePrint.html</url> <dl> <dt>'FilePrint[$file$]' @@ -307,7 +314,7 @@ class FilePrint(Builtin): "WordSeparators": '{" ", "\t"}', } - def apply(self, path, evaluation, options): + def eval(self, path, evaluation, options): "FilePrint[path_, OptionsPattern[FilePrint]]" pypath = path.to_python() if not ( @@ -415,7 +422,7 @@ class Get(PrefixOperator): "Trace": "False", } - def apply(self, path, evaluation, options): + def eval(self, path, evaluation, options): "Get[path_String, OptionsPattern[Get]]" def check_options(options): @@ -467,7 +474,7 @@ def check_options(options): return SymbolFailed return result - def apply_default(self, filename, evaluation): + def eval_default(self, filename, evaluation): "Get[filename_]" expr = to_expression("Get", filename) evaluation.message("General", "stream", filename) @@ -476,11 +483,13 @@ def apply_default(self, filename, evaluation): class InputFileName_(Predefined): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/$InputFileName.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/$InputFileName.html</url> <dl> - <dt>'$InputFileName' - <dd>is the name of the file from which input is currently being read. + <dt>'$InputFileName' + <dd>is the name of the file from which input is currently being read. </dl> While in interactive mode, '$InputFileName' is "". @@ -518,7 +527,9 @@ class InputStream(Builtin): class OpenRead(_OpenAction): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/OpenRead.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/OpenRead.html</url> <dl> <dt>'OpenRead["file"]' @@ -562,7 +573,7 @@ class OpenWrite(_OpenAction): <url>:WMA link:https://reference.wolfram.com/language/ref/OpenWrite.html</url> <dl> - <dt>'OpenWrite["file"]' + <dt>'OpenWrite["file"]' <dd>opens a file and returns an OutputStream. </dl> @@ -615,7 +626,7 @@ class Put(BinaryOperator): <url>:WMA link:https://reference.wolfram.com/language/ref/Put.html</url> <dl> - <dt>'$expr$ >> $filename$' + <dt>'$expr$ >> $filename$' <dd>write $expr$ to a file. <dt>'Put[$expr1$, $expr2$, ..., $filename$]' <dd>write a sequence of expressions to a file. @@ -667,21 +678,21 @@ class Put(BinaryOperator): operator = ">>" precedence = 30 - def apply(self, exprs, filename, evaluation): + def eval(self, exprs, filename, evaluation): "Put[exprs___, filename_String]" instream = to_expression("OpenWrite", filename).evaluate(evaluation) if len(instream.elements) == 2: name, n = instream.elements else: return # opening failed - result = self.apply_input(exprs, name, n, evaluation) + result = self.eval_input(exprs, name, n, evaluation) instream_number = instream.elements[1].value py_instream = stream_manager.lookup_stream(instream_number) close_stream(py_instream, instream_number) return result - def apply_input(self, exprs, name, n, evaluation): + def eval_input(self, exprs, name, n, evaluation): "Put[exprs___, OutputStream[name_, n_]]" stream = stream_manager.lookup_stream(n.get_int_value()) @@ -700,7 +711,7 @@ def apply_input(self, exprs, name, n, evaluation): return SymbolNull - def apply_default(self, exprs, filename, evaluation): + def eval_default(self, exprs, filename, evaluation): "Put[exprs___, filename_]" expr = to_expression("Put", exprs, filename) evaluation.message("General", "stream", filename) @@ -709,12 +720,15 @@ def apply_default(self, exprs, filename, evaluation): class PutAppend(BinaryOperator): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/PutAppend.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/PutAppend.html</url> <dl> - <dt>'$expr$ >>> $filename$' + <dt>'$expr$ >>> $filename$' <dd>append $expr$ to a file. - <dt>'PutAppend[$expr1$, $expr2$, ..., $"filename"$]' + + <dt>'PutAppend[$expr1$, $expr2$, ..., $"filename"$]' <dd>write a sequence of expressions to a file. </dl> @@ -762,18 +776,18 @@ class PutAppend(BinaryOperator): operator = ">>>" precedence = 30 - def apply(self, exprs, filename, evaluation): + def eval(self, exprs, filename, evaluation): "PutAppend[exprs___, filename_String]" instream = to_expression("OpenAppend", filename).evaluate(evaluation) if len(instream.elements) == 2: name, n = instream.elements else: return # opening failed - result = self.apply_input(exprs, name, n, evaluation) + result = self.eval_input(exprs, name, n, evaluation) to_expression("Close", instream).evaluate(evaluation) return result - def apply_input(self, exprs, name, n, evaluation): + def eval_input(self, exprs, name, n, evaluation): "PutAppend[exprs___, OutputStream[name_, n_]]" stream = stream_manager.lookup_stream(n.get_int_value()) @@ -792,7 +806,7 @@ def apply_input(self, exprs, name, n, evaluation): return SymbolNull - def apply_default(self, exprs, filename, evaluation): + def eval_default(self, exprs, filename, evaluation): "PutAppend[exprs___, filename_]" expr = to_expression("PutAppend", exprs, filename) evaluation.message("General", "stream", filename) @@ -1043,7 +1057,7 @@ def check_options(self, options): return result - def apply(self, channel, types, evaluation, options): + def eval(self, channel, types, evaluation, options): "Read[channel_, types_, OptionsPattern[Read]]" name, n, stream = read_name_and_stream_from_channel(channel, evaluation) @@ -1188,7 +1202,7 @@ def apply(self, channel, types, evaluation, options): return from_python(result) - def apply_nostream(self, arg1, arg2, evaluation): + def eval_nostream(self, arg1, arg2, evaluation): "Read[arg1_, arg2_]" evaluation.message("General", "stream", arg1) return @@ -1196,14 +1210,18 @@ def apply_nostream(self, arg1, arg2, evaluation): class ReadList(Read): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ReadList.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ReadList.html</url> <dl> - <dt>'ReadList["$file$"]' + <dt>'ReadList["$file$"]' <dd>Reads all the expressions until the end of file. - <dt>'ReadList["$file$", $type$]' + + <dt>'ReadList["$file$", $type$]' <dd>Reads objects of a specified type until the end of file. - <dt>'ReadList["$file$", {$type1$, $type2$, ...}]' + + <dt>'ReadList["$file$", {$type1$, $type2$, ...}]' <dd>Reads a sequence of specified types until the end of file. </dl> @@ -1259,7 +1277,7 @@ class ReadList(Read): "WordSeparators": '{" ", "\t"}', } - def apply(self, channel, types, evaluation, options): + def eval(self, channel, types, evaluation, options): "ReadList[channel_, types_, OptionsPattern[ReadList]]" # Options @@ -1273,7 +1291,7 @@ def apply(self, channel, types, evaluation, options): result = [] while True: - tmp = super(ReadList, self).apply(channel, types, evaluation, options) + tmp = super(ReadList, self).eval(channel, types, evaluation, options) if tmp is None: return @@ -1286,7 +1304,7 @@ def apply(self, channel, types, evaluation, options): result.append(tmp) return from_python(result) - def apply_m(self, channel, types, m, evaluation, options): + def eval_m(self, channel, types, m, evaluation, options): "ReadList[channel_, types_, m_, OptionsPattern[ReadList]]" # Options @@ -1307,7 +1325,7 @@ def apply_m(self, channel, types, m, evaluation, options): result = [] for i in range(py_m): - tmp = super(ReadList, self).apply(channel, types, evaluation, options) + tmp = super(ReadList, self).eval(channel, types, evaluation, options) if tmp is SymbolFailed: return @@ -1320,10 +1338,12 @@ def apply_m(self, channel, types, m, evaluation, options): class StreamPosition(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/StreamPosition.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/StreamPosition.html</url> <dl> - <dt>'StreamPosition[$stream$]' + <dt>'StreamPosition[$stream$]' <dd>returns the current position in a stream as an integer. </dl> @@ -1339,7 +1359,7 @@ class StreamPosition(Builtin): summary_text = "find the position of the current point in an open stream" - def apply_input(self, name, n, evaluation): + def eval_input(self, name, n, evaluation): "StreamPosition[InputStream[name_, n_]]" stream = stream_manager.lookup_stream(n.get_int_value()) @@ -1349,11 +1369,11 @@ def apply_input(self, name, n, evaluation): return Integer(stream.io.tell()) - def apply_output(self, name, n, evaluation): + def eval_output(self, name, n, evaluation): "StreamPosition[OutputStream[name_, n_]]" self.input_apply(name, n, evaluation) - def apply_default(self, stream, evaluation): + def eval_default(self, stream, evaluation): "StreamPosition[stream_]" evaluation.message("General", "stream", stream) return @@ -1361,7 +1381,9 @@ def apply_default(self, stream, evaluation): class SetStreamPosition(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/SetStreamPosition.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/SetStreamPosition.html</url> <dl> <dt>'SetStreamPosition[$stream$, $n$]' @@ -1400,7 +1422,7 @@ class SetStreamPosition(Builtin): } summary_text = "set the position of the current point in an open stream" - def apply_input(self, name, n, m, evaluation): + def eval_input(self, name, n, m, evaluation): "SetStreamPosition[InputStream[name_, n_], m_]" stream = stream_manager.lookup_stream(n.get_int_value()) @@ -1431,11 +1453,11 @@ def apply_input(self, name, n, m, evaluation): return Integer(stream.io.tell()) - def apply_output(self, name, n, m, evaluation): + def eval_output(self, name, n, m, evaluation): "SetStreamPosition[OutputStream[name_, n_], m_]" - return self.apply_input(name, n, m, evaluation) + return self.eval_input(name, n, m, evaluation) - def apply_default(self, stream, evaluation): + def eval_default(self, stream, evaluation): "SetStreamPosition[stream_]" evaluation.message("General", "stream", stream) return @@ -1443,12 +1465,15 @@ def apply_default(self, stream, evaluation): class Skip(Read): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Skip.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/Skip.html</url> <dl> - <dt>'Skip[$stream$, $type$]' + <dt>'Skip[$stream$, $type$]' <dd>skips ahead in an input steream by one object of the specified $type$. - <dt>'Skip[$stream$, $type$, $n$]' + + <dt>'Skip[$stream$, $type$, $n$]' <dd>skips ahead in an input steream by $n$ objects of the specified $type$. </dl> @@ -1488,7 +1513,7 @@ class Skip(Read): } summary_text = "skip over an object of the specified type in an input stream" - def apply(self, name, n, types, m, evaluation, options): + def eval(self, name, n, types, m, evaluation, options): "Skip[InputStream[name_, n_], types_, m_, OptionsPattern[Skip]]" channel = to_expression("InputStream", name, n) @@ -1511,7 +1536,7 @@ def apply(self, name, n, types, m, evaluation, options): ) return for i in range(py_m): - result = super(Skip, self).apply(channel, types, evaluation, options) + result = super(Skip, self).eval(channel, types, evaluation, options) if result is SymbolEndOfFile: return result return SymbolNull @@ -1522,7 +1547,7 @@ class Find(Read): <url>:WMA link:https://reference.wolfram.com/language/ref/Find.html</url> <dl> - <dt>'Find[$stream$, $text$]' + <dt>'Find[$stream$, $text$]' <dd>find the first line in $stream$ that contains $text$. </dl> @@ -1552,7 +1577,7 @@ class Find(Read): } summary_text = "find the next occurrence of a string" - def apply(self, name, n, text, evaluation, options): + def eval(self, name, n, text, evaluation, options): "Find[InputStream[name_, n_], text_, OptionsPattern[Find]]" # Options @@ -1578,9 +1603,7 @@ def apply(self, name, n, text, evaluation, options): py_text = [t[1:-1] for t in py_text] while True: - tmp = super(Find, self).apply( - channel, Symbol("Record"), evaluation, options - ) + tmp = super(Find, self).eval(channel, Symbol("Record"), evaluation, options) py_tmp = tmp.to_python()[1:-1] if py_tmp == "System`EndOfFile": @@ -1596,7 +1619,9 @@ def apply(self, name, n, text, evaluation, options): class OutputStream(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/OutputStream.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/OutputStream.html</url> <dl> <dt>'OutputStream[$name$, $n$]' @@ -1613,7 +1638,9 @@ class OutputStream(Builtin): class StringToStream(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/StringToStream.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/StringToStream.html</url> <dl> <dt>'StringToStream[$string$]' @@ -1635,7 +1662,7 @@ class StringToStream(Builtin): summary_text = "open an input stream for reading from a string" - def apply(self, string, evaluation): + def eval(self, string, evaluation): "StringToStream[string_]" pystring = string.to_python()[1:-1] fp = io.StringIO(str(pystring)) @@ -1671,11 +1698,11 @@ class Streams(Builtin): summary_text = "list currently open streams" - def apply(self, evaluation): + def eval(self, evaluation): "Streams[]" - return self.apply_name(None, evaluation) + return self.eval_name(None, evaluation) - def apply_name(self, name, evaluation): + def eval_name(self, name, evaluation): "Streams[name_String]" result = [] for stream in stream_manager.STREAMS.values(): @@ -1747,7 +1774,7 @@ class Write(Builtin): summary_text = "write a sequence of expressions to a stream, ending the output with a newline (line feed)" - def apply(self, channel, expr, evaluation): + def eval(self, channel, expr, evaluation): "Write[channel_, expr___]" stream = None @@ -1775,7 +1802,9 @@ def apply(self, channel, expr, evaluation): class WriteString(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/WriteString.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/WriteString.html</url> <dl> <dt>'WriteString[$stream$, $str1, $str2$, ... ]' @@ -1832,7 +1861,7 @@ class WriteString(Builtin): "writex": "`1`.", } - def apply(self, channel, expr, evaluation): + def eval(self, channel, expr, evaluation): "WriteString[channel_, expr___]" stream = None if isinstance(channel, String): From 7be1dd9f4263c3cee4fe720fce0e451358588d99 Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Wed, 21 Dec 2022 06:03:55 -0300 Subject: [PATCH 064/121] docstr url for files_io, intfns, list and trace --- mathics/builtin/files_io/files.py | 9 ++++----- mathics/builtin/trace.py | 10 +++++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index e61909ce2..eea507877 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -249,7 +249,7 @@ class EndOfFile(Builtin): https://reference.wolfram.com/language/ref/EndOfFile.html</url> <dl> - <dt>'EndOfFile' + <dt>'EndOfFile' <dd>is returned by 'Read' when the end of an input stream is reached. </dl> """ @@ -279,11 +279,10 @@ class Expression_(Builtin): class FilePrint(Builtin): """ - <url>:WMA link: - https://reference.wolfram.com/language/ref/FilePrint.html</url> + <url>:WMA link:https://reference.wolfram.com/language/ref/FilePrint.html</url> <dl> - <dt>'FilePrint[$file$]' + <dt>'FilePrint[$file$]' <dd>prints the raw contents of $file$. </dl> @@ -489,7 +488,7 @@ class InputFileName_(Predefined): <dl> <dt>'$InputFileName' - <dd>is the name of the file from which input is currently being read. + <dd>is the name of the file from which input is currently being read. </dl> While in interactive mode, '$InputFileName' is "". diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index 1ef6347ae..bd0097eb8 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -3,9 +3,11 @@ """ Tracing Built-in Functions -Built-in Function Tracing provides one high-level way understand what is getting evaluated and where the time is spent in evaluation. +Built-in Function Tracing provides one high-level way understand what is \ +getting evaluated and where the time is spent in evaluation. -With this, it may be possible for both users and implementers to follow how Mathics arrives at its results, or guide how to speed up expression evaluation. +With this, it may be possible for both users and implementers to follow \ +how Mathics arrives at its results, or guide how to speed up expression evaluation. """ @@ -144,7 +146,9 @@ class TraceBuiltins(_TraceBase): <dl> <dt>'TraceBuiltins[$expr$]' - <dd>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. + <dd>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. </dl> Sort Options: From 07c9330e0e3afa07a66938fa10cc5ac246ca14c3 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 08:28:03 -0500 Subject: [PATCH 065/121] docstr url for logic, optiondoc, sparse, tensor layout, evaluation, logic, comparison, datentime, attributes and binary --- mathics/builtin/attributes.py | 6 ++-- mathics/builtin/binary/io.py | 5 ++++ mathics/builtin/binary/system.py | 19 +++++++----- mathics/builtin/binary/types.py | 2 ++ mathics/builtin/comparison.py | 34 +++++++++++++++++++++ mathics/builtin/datentime.py | 41 ++++++++++++++++++++++++-- mathics/builtin/distance/numeric.py | 21 ++++++++++++- mathics/builtin/distance/stringdata.py | 6 ++++ mathics/builtin/evaluation.py | 20 +++++++++++++ mathics/builtin/layout.py | 30 +++++++++++++++++++ mathics/builtin/logic.py | 26 ++++++++++++++++ mathics/builtin/optiondoc.py | 40 ++++++++++++++++++++++--- mathics/builtin/sparse.py | 2 ++ mathics/builtin/tensors.py | 26 ++++++++++++++++ 14 files changed, 260 insertions(+), 18 deletions(-) diff --git a/mathics/builtin/attributes.py b/mathics/builtin/attributes.py index 70a0a3777..aedfb0ea9 100644 --- a/mathics/builtin/attributes.py +++ b/mathics/builtin/attributes.py @@ -237,7 +237,7 @@ class HoldAll(Predefined): <dl> <dt>'HoldAll' <dd>is an attribute specifying that all arguments of a \ - function should be left unevaluated. + function should be left unevaluated. </dl> >> Attributes[Function] @@ -283,7 +283,7 @@ class HoldFirst(Predefined): <dl> <dt>'HoldFirst' <dd>is an attribute specifying that the first argument of a \ - function should be left unevaluated. + function should be left unevaluated. </dl> >> Attributes[Set] @@ -379,7 +379,7 @@ class NHoldAll(Predefined): <dl> <dt>'NHoldAll' <dd>is an attribute that protects all arguments of a \ - function from numeric evaluation. + function from numeric evaluation. </dl> >> N[f[2, 3]] diff --git a/mathics/builtin/binary/io.py b/mathics/builtin/binary/io.py index d8dd000bf..006249163 100644 --- a/mathics/builtin/binary/io.py +++ b/mathics/builtin/binary/io.py @@ -34,6 +34,7 @@ class _BinaryFormat: """ + Container for BinaryRead readers and BinaryWrite writers """ @@ -362,6 +363,8 @@ def _UnsignedInteger128_writer(s, x): class BinaryRead(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/BinaryRead.html</url> + <dl> <dt>'BinaryRead[$stream$]' <dd>reads one byte from the stream as an integer from 0 to 255. @@ -657,6 +660,8 @@ def apply(self, name, n, typ, evaluation): class BinaryWrite(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/BinaryWrite.html</url> + <dl> <dt>'BinaryWrite[$channel$, $b$]' <dd>writes a single byte given as an integer from 0 to 255. diff --git a/mathics/builtin/binary/system.py b/mathics/builtin/binary/system.py index 056a34891..db5951aa6 100644 --- a/mathics/builtin/binary/system.py +++ b/mathics/builtin/binary/system.py @@ -11,6 +11,8 @@ class ByteOrdering(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ByteOrdering.html</url> + <dl> <dt>'ByteOrdering' <dd> is an option for BinaryRead, BinaryWrite, and related functions that specifies what ordering @@ -31,16 +33,17 @@ class ByteOrdering(Predefined): class ByteOrdering_(Predefined): """ - <dl> - <dt>'$ByteOrdering' - <dd>returns the native ordering of bytes in binary data on your computer system. - </dl> + <url>:WMA link:https://reference.wolfram.com/language/ref/$ByteOrdering.html</url> + <dl> + <dt>'$ByteOrdering' + <dd>returns the native ordering of bytes in binary data on your computer system. + </dl> - X> $ByteOrdering - = 1 + X> $ByteOrdering + = 1 - #> $ByteOrdering == -1 || $ByteOrdering == 1 - = True + #> $ByteOrdering == -1 || $ByteOrdering == 1 + = True """ name = "$ByteOrdering" diff --git a/mathics/builtin/binary/types.py b/mathics/builtin/binary/types.py index 68a35cf8e..f0fc13aab 100644 --- a/mathics/builtin/binary/types.py +++ b/mathics/builtin/binary/types.py @@ -9,6 +9,8 @@ class Byte(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Byte.html</url> + <dl> <dt>'Byte' <dd>is a data type for 'Read'. diff --git a/mathics/builtin/comparison.py b/mathics/builtin/comparison.py index a625e13ea..6d6a90d84 100644 --- a/mathics/builtin/comparison.py +++ b/mathics/builtin/comparison.py @@ -447,6 +447,8 @@ def pairs(elements): class BooleanQ(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/BooleanQ.html</url> + <dl> <dt>'BooleanQ[$expr$]' <dd>returns 'True' if $expr$ is either 'True' or 'False'. @@ -479,6 +481,8 @@ class BooleanQ(Builtin): class Equal(_EqualityOperator, _SympyComparison): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Equal.html</url> + <dl> <dt>'Equal[$x$, $y$]' <dt>'$x$ == $y$' @@ -614,6 +618,8 @@ def _op(x): class Greater(_ComparisonOperator, _SympyComparison): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Greater.html</url> + <dl> <dt>'Greater[$x$, $y$]' or '$x$ > $y$' <dd>yields 'True' if $x$ is known to be greater than $y$. @@ -638,6 +644,8 @@ class Greater(_ComparisonOperator, _SympyComparison): class GreaterEqual(_ComparisonOperator, _SympyComparison): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/GreaterEqual.html</url> + <dl> <dt>'GreaterEqual[$x$, $y$]' <dt>$x$ \u2256 $y$ or '$x$ >= $y$' @@ -653,6 +661,8 @@ class GreaterEqual(_ComparisonOperator, _SympyComparison): class Inequality(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Inequality.html</url> + <dl> <dt>'Inequality' <dd>is the head of expressions involving different inequality @@ -703,6 +713,8 @@ def apply(self, items, evaluation): class Less(_ComparisonOperator, _SympyComparison): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Less.html</url> + <dl> <dt>'Less[$x$, $y$]' or $x$ < $y$ <dd>yields 'True' if $x$ is known to be less than $y$. @@ -727,6 +739,8 @@ class Less(_ComparisonOperator, _SympyComparison): class LessEqual(_ComparisonOperator, _SympyComparison): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/LessEqual.html</url> + <dl> <dt>'LessEqual[$x$, $y$, ...]' or $x$ <= $y$ or $x$ \u2264 $y$ <dd>yields 'True' if $x$ is known to be less than or equal to $y$. @@ -748,6 +762,8 @@ class LessEqual(_ComparisonOperator, _SympyComparison): class Max(_MinMax): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Max.html</url> + <dl> <dt>'Max[$e_1$, $e_2$, ..., $e_i$]' <dd>returns the expression with the greatest value among the $e_i$. @@ -786,6 +802,8 @@ class Max(_MinMax): class Min(_MinMax): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Min.html</url> + <dl> <dt>'Min[$e_1$, $e_2$, ..., $e_i$]' <dd>returns the expression with the lowest value among the $e_i$. @@ -821,6 +839,8 @@ class Min(_MinMax): class Negative(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Negative.html</url> + <dl> <dt>'Negative[$x$]' <dd>returns 'True' if $x$ is a negative real number. @@ -851,6 +871,8 @@ class Negative(Builtin): class NonNegative(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/NonNegative.html</url> + <dl> <dt>'NonNegative[$x$]' <dd>returns 'True' if $x$ is a positive real number or zero. @@ -870,6 +892,8 @@ class NonNegative(Builtin): class NonPositive(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/NonPositive.html</url> + <dl> <dt>'NonPositive[$x$]' <dd>returns 'True' if $x$ is a negative real number or zero. @@ -889,6 +913,8 @@ class NonPositive(Builtin): class Positive(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Positive.html</url> + <dl> <dt>'Positive[$x$]' <dd>returns 'True' if $x$ is a positive real number. @@ -921,6 +947,8 @@ class Positive(Builtin): class SameQ(_ComparisonOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/SameQ.html</url> + <dl> <dt>'SameQ[$x$, $y$]' <dt>'$x$ === $y$' @@ -982,6 +1010,8 @@ def apply_list(self, items, evaluation): class TrueQ(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/TrueQ.html</url> + <dl> <dt>'TrueQ[$expr$]' <dd>returns 'True' if and only if $expr$ is 'True'. @@ -1005,6 +1035,8 @@ class TrueQ(Builtin): class Unequal(_EqualityOperator, _SympyComparison): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Unequal.html</url> + <dl> <dt>'Unequal[$x$, $y$]' or $x$ != $y$ or $x$ \u2260 $y$ <dd>is 'False' if $x$ and $y$ are known to be equal, or 'True' if $x$ and $y$ are known to be unequal. @@ -1077,6 +1109,8 @@ def _op(x): class UnsameQ(_ComparisonOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/UnsameQ.html</url> + <dl> <dt>'UnsameQ[$x$, $y$]' <dt>'$x$ =!= $y$' diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index 3900cf085..014cdc515 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -341,6 +341,8 @@ def to_datelist(self, epochtime, evaluation): class AbsoluteTime(_DateFormat): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/AbsoluteTime.html</url> + <dl> <dt>'AbsoluteTime[]' <dd>gives the local time in seconds since epoch January 1, 1900, in your time zone. @@ -401,6 +403,8 @@ def apply_spec(self, epochtime, evaluation): class AbsoluteTiming(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/AbsoluteTiming.html</url> + <dl> <dt>'AbsoluteTiming[$expr$]' <dd>evaluates $expr$, returning a list of the absolute number of seconds in real time that have elapsed, together with the result obtained. @@ -427,6 +431,8 @@ def apply(self, expr, evaluation): class DateDifference(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/DateDifference.html</url> + <dl> <dt>'DateDifference[$date1$, $date2$]' <dd>returns the difference between $date1$ and $date2$ in days. @@ -578,6 +584,8 @@ def intdiv(a, b, flag=True): class DateObject(_DateFormat, ImmutableValueMixin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/DateObject.html</url> + <dl> <dt>'DateObject[...]' <dd> Returns an object codifiyng DateList.... @@ -693,6 +701,8 @@ def apply_makeboxes(self, datetime, gran, cal, tz, fmt, evaluation): class DatePlus(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/DatePlus.html</url> + <dl> <dt>'DatePlus[$date$, $n$]' <dd>finds the date $n$ days after $date$. @@ -790,6 +800,8 @@ def apply(self, date, off, evaluation): class DateList(_DateFormat): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/DateList.html</url> + <dl> <dt>'DateList[]' <dd>returns the current local time in the form {$year$, $month$, $day$, $hour$, $minute$, $second$}. @@ -858,6 +870,8 @@ def apply(self, epochtime, evaluation): class DateString(_DateFormat): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/DateString.html</url> + <dl> <dt>'DateString[]' <dd>returns the current local time and date as a string. @@ -966,6 +980,8 @@ def apply(self, epochtime, form, evaluation): class DateStringFormat(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/$DateStringFormat.html</url> + <dl> <dt>'$DateStringFormat' <dd>gives the format used for dates generated by 'DateString'. @@ -989,6 +1005,8 @@ def evaluate(self, evaluation): class EasterSunday(Builtin): # Calendar`EasterSunday """ + <url>:WMA link:https://reference.wolfram.com/language/ref/EasterSunday.html</url> + <dl> <dt>'EasterSunday[$year$]' <dd>returns the date of the Gregorian Easter Sunday as {year, month, day}. @@ -1028,6 +1046,8 @@ def apply(self, year, evaluation): class Pause(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Pause.html</url> + <dl> <dt>'Pause[n]' <dd>pauses for $n$ seconds. @@ -1059,6 +1079,8 @@ def apply(self, n, evaluation): class SystemTimeZone(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/SystemTimeZone.html</url> + <dl> <dt>'$SystemTimeZone' <dd> gives the current time zone for the computer system on which Mathics is being run. @@ -1079,6 +1101,8 @@ def evaluate(self, evaluation): class Now(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Now.html</url> + <dl> <dt>'Now' <dd> gives the current time on the system. @@ -1097,7 +1121,9 @@ def evaluate(self, evaluation): if sys.platform != "win32" and not hasattr(sys, "pyston_version_info"): class TimeConstrained(Builtin): - r""" + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/TimeConstrained.html</url> + <dl> <dt>'TimeConstrained[$expr$, $t$]' <dd>'evaluates $expr$, stopping after $t$ seconds.' @@ -1109,7 +1135,7 @@ 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 state of the \\Mathics kernel. """ @@ -1164,6 +1190,9 @@ def apply_3(self, expr, t, failexpr, evaluation): class TimeZone(Predefined): """ + <url>:Time Zone:https://en.wikipedia.org/wiki/Time_zone</url> + (<url>:WMA link:https://reference.wolfram.com/language/ref/TimeZone.html</url>) + <dl> <dt>'$TimeZone' <dd> gives the current time zone to assume for dates and times. @@ -1195,6 +1224,8 @@ def evaluate(self, evaluation) -> Real: class TimeUsed(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/TimeUsed.html</url> + <dl> <dt>'TimeUsed[]' <dd>returns the total CPU time used for this session, in seconds. @@ -1217,6 +1248,8 @@ def apply(self, evaluation): class Timing(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Timing.html</url> + <dl> <dt>'Timing[$expr$]' <dd>measures the processor time taken to evaluate $expr$. @@ -1244,6 +1277,8 @@ def apply(self, expr, evaluation): class SessionTime(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/SessionTime.html</url> + <dl> <dt>'SessionTime[]' <dd>returns the total time in seconds since this session started. @@ -1264,6 +1299,8 @@ def apply(self, evaluation): class TimeRemaining(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/TimeRemaining.html</url> + <dl> <dt>'TimeRemaining[]' <dd>Gives the number of seconds remaining until the earliest enclosing 'TimeConstrained' will request the current computation to stop. diff --git a/mathics/builtin/distance/numeric.py b/mathics/builtin/distance/numeric.py index c2fd5afac..7a9e004f6 100644 --- a/mathics/builtin/distance/numeric.py +++ b/mathics/builtin/distance/numeric.py @@ -7,7 +7,6 @@ from mathics.core.expression import Expression from mathics.core.symbols import ( - Symbol, SymbolAbs, SymbolDivide, SymbolPlus, @@ -40,6 +39,9 @@ def _norm_calc(head, u, v, evaluation): class BrayCurtisDistance(Builtin): """ + <url>:Bray-Curtis Dissimilarity:https://en.wikipedia.org/wiki/Bray%E2%80%93Curtis_dissimilarity</url> \ + (<url>:WMA link:https://reference.wolfram.com/language/ref/BrayCurtisDistance.html</url>) + <dl> <dt>'BrayCurtisDistance[$u$, $v$]' <dd>returns the Bray-Curtis distance between $u$ and $v$. @@ -70,6 +72,9 @@ def apply(self, u, v, evaluation): class CanberraDistance(Builtin): """ + <url>:Canberra distance:https://en.wikipedia.org/wiki/Canberra_distance</url> \ + (<url>:WMA link:https://reference.wolfram.com/language/ref/CanberraDistance.html</url>) + <dl> <dt>'CanberraDistance[$u$, $v$]' <dd>returns the canberra distance between $u$ and $v$, which is a weighted version of the Manhattan distance. @@ -102,6 +107,9 @@ def apply(self, u, v, evaluation): class ChessboardDistance(Builtin): """ + <url>:Chebyshev distance:https://en.wikipedia.org/wiki/Chebyshev_distance</url> \ + (<url>:WMA link:https://reference.wolfram.com/language/ref/ChessboardDistance.html</url>) + <dl> <dt>'ChessboardDistance[$u$, $v$]' <dd>returns the chessboard distance (also known as Chebyshev distance) between $u$ and $v$, which is the number of moves a king on a chessboard needs to get from square $u$ to square $v$. @@ -125,6 +133,9 @@ def apply(self, u, v, evaluation): class CosineDistance(Builtin): r""" + <url>:Cosine similarity:https://en.wikipedia.org/wiki/Cosine_similarity</url> \ + (<url>:WMA link:https://reference.wolfram.com/language/ref/CosineDistance.html</url>) + <dl> <dt>'CosineDistance[$u$, $v$]' <dd>returns the cosine distance between $u$ and $v$. @@ -163,6 +174,9 @@ def apply(self, u, v, evaluation): class EuclideanDistance(Builtin): """ + <url>:Euclidean similarity:https://en.wikipedia.org/wiki/Euclidean_distance</url> \ + (<url>:WMA link:https://reference.wolfram.com/language/ref/EuclideanDistance.html</url>) + <dl> <dt>'EuclideanDistance[$u$, $v$]' <dd>returns the euclidean distance between $u$ and $v$. @@ -189,6 +203,9 @@ def apply(self, u, v, evaluation): class ManhattanDistance(Builtin): """ + <url>:Manhattan distance:https://en.wikipedia.org/wiki/Taxicab_geometry</url> \ + (<url>:WMA link:https://reference.wolfram.com/language/ref/ManhattanDistance.html</url>) + <dl> <dt>'ManhattanDistance[$u$, $v$]' <dd>returns the Manhattan distance between $u$ and $v$, which is the number of horizontal or vertical moves in the gridlike Manhattan city layout to get from $u$ to $v$. @@ -212,6 +229,8 @@ def apply(self, u, v, evaluation): class SquaredEuclideanDistance(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/SquaredEuclideanDistance.html</url> + <dl> <dt>'SquaredEuclideanDistance[$u$, $v$]' <dd>returns squared the euclidean distance between $u$ and $v$. diff --git a/mathics/builtin/distance/stringdata.py b/mathics/builtin/distance/stringdata.py index 4cbefd712..e6affb476 100644 --- a/mathics/builtin/distance/stringdata.py +++ b/mathics/builtin/distance/stringdata.py @@ -147,6 +147,8 @@ def normalize(c): class DamerauLevenshteinDistance(_StringDistance): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/DamerauLevenshteinDistance.html</url> + <dl> <dt>'DamerauLevenshteinDistance[$a$, $b$]' <dd>returns the Damerau-Levenshtein distance of $a$ and $b$, which is defined as the minimum number of @@ -187,6 +189,8 @@ def _distance(self, s1, s2, sameQ: Callable[..., bool]): class EditDistance(_StringDistance): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/EditDistance.html</url> + <dl> <dt>'EditDistance[$a$, $b$]' <dd>returns the Levenshtein distance of $a$ and $b$, which is defined as the minimum number of @@ -226,6 +230,8 @@ def _distance(self, s1, s2, sameQ: Callable[..., bool]): class HammingDistance(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/HammingDistance.html</url> + <dl> <dt>'HammingDistance[$u$, $v$]' <dd>returns the Hamming distance between $u$ and $v$, i.e. the number of different elements. diff --git a/mathics/builtin/evaluation.py b/mathics/builtin/evaluation.py index e35ef2186..078915c6d 100644 --- a/mathics/builtin/evaluation.py +++ b/mathics/builtin/evaluation.py @@ -10,6 +10,8 @@ class RecursionLimit(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/RecursionLimit.html</url> + <dl> <dt>'$RecursionLimit' <dd>specifies the maximum allowable recursion depth after which a calculation is terminated. @@ -84,6 +86,8 @@ def evaluate(self, evaluation) -> Integer: class IterationLimit(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/$IterationLimit.html</url> + <dl> <dt>'$IterationLimit' @@ -145,6 +149,8 @@ def evaluate(self, evaluation): class Hold(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Hold.html</url> + <dl> <dt>'Hold[$expr$]' <dd>prevents $expr$ from being evaluated. @@ -159,6 +165,8 @@ class Hold(Builtin): class HoldComplete(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/HoldComplete.html</url> + <dl> <dt>'HoldComplete[$expr$]' <dd>prevents $expr$ from being evaluated, and also prevents @@ -174,6 +182,8 @@ class HoldComplete(Builtin): class HoldForm(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/HoldForm.html</url> + <dl> <dt>'HoldForm[$expr$]' <dd>is equivalent to 'Hold[$expr$]', but prints as $expr$. @@ -197,6 +207,8 @@ class HoldForm(Builtin): class Evaluate(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Evaluate.html</url> + <dl> <dt>'Evaluate[$expr$]' <dd>forces evaluation of $expr$, even if it occurs inside a @@ -230,6 +242,8 @@ class Evaluate(Builtin): class Unevaluated(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Unevaluated.html</url> + <dl> <dt>'Unevaluated[$expr$]' <dd>temporarily leaves $expr$ in an unevaluated form when it @@ -272,6 +286,8 @@ class Unevaluated(Builtin): class ReleaseHold(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ReleaseHold.html</url> + <dl> <dt>'ReleaseHold[$expr$]' <dd>removes any 'Hold', 'HoldForm', 'HoldPattern' or @@ -295,6 +311,8 @@ class ReleaseHold(Builtin): class Sequence(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Sequence.html</url> + <dl> <dt>'Sequence[$x1$, $x2$, ...]' <dd>represents a sequence of arguments to a function. @@ -331,6 +349,8 @@ class Sequence(Builtin): class Quit(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Quit.html</url> + <dl> <dt>'Quit'[] <dd> Terminates the Mathics session. diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index 6858ff4e4..657beec71 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -34,6 +34,8 @@ class Center(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Center.html</url> + <dl> <dt>'Center' <dd>is used with the 'ColumnAlignments' option to 'Grid' or @@ -46,6 +48,8 @@ class Center(Builtin): class Format(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Format.html</url> + <dl> <dt>'Format[$expr$]' <dd>holds values specifying how $expr$ should be printed. @@ -80,6 +84,8 @@ class Format(Builtin): class Grid(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Grid.html</url> + <dl> <dt>'Grid[{{$a1$, $a2$, ...}, {$b1$, $b2$, ...}, ...}]' <dd>formats several expressions inside a 'GridBox'. @@ -112,6 +118,8 @@ def apply_makeboxes(self, array, f, evaluation, options) -> Expression: class Infix(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Infix.html</url> + <dl> <dt>'Infix[$expr$, $oper$, $prec$, $assoc$]' <dd>displays $expr$ with the infix operator $oper$, with precedence $prec$ and associativity $assoc$. @@ -151,6 +159,8 @@ class Infix(Builtin): class Left(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Left.html</url> + <dl> <dt>'Left' <dd>is used with operator formatting constructs to specify a left-associative operator. @@ -162,6 +172,8 @@ class Left(Builtin): class NonAssociative(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/NonAssociative.html</url> + <dl> <dt>'NonAssociative' <dd>is used with operator formatting constructs to specify a non-associative operator. @@ -173,6 +185,8 @@ class NonAssociative(Builtin): class Postfix(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Postfix.html</url> + <dl> <dt>'$x$ // $f$' <dd>is equivalent to '$f$[$x$]'. @@ -197,6 +211,8 @@ class Postfix(BinaryOperator): class Precedence(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Precedence.html</url> + <dl> <dt>'Precedence[$op$]' <dd>returns the precedence of the built-in operator $op$. @@ -235,6 +251,8 @@ def apply(self, expr, evaluation) -> Real: class Prefix(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Prefix.html</url> + <dl> <dt>'$f$ @ $x$' <dd>is equivalent to '$f$[$x$]'. @@ -269,6 +287,8 @@ class Prefix(BinaryOperator): class Right(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Right.html</url> + <dl> <dt>'Right' <dd>is used with operator formatting constructs to specify a right-associative operator. @@ -280,6 +300,8 @@ class Right(Builtin): class Row(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Row.html</url> + <dl> <dt>'Row[{$expr$, ...}]' <dd>formats several expressions inside a 'RowBox'. @@ -310,6 +332,8 @@ def apply_makeboxes(self, items, sep, f, evaluation): class Style(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Style.html</url> + <dl> <dt>'Style[$expr$, options]' <dd>displays $expr$ formatted using the specified option settings. @@ -348,6 +372,8 @@ class Style(Builtin): class Subscript(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Subscript.html</url> + <dl> <dt>'Subscript[$a$, $i$]' <dd>displays as $a_i$. @@ -372,6 +398,8 @@ def apply_makeboxes(self, x, y, f, evaluation) -> Expression: class Subsuperscript(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Subsuperscript.html</url> + <dl> <dt>'Subsuperscript[$a$, $b$, $c$]' <dd>displays as $a_b^c$. @@ -392,6 +420,8 @@ class Subsuperscript(Builtin): class Superscript(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Superscript.html</url> + <dl> <dt>'Superscript[$x$, $y$]' <dd>displays as $x$^$y$. diff --git a/mathics/builtin/logic.py b/mathics/builtin/logic.py index 12743c81d..6308d8285 100644 --- a/mathics/builtin/logic.py +++ b/mathics/builtin/logic.py @@ -34,6 +34,8 @@ class Or(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Or.html</url> + <dl> <dt>'Or[$expr1$, $expr2$, ...]' <dt>'$expr1$ || $expr2$ || ...' @@ -83,6 +85,8 @@ def apply(self, args, evaluation): class And(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/And.html</url> + <dl> <dt>'And[$expr1$, $expr2$, ...]' <dt>'$expr1$ && $expr2$ && ...' @@ -132,6 +136,8 @@ def apply(self, args, evaluation): class Not(PrefixOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Not.html</url> + <dl> <dt>'Not[$expr$]' <dt>'!$expr$' @@ -159,6 +165,8 @@ class Not(PrefixOperator): class Nand(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Nand.html</url> + <dl> <dt>'Nand[$expr1$, $expr2$, ...]' <dt>$expr1$ \u22BC $expr2$ \u22BC ... @@ -177,6 +185,8 @@ class Nand(Builtin): class Nor(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Nor.html</url> + <dl> <dt>'Nor[$expr1$, $expr2$, ...]' <dt>$expr1$ \u22BD $expr2$ \u22BD ... @@ -195,6 +205,8 @@ class Nor(Builtin): class Implies(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Implies.html</url> + <dl> <dt>'Implies[$expr1$, $expr2$]' <dt>$expr1$ \u21D2 $expr2$ @@ -234,6 +246,8 @@ def apply(self, x, y, evaluation): class Equivalent(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Equivalent.html</url> + <dl> <dt>'Equivalent[$expr1$, $expr2$, ...]' <dt>$expr1$ \u29E6 $expr2$ \u29E6 ... @@ -288,6 +302,8 @@ def apply(self, args, evaluation): class Xor(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Xor.html</url> + <dl> <dt>'Xor[$expr1$, $expr2$, ...]' <dt>$expr1$ \u22BB $expr2$ \u22BB ... @@ -351,6 +367,8 @@ def apply(self, args, evaluation): class True_(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/True_.html</url> + <dl> <dt>'True' <dd>represents the Boolean true value. @@ -364,6 +382,8 @@ class True_(Predefined): class False_(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/False.html</url> + <dl> <dt>'False' <dd>represents the Boolean false value. @@ -417,6 +437,8 @@ def callback(node): class NoneTrue(_ManyTrue): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/NoneTrue.html</url> + <dl> <dt>'NoneTrue[{$expr1$, $expr2$, ...}, $test$]' <dd>returns True if no application of $test$ to $expr1$, $expr2$, ... evaluates to True. @@ -448,6 +470,8 @@ def _no_short_circuit(self): class AnyTrue(_ManyTrue): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/AnyTrue.html</url> + <dl> <dt>'AnyTrue[{$expr1$, $expr2$, ...}, $test$]' <dd>returns True if any application of $test$ to $expr1$, $expr2$, ... evaluates to True. @@ -479,6 +503,8 @@ def _no_short_circuit(self): class AllTrue(_ManyTrue): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/AllTrue.html</url> + <dl> <dt>'AllTrue[{$expr1$, $expr2$, ...}, $test$]' <dd>returns True if all applications of $test$ to $expr1$, $expr2$, ... evaluate to True. diff --git a/mathics/builtin/optiondoc.py b/mathics/builtin/optiondoc.py index 80ef60a7f..a64a8bf62 100644 --- a/mathics/builtin/optiondoc.py +++ b/mathics/builtin/optiondoc.py @@ -22,6 +22,8 @@ class Automatic(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Automatic.html</url> + <dl> <dt>'Automatic' <dd>is used to specify an automatically computed option value. @@ -39,6 +41,8 @@ class Automatic(Builtin): class Axes(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Axes.html</url> + <dl> <dt>'Axes' <dd>is an option for charting and graphics functions that specifies whether axes should be drawn. @@ -59,6 +63,8 @@ class Axes(Builtin): class Axis(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Axis.html</url> + <dl> <dt>'Axis' <dd>is a possible value for the 'Filling' option. @@ -73,6 +79,8 @@ class Axis(Builtin): class Bottom(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Bottom.html</url> + <dl> <dt>'Bottom' <dd>is a possible value for the 'Filling' option. @@ -87,6 +95,8 @@ class Bottom(Builtin): class ChartLabels(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ChartLabels.html</url> + <dl> <dt>'ChartLabels' <dd>is a charting option that specifies what labels should be used for chart elements. @@ -101,6 +111,8 @@ class ChartLabels(Builtin): class ChartLegends(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ChartLegends.html</url> + <dl> <dt>'ChartLegends' <dd>is an option for charting functions that specifies the legends to be used for chart elements. @@ -112,6 +124,8 @@ class ChartLegends(Builtin): class Filling(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Filling.html</url> + <dl> <dt>'Filling -> [Top | Bottom| Axis]' <dd>'Filling' is a an option to 'ListPlot', 'Plot' or 'Plot3D', and related functions that indicates what filling to add under point, curves, and surfaces. @@ -126,6 +140,8 @@ class Filling(Builtin): class Full(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Full.html</url> + <dl> <dt>'Full' <dd>is a possible value for the 'Mesh' and 'PlotRange' options. @@ -137,6 +153,8 @@ class Full(Builtin): class ImageSize(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ImageSize.html</url> + <dl> <dt>'ImageSize' <dd>is an option that specifies the overall size of an image to display. @@ -160,6 +178,8 @@ class ImageSize(Builtin): class Joined(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Joined.html</url> + <dl> <dt>'Joined $boolean$' <dd>is an option for 'Plot' that gives whether to join points to make lines. @@ -176,6 +196,8 @@ class Joined(Builtin): class MaxRecursion(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/MaxRecursion.html</url> + <dl> <dt>'MaxRecursion' <dd>is an option for functions like NIntegrate and Plot that specifies how many recursive subdivisions can be made. @@ -192,6 +214,8 @@ class MaxRecursion(Builtin): class Mesh(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Mesh.html</url> + <dl> <dt>'Mesh' <dd>is a charting option, such as for 'Plot', 'BarChart', 'PieChart', etc. that specifies the mesh to be drawn. The default is 'Mesh->None'. @@ -218,6 +242,8 @@ class Mesh(Builtin): class PlotPoints(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/PlotPoints.html</url> + <dl> <dt>'PlotPoints $n$' <dd>A number specifies how many initial sample points to use. @@ -232,6 +258,8 @@ class PlotPoints(Builtin): class PlotRange(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/PlotRange.html</url> + <dl> <dt>'PlotRange' <dd>is a charting option, such as for 'Plot', 'BarChart', 'PieChart', etc. that gives the range of coordinates to include in a plot. @@ -256,6 +284,8 @@ class PlotRange(Builtin): class TicksStyle(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/TicksStyle.html</url> + <dl> <dt>'TicksStyle' <dd>is an option for graphics functions which specifies how ticks should be rendered. @@ -276,10 +306,12 @@ class TicksStyle(Builtin): class Top(Builtin): """ - <dl> - <dt>'Top' - <dd>is a possible value for the 'Filling' option. - </dl> + <url>:WMA link:https://reference.wolfram.com/language/ref/Top.html</url> + + <dl> + <dt>'Top' + <dd>is a possible value for the 'Filling' option. + </dl> >> ListLinePlot[Table[Cos[x], {x, -5, 5, 0.2}], Filling->Top] = -Graphics- diff --git a/mathics/builtin/sparse.py b/mathics/builtin/sparse.py index f9b89c322..63318ba37 100644 --- a/mathics/builtin/sparse.py +++ b/mathics/builtin/sparse.py @@ -21,6 +21,8 @@ class SparseArray(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/SparseArray.html</url> + <dl> <dt>'SparseArray[$rules$]' <dd>Builds a sparse array acording to the list of $rules$. diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index 817768158..ace75681f 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -83,6 +83,8 @@ def get_dimensions(expr, head=None): class ArrayDepth(Builtin): """ + <url>:WMA: https://reference.wolfram.com/language/ref/ArrayDepth.html</url> + <dl> <dt>'ArrayDepth[$a$]' <dd>returns the depth of the non-ragged array $a$, defined as 'Length[Dimensions[$a$]]'. @@ -103,6 +105,8 @@ class ArrayDepth(Builtin): class ArrayQ(Builtin): """ + <url>:WMA: https://reference.wolfram.com/language/ref/ArrayQ.html</url> + <dl> <dt>'ArrayQ[$expr$]' <dd>tests whether $expr$ is a full array. @@ -170,6 +174,8 @@ def check(level, expr): class Dimensions(Builtin): """ + <url>:WMA: https://reference.wolfram.com/language/ref/Dimensions.html</url> + <dl> <dt>'Dimensions[$expr$]' <dd>returns a list of the dimensions of the expression $expr$. @@ -207,6 +213,9 @@ def apply(self, expr, evaluation): class Dot(BinaryOperator): """ + <url>:Dot product:https://en.wikipedia.org/wiki/Dot_product</url> \ + (<url>:WMA link: https://reference.wolfram.com/language/ref/Dot.html</url>) + <dl> <dt>'Dot[$x$, $y$]' <dt>'$x$ . $y$' @@ -239,6 +248,8 @@ class Dot(BinaryOperator): class Inner(Builtin): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/Inner.html</url> + <dl> <dt>'Inner[$f$, $x$, $y$, $g$]' <dd>computes a generalised inner product of $x$ and $y$, using @@ -330,6 +341,9 @@ def summand(i): class Outer(Builtin): """ + <url>:Outer product:https://en.wikipedia.org/wiki/Outer_product</url> \ + (<url>:WMA link: https://reference.wolfram.com/language/ref/Outer.html</url>) + <dl> <dt>'Outer[$f$, $x$, $y$]' <dd>computes a generalised outer product of $x$ and $y$, using the function $f$ in place of multiplication. @@ -397,6 +411,8 @@ def rec(item, rest_lists, current): class RotationTransform(Builtin): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/RotationTransform.html</url> + <dl> <dt>'RotationTransform[$phi$]' <dd>gives a rotation by $phi$. @@ -415,6 +431,8 @@ class RotationTransform(Builtin): class ScalingTransform(Builtin): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/ScalingTransform.html</url> + <dl> <dt>'ScalingTransform[$v$]' <dd>gives a scaling transform of $v$. $v$ may be a scalar or a vector. @@ -433,6 +451,8 @@ class ScalingTransform(Builtin): class ShearingTransform(Builtin): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/ShearingTransform.html</url> + <dl> <dt>'ShearingTransform[$phi$, {1, 0}, {0, 1}]' <dd>gives a horizontal shear by the angle $phi$. @@ -453,6 +473,8 @@ class ShearingTransform(Builtin): class TransformationFunction(Builtin): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/TransformationFunction.html</url> + <dl> <dt>'TransformationFunction[$m$]' <dd>represents a transformation. @@ -474,6 +496,8 @@ class TransformationFunction(Builtin): class TranslationTransform(Builtin): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/TranslationTransform.html</url> + <dl> <dt>'TranslationTransform[$v$]' <dd>gives the translation by the vector $v$. @@ -543,6 +567,8 @@ def apply(self, m, evaluation): # are subsumed by Elements of Lists. class VectorQ(Builtin): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/VectorQ.html</url> + <dl> <dt>'VectorQ[$v$]' <dd>returns 'True' if $v$ is a list of elements which are not themselves lists. From 9cc443f63476170614517fff29d9240113078d52 Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Wed, 21 Dec 2022 19:45:00 -0300 Subject: [PATCH 066/121] fixes from Thiago's comments --- mathics/builtin/datentime.py | 7 ++++--- mathics/builtin/evaluation.py | 2 +- mathics/builtin/layout.py | 6 ++++-- mathics/builtin/logic.py | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index 014cdc515..5e8fb54d1 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -1005,7 +1005,8 @@ def evaluate(self, evaluation): class EasterSunday(Builtin): # Calendar`EasterSunday """ - <url>:WMA link:https://reference.wolfram.com/language/ref/EasterSunday.html</url> + <url>:Date of Easter:https://en.wikipedia.org/wiki/Date_of_Easter</url> \ + (<url>:WMA link:https://reference.wolfram.com/language/Calendar/ref/EasterSunday.html</url>) <dl> <dt>'EasterSunday[$year$]' @@ -1079,7 +1080,7 @@ def apply(self, n, evaluation): class SystemTimeZone(Predefined): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/SystemTimeZone.html</url> + <url>:WMA link:https://reference.wolfram.com/language/ref/$SystemTimeZone.html</url> <dl> <dt>'$SystemTimeZone' @@ -1190,7 +1191,7 @@ def apply_3(self, expr, t, failexpr, evaluation): class TimeZone(Predefined): """ - <url>:Time Zone:https://en.wikipedia.org/wiki/Time_zone</url> + <url>:Time Zone:https://en.wikipedia.org/wiki/Time_zone</url> \ (<url>:WMA link:https://reference.wolfram.com/language/ref/TimeZone.html</url>) <dl> diff --git a/mathics/builtin/evaluation.py b/mathics/builtin/evaluation.py index 078915c6d..883fd7653 100644 --- a/mathics/builtin/evaluation.py +++ b/mathics/builtin/evaluation.py @@ -10,7 +10,7 @@ class RecursionLimit(Predefined): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/RecursionLimit.html</url> + <url>:WMA link:https://reference.wolfram.com/language/ref/$RecursionLimit.html</url> <dl> <dt>'$RecursionLimit' diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index 657beec71..155ccaf19 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -172,7 +172,8 @@ class Left(Builtin): class NonAssociative(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/NonAssociative.html</url> + ## For some reason, this is a Builtin symbol in WMA, but it is not available in WR. + ## <url>:WMA link:https://reference.wolfram.com/language/ref/NonAssociative.html</url> <dl> <dt>'NonAssociative' @@ -211,7 +212,8 @@ class Postfix(BinaryOperator): class Precedence(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Precedence.html</url> + ## As NonAssociative, this is a Builtin in WMA that does not have an entry in WR. + ## <url>:WMA link:https://reference.wolfram.com/language/ref/Precedence.html</url> <dl> <dt>'Precedence[$op$]' diff --git a/mathics/builtin/logic.py b/mathics/builtin/logic.py index 6308d8285..397c6bca2 100644 --- a/mathics/builtin/logic.py +++ b/mathics/builtin/logic.py @@ -367,7 +367,7 @@ def apply(self, args, evaluation): class True_(Predefined): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/True_.html</url> + <url>:WMA link:https://reference.wolfram.com/language/ref/True.html</url> <dl> <dt>'True' From c2affaca3d83e7c9f683d2d07d3bcf9c26ee74c4 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 08:40:38 -0500 Subject: [PATCH 067/121] isort, black, shorten long lines, apply->eval --- mathics/builtin/attributes.py | 16 ++++++----- mathics/builtin/binary/io.py | 48 +++++++++++++++++--------------- mathics/builtin/binary/system.py | 9 ++++-- mathics/builtin/comparison.py | 42 +++++++++++++--------------- 4 files changed, 60 insertions(+), 55 deletions(-) diff --git a/mathics/builtin/attributes.py b/mathics/builtin/attributes.py index aedfb0ea9..e1b6ec8fc 100644 --- a/mathics/builtin/attributes.py +++ b/mathics/builtin/attributes.py @@ -14,17 +14,17 @@ """ -from mathics.builtin.base import Predefined, Builtin +from mathics.builtin.base import Builtin, Predefined from mathics.core.assignment import get_symbol_list from mathics.core.atoms import String from mathics.core.attributes import ( - attributes_bitset_to_list, - attribute_string_to_number, A_HOLD_ALL, A_HOLD_FIRST, A_LISTABLE, A_LOCKED, A_PROTECTED, + attribute_string_to_number, + attributes_bitset_to_list, ) from mathics.core.expression import Expression from mathics.core.list import ListExpression @@ -256,8 +256,8 @@ class HoldAllComplete(Predefined): <dl> <dt>'HoldAllComplete' <dd>is an attribute that includes the effects of 'HoldAll' and \ - 'SequenceHold', and also protects the function from being \ - affected by the upvalues of any arguments. + 'SequenceHold', and also protects the function from being \ + affected by the upvalues of any arguments. </dl> 'HoldAllComplete' even prevents upvalues from being used, and \ @@ -271,7 +271,8 @@ class HoldAllComplete(Predefined): = f[Sequence[a, b]] """ - summary_text = "attribute for symbols that keep unevaluated all their elements, and discards upvalues" + summary_text = "attribute for symbols that keep unevaluated all \ + their elements, and discards upvalues" class HoldFirst(Predefined): @@ -290,7 +291,8 @@ class HoldFirst(Predefined): = {HoldFirst, Protected, SequenceHold} """ - summary_text = "attribute for symbols that keep unevaluated their first element" + summary_text = "attribute for symbols that keep unevaluated their \ + first element" class HoldRest(Predefined): diff --git a/mathics/builtin/binary/io.py b/mathics/builtin/binary/io.py index 006249163..9dcd5fb41 100644 --- a/mathics/builtin/binary/io.py +++ b/mathics/builtin/binary/io.py @@ -4,29 +4,26 @@ """ import math -import mpmath import struct -import sympy - from itertools import chain +import mpmath +import sympy + from mathics.builtin.base import Builtin from mathics.core.atoms import Complex, Integer, MachineReal, Real, String from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.convert.mpmath import from_mpmath from mathics.core.expression import Expression - -from mathics.core.systemsymbols import ( - SymbolDirectedInfinity, - SymbolIndeterminate, -) - from mathics.core.number import dps from mathics.core.read import SymbolEndOfFile from mathics.core.streams import stream_manager from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import SymbolComplex - +from mathics.core.systemsymbols import ( + SymbolComplex, + SymbolDirectedInfinity, + SymbolIndeterminate, +) from mathics.eval.nevaluator import eval_N SymbolBinaryWrite = Symbol("BinaryWrite") @@ -34,7 +31,6 @@ class _BinaryFormat: """ - Container for BinaryRead readers and BinaryWrite writers """ @@ -363,14 +359,18 @@ def _UnsignedInteger128_writer(s, x): class BinaryRead(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/BinaryRead.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/BinaryRead.html</url> <dl> - <dt>'BinaryRead[$stream$]' + <dt>'BinaryRead[$stream$]' <dd>reads one byte from the stream as an integer from 0 to 255. - <dt>'BinaryRead[$stream$, $type$]' + + <dt>'BinaryRead[$stream$, $type$]' <dd>reads one object of specified type from the stream. - <dt>'BinaryRead[$stream$, {$type1$, $type2$, ...}]' + + <dt>'BinaryRead[$stream$, {$type1$, $type2$, ...}]' <dd>reads a sequence of objects of specified types. </dl> @@ -606,11 +606,11 @@ class BinaryRead(Builtin): "bfmt": "The stream `1` has been opened with BinaryFormat -> False and cannot be used with binary data.", } - def apply_empty(self, name, n, evaluation): + def eval_empty(self, name, n, evaluation): "BinaryRead[InputStream[name_, n_Integer]]" - return self.apply(name, n, None, evaluation) + return self.eval(name, n, None, evaluation) - def apply(self, name, n, typ, evaluation): + def eval(self, name, n, typ, evaluation): "BinaryRead[InputStream[name_, n_Integer], typ_]" channel = to_expression("InputStream", name, n) @@ -660,7 +660,9 @@ def apply(self, name, n, typ, evaluation): class BinaryWrite(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/BinaryWrite.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/BinaryWrite.html</url> <dl> <dt>'BinaryWrite[$channel$, $b$]' @@ -899,11 +901,11 @@ class BinaryWrite(Builtin): writers = _BinaryFormat.get_writers() - def apply_notype(self, name, n, b, evaluation): + def eval_notype(self, name, n, b, evaluation): "BinaryWrite[OutputStream[name_, n_], b_]" - return self.apply(name, n, b, None, evaluation) + return self.eval(name, n, b, None, evaluation) - def apply(self, name, n, b, typ, evaluation): + def eval(self, name, n, b, typ, evaluation): "BinaryWrite[OutputStream[name_, n_], b_, typ_]" channel = to_expression("OutputStream", name, n) diff --git a/mathics/builtin/binary/system.py b/mathics/builtin/binary/system.py index db5951aa6..4160bd7d8 100644 --- a/mathics/builtin/binary/system.py +++ b/mathics/builtin/binary/system.py @@ -11,12 +11,15 @@ class ByteOrdering(Predefined): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ByteOrdering.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ByteOrdering.html</url> <dl> <dt>'ByteOrdering' - <dd> is an option for BinaryRead, BinaryWrite, and related functions that specifies what ordering - of bytes should be assumed for your computer system.. + <dd> is an option for BinaryRead, BinaryWrite, and related functions \ + that specifies what ordering of bytes should be assumed for your \ + computer system.. </dl> X> ByteOrdering diff --git a/mathics/builtin/comparison.py b/mathics/builtin/comparison.py index 6d6a90d84..76c663a91 100644 --- a/mathics/builtin/comparison.py +++ b/mathics/builtin/comparison.py @@ -10,19 +10,12 @@ # This tells documentation how to sort this module sort_order = "mathics.builtin.testing-expressions" -from typing import Optional, Any +from typing import Any, Optional import sympy - -from mathics.builtin.base import ( - BinaryOperator, - Builtin, - SympyFunction, -) - +from mathics.builtin.base import BinaryOperator, Builtin, SympyFunction from mathics.builtin.numbers.constants import mp_convert_constant - from mathics.core.atoms import ( COMPARE_PREC, Complex, @@ -42,7 +35,6 @@ A_PROTECTED, ) from mathics.core.convert.expression import to_expression, to_numeric_args -from mathics.eval.nevaluator import eval_N from mathics.core.expression import Expression from mathics.core.number import dps from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolList, SymbolTrue @@ -55,7 +47,7 @@ SymbolMaxPrecision, SymbolSign, ) - +from mathics.eval.nevaluator import eval_N from mathics.eval.numerify import numerify operators = { @@ -201,7 +193,7 @@ def numerify_args(items, evaluation) -> list: class _ComparisonOperator(_InequalityOperator): "Compares arguments in a chain e.g. a < b < c compares a < b and b < c." - def apply(self, items, evaluation): + def eval(self, items, evaluation): "%(name)s[items___]" items_sequence = items.get_sequence() if len(items_sequence) <= 1: @@ -340,7 +332,7 @@ def equal2(self, lhs: Any, rhs: Any, max_extra_prec=None) -> Optional[bool]: return c return None - def apply(self, items, evaluation): + def eval(self, items, evaluation): "%(name)s[items___]" items_sequence = items.get_sequence() n = len(items_sequence) @@ -351,7 +343,7 @@ def apply(self, items, evaluation): for arg in items_sequence ] if not all(val is SymbolTrue for val in is_exact_vals): - return self.apply_other(items, evaluation) + return self.eval_other(items, evaluation) args = self.numerify_args(items, evaluation) for x, y in self.get_pairs(args): c = do_cplx_equal(x, y) @@ -361,7 +353,7 @@ def apply(self, items, evaluation): return SymbolFalse return SymbolTrue - def apply_other(self, args, evaluation): + def eval_other(self, args, evaluation): "%(name)s[args___?(!ExactNumberQ[#]&)]" args = args.get_sequence() @@ -388,7 +380,7 @@ class _MinMax(Builtin): A_FLAT | A_NUMERIC_FUNCTION | A_ONE_IDENTITY | A_ORDERLESS | A_PROTECTED ) - def apply(self, items, evaluation): + def eval(self, items, evaluation): "%(name)s[items___]" if hasattr(items, "flatten_with_respect_to_head"): items = items.flatten_with_respect_to_head(SymbolList) @@ -447,7 +439,9 @@ def pairs(elements): class BooleanQ(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/BooleanQ.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/BooleanQ.html</url> <dl> <dt>'BooleanQ[$expr$]' @@ -481,7 +475,9 @@ class BooleanQ(Builtin): class Equal(_EqualityOperator, _SympyComparison): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Equal.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/Equal.html</url> <dl> <dt>'Equal[$x$, $y$]' @@ -644,7 +640,9 @@ class Greater(_ComparisonOperator, _SympyComparison): class GreaterEqual(_ComparisonOperator, _SympyComparison): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/GreaterEqual.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/GreaterEqual.html</url> <dl> <dt>'GreaterEqual[$x$, $y$]' @@ -690,7 +688,7 @@ class Inequality(Builtin): } summary_text = "chain of inequalities" - def apply(self, items, evaluation): + def eval(self, items, evaluation): "Inequality[items___]" elements = numerify(items, evaluation).get_sequence() @@ -995,7 +993,7 @@ class SameQ(_ComparisonOperator): summary_text = "literal symbolic identity" - def apply_list(self, items, evaluation): + def eval_list(self, items, evaluation): "%(name)s[items___]" items_sequence = items.get_sequence() if len(items_sequence) <= 1: @@ -1145,7 +1143,7 @@ class UnsameQ(_ComparisonOperator): summary_text = "not literal symbolic identity" - def apply_list(self, items, evaluation): + def eval_list(self, items, evaluation): "%(name)s[items___]" items_sequence = items.get_sequence() if len(items_sequence) <= 1: From 26befdd96c2417ce4109f2f7c3647e957689626d Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 08:14:59 -0500 Subject: [PATCH 068/121] Remove duplicate Catalan def in Combinatorica... isort, apply->eval, and and go over long lines in builtin.itfns.combinatorial --- mathics/builtin/base.py | 7 +- mathics/builtin/intfns/combinatorial.py | 130 +++++++++++++----- .../packages/DiscreteMath/CombinatoricaV0.9.m | 7 +- 3 files changed, 104 insertions(+), 40 deletions(-) diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index cee799cc3..e520c0201 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -9,7 +9,6 @@ import sympy -from mathics.core.exceptions import MessageException from mathics.core.atoms import Integer, MachineReal, PrecisionReal, String from mathics.core.attributes import A_NO_ATTRIBUTES, A_PROTECTED, A_READ_PROTECTED from mathics.core.convert.expression import to_expression, to_numeric_sympy_args @@ -18,6 +17,7 @@ from mathics.core.convert.sympy import from_sympy from mathics.core.definitions import Definition from mathics.core.element import BoxElementMixin +from mathics.core.exceptions import MessageException from mathics.core.expression import Expression, SymbolDefault from mathics.core.list import ListExpression from mathics.core.number import PrecisionValueError, get_precision @@ -659,7 +659,7 @@ def run_sympy(sympy_fn: Callable, *sympy_args) -> Any: class SympyFunction(SympyObject): - def apply(self, z, evaluation): + def eval(self, z, evaluation): # Note: we omit a docstring here, so as not to confuse # function signature collector ``contribute``. @@ -675,6 +675,9 @@ def apply(self, z, evaluation): except: return + # FIXME: remove after all apply->eval conversions have been done + apply = eval + def get_constant(self, precision, evaluation, have_mpmath=False): try: d = get_precision(precision, evaluation) diff --git a/mathics/builtin/intfns/combinatorial.py b/mathics/builtin/intfns/combinatorial.py index 9bbe84c85..6701b0b08 100644 --- a/mathics/builtin/intfns/combinatorial.py +++ b/mathics/builtin/intfns/combinatorial.py @@ -2,15 +2,20 @@ """ Combinatorial Functions -<url>:Combinatorics: https://en.wikipedia.org/wiki/Combinatorics</url> is an area of mathematics primarily concerned with counting, both as a means and an end in obtaining results, and certain properties of finite structures. +<url>:Combinatorics: https://en.wikipedia.org/wiki/Combinatorics</url> is an \ +area of mathematics primarily concerned with counting, both as a means and an \ +end in obtaining results, and certain properties of finite structures. -It is closely related to many other areas of Mathematics and has many applications ranging from logic to statistical physics, from evolutionary biology to computer science, etc. +It is closely related to many other areas of Mathematics and has many \ +applications ranging from logic to statistical physics, from evolutionary \ +biology to computer science, etc. """ +from itertools import combinations + from mathics.builtin.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin, SympyFunction - from mathics.core.atoms import Integer from mathics.core.attributes import ( A_LISTABLE, @@ -30,7 +35,6 @@ SymbolTimes, SymbolTrue, ) -from itertools import combinations SymbolBinomial = Symbol("Binomial") SymbolSubsets = Symbol("Subsets") @@ -62,7 +66,7 @@ def generate(): except _NoBoolVector: return None - def apply(self, u, v, evaluation): + def eval(self, u, v, evaluation): "%(name)s[u_List, v_List]" if len(u.elements) != len(v.elements): return @@ -85,7 +89,13 @@ class _NoBoolVector(Exception): class Binomial(_MPMathFunction): """ - <url>:Binomial Coefficient: https://en.wikipedia.org/wiki/Binomial_coefficient</url> (<url>:SymPy: https://docs.sympy.org/latest/modules/functions/combinatorial.html#binomial</url>, <url>:WMA: https://reference.wolfram.com/language/ref/Binomial.html</url>) + <url> + :Binomial Coefficient: + https://en.wikipedia.org/wiki/Binomial_coefficient</url> (<url> + :SymPy: + https://docs.sympy.org/latest/modules/functions/combinatorial.html#binomial</url>, <url> + :WMA: + https://reference.wolfram.com/language/ref/Binomial.html</url>) <dl> <dt>'Binomial[$n$, $k$]' @@ -120,7 +130,14 @@ class Binomial(_MPMathFunction): class CatalanNumber(SympyFunction): """ - <url>:Catalan Number: https://en.wikipedia.org/wiki/Catalan_number</url> (<url>:SymPy: https://docs.sympy.org/latest/modules/functions/combinatorial.html#sympy.functions.combinatorial.numbers.catalan</url>, <url>:WMA: https://reference.wolfram.com/language/ref/CatalanNumber.html</url>) + <url> + :Catalan Number: + https://en.wikipedia.org/wiki/Catalan_number</url> (<url> + :SymPy: + https://docs.sympy.org/latest/modules/functions/combinatorial.html#sympy.functions.combinatorial.numbers.catalan</url>, \ + <url> + :WMA: + https://reference.wolfram.com/language/ref/CatalanNumber.html</url>) <dl> <dt>'CatalanNumber[$n$]' @@ -139,19 +156,25 @@ class CatalanNumber(SympyFunction): # We (and sympy) do not handle fractions or other non-integers # right now. - def apply_integer(self, n: Integer, evaluation): + def eval_integer(self, n: Integer, evaluation): "CatalanNumber[n_Integer]" - return self.apply(n, evaluation) + return self.eval(n, evaluation) class DiceDissimilarity(_BooleanDissimilarity): r""" - <url>:Sørensen–Dice coefficient: https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient</url> (<url>:Sympy: https://docs.scipy.org/doc/scipy/search.html</url>, <url>:DiceDissimilarity: https://reference.wolfram.com/language/ref/DiceDissimilarity.html</url>) + <url> + :Sørensen–Dice coefficient: + https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient</url> (<url> + :Sympy: + https://docs.scipy.org/doc/scipy/search.html</url>, <url> + :DiceDissimilarity: + https://reference.wolfram.com/language/ref/DiceDissimilarity.html</url>) <dl> <dt>'DiceDissimilarity[$u$, $v$]' - <dd>returns the Dice dissimilarity between the two boolean 1-D lists $u$ and $v$, - which is defined as (c_tf + c_ft) / (2 * c_tt + c_ft + c_tf), where $n$ is len($u$) and c_ij is - the number of occurrences of $u$[k]=i and $v$[k]=j for $k$ < $n$. + <dd>returns the Dice dissimilarity between the two boolean 1-D lists $u$ and $v$. + This is defined as ($c_tf$ + $c_ft$) / (2 * $c_tt$ + $c_ft$ + c_tf). + $n$ is len($u$) and $c_ij$ is the number of occurrences of $u$[$k$]=$i$ and $v$[$k$]=$j$ for $k$ < $n$. </dl> >> DiceDissimilarity[{1, 0, 1, 1, 0, 1, 1}, {0, 1, 1, 0, 0, 0, 1}] @@ -168,10 +191,20 @@ def _compute(self, n, c_ff, c_ft, c_tf, c_tt): class JaccardDissimilarity(_BooleanDissimilarity): """ - <url>:Jaccard index: https://en.wikipedia.org/wiki/Jaccard_index</url> (<url>:SciPy: https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.jaccard.html</url>, <url>:WMA: https://reference.wolfram.com/language/ref/JaccardDissimilarity.html</url>) + <url> + :Jaccard index: + https://en.wikipedia.org/wiki/Jaccard_index</url> (<url> + :SciPy: + https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.jaccard.html</url>, <url> + :WMA: + https://reference.wolfram.com/language/ref/JaccardDissimilarity.html</url>) <dl> <dt>'JaccardDissimilarity[$u$, $v$]' - <dd>returns the Jaccard-Needham dissimilarity between the two boolean 1-D lists $u$ and $v$, which is defined as (c_tf + c_ft) / (c_tt + c_ft + c_tf), where $n$ is len($u$) and c_ij is the number of occurrences of $u$[k]=i and $v$[k]=j for $k$ < $n$. + <dd>returns the Jaccard-Needham dissimilarity between the two boolean \ + 1-D lists $u$ and $v$, which is defined as \ + ($c_tf$ + $c_ft$) / ($c_tt$ + $c_ft$ + $c_tf$), where $n$ is \ + len($u$) and $c_ij$ is the number of occurrences of \ + $u$[$k$]=$i$ and $v$[$k$]=$j$ for $k$ < $n$. </dl> >> JaccardDissimilarity[{1, 0, 1, 1, 0, 1, 1}, {0, 1, 1, 0, 0, 0, 1}] @@ -193,7 +226,10 @@ class MatchingDissimilarity(_BooleanDissimilarity): <dl> <dt>'MatchingDissimilarity[$u$, $v$]' - <dd>returns the Matching dissimilarity between the two boolean 1-D lists $u$ and $v$, which is defined as (c_tf + c_ft) / $n$, where $n$ is len($u$) and c_ij is the number of occurrences of $u$[$k$]=$i$ and $v$[k]=$j$ for $k$ < $n$. + <dd>returns the Matching dissimilarity between the two boolean \ + 1-D lists $u$ and $v$, which is defined as ($c_tf$ + $c_ft$) / $n$, \ + where $n$ is len($u$) and $c_ij$ is the number of occurrences of \ + $u$[$k$]=$i$ and $v$[$k$]=$j$ for $k$ < $n$. </dl> >> MatchingDissimilarity[{1, 0, 1, 1, 0, 1, 1}, {0, 1, 1, 0, 0, 0, 1}] @@ -208,7 +244,10 @@ def _compute(self, n, c_ff, c_ft, c_tf, c_tt): class Multinomial(Builtin): """ - <url>:Multinomial distribution: https://en.wikipedia.org/wiki/Multinomial_distribution</url> (<url>:WMA: https://reference.wolfram.com/language/ref/Multinomial.html</url>) + <url> + :Multinomial distribution: + https://en.wikipedia.org/wiki/Multinomial_distribution</url> (<url>\ + :WMA: https://reference.wolfram.com/language/ref/Multinomial.html</url>) <dl> <dt>'Multinomial[$n1$, $n2$, ...]' <dd>gives the multinomial coefficient '($n1$+$n2$+...)!/($n1$!$n2$!...)'. @@ -229,7 +268,7 @@ class Multinomial(Builtin): attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_ORDERLESS | A_PROTECTED summary_text = "multinomial coefficients" - def apply(self, values, evaluation): + def eval(self, values, evaluation): "Multinomial[values___]" values = values.get_sequence() @@ -245,13 +284,17 @@ def apply(self, values, evaluation): class RogersTanimotoDissimilarity(_BooleanDissimilarity): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/RogersTanimotoDissimilarity.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/RogersTanimotoDissimilarity.html</url> <dl> <dt>'RogersTanimotoDissimilarity[$u$, $v$]' - <dd>returns the Rogers-Tanimoto dissimilarity between the two boolean 1-D lists $u$ and $v$, - which is defined as $R$ / (c_tt + c_ff + $R$) where $n$ is len($u$), c_ij is - the number of occurrences of $u$[$k$]=$i$ and $v$[$k]$=$j$ for $k$<n, and $R$ = 2 * (c_tf + c_ft). + <dd>returns the Rogers-Tanimoto dissimilarity between the two boolean \ + 1-D lists $u$ and $v$, which is defined as \ + $R$ / (c_tt + c_ff + $R$) where $n$ is len($u$), c_ij is \ + the number of occurrences of $u$[$k$]=$i$ and $v$[$k]$=$j$ for $k$<n, \ + and $R$ = 2 * ($c_tf$ + $c_ft$). </dl> >> RogersTanimotoDissimilarity[{1, 0, 1, 1, 0, 1, 1}, {0, 1, 1, 0, 0, 0, 1}] @@ -267,13 +310,16 @@ def _compute(self, n, c_ff, c_ft, c_tf, c_tt): class RussellRaoDissimilarity(_BooleanDissimilarity): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/RusselRaoDissimilarity.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/RusselRaoDissimilarity.html</url> <dl> <dt>'RussellRaoDissimilarity[$u$, $v$]' - <dd>returns the Russell-Rao dissimilarity between the two boolean 1-D lists $u$ and $v$, - which is defined as (n - c_tt) / c_tt where n is len($u$) and c_ij is - the number of occurrences of $u$[k]=i and $v$[k]=j for k<n. + <dd>returns the Russell-Rao dissimilarity between the two boolean \ + 1-D lists $u$ and $v$, which is defined as ($n$ - $c_tt$) / $c_tt$ \ + where $n$ is len($u$) and $c_ij$ is \ + the number of occurrences of $u$[k]=i and $v$[$k$]=$j$ for $k$ < $n$. </dl> >> RussellRaoDissimilarity[{1, 0, 1, 1, 0, 1, 1}, {0, 1, 1, 0, 0, 0, 1}] @@ -288,12 +334,17 @@ def _compute(self, n, c_ff, c_ft, c_tf, c_tt): class SokalSneathDissimilarity(_BooleanDissimilarity): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/SokalSneathDissimilarity.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/SokalSneathDissimilarity.html</url> <dl> <dt>'SokalSneathDissimilarity[$u$, $v$]' - <dd>returns the Sokal-Sneath dissimilarity between the two boolean 1-D lists $u$ and $v$, - which is defined as $R$ / (c_tt + $R$) where $n$ is len($u$), c_ij is the number of occurrences of $u$[$k$]=$i$ and $v$[k]=$j$ for $k$ < $n$, and R = 2 * (c_tf + c_ft). + <dd>returns the Sokal-Sneath dissimilarity between the two boolean \ + 1-D lists $u$ and $v$, which is defined as $R$ / (c_tt + $R$) where \ + $n$ is len($u$), $c_ij$ is the number of occurrences of \ + $u$[$k$]=$i$ and $v$[k]=$j$ for $k$ < $n$, \ + and $R$ = 2 * ($c_tf$ + $c_ft$). </dl> >> SokalSneathDissimilarity[{1, 0, 1, 1, 0, 1, 1}, {0, 1, 1, 0, 0, 0, 1}] @@ -322,7 +373,8 @@ class Subsets(Builtin): <dd>finds a list of all possible subsets containing exactly $n$ elements. <dt>'Subsets[$list$, {$min$, $max$}]' - <dd>finds a list of all possible subsets containing between $min$ and $max$ elements. + <dd>finds a list of all possible subsets containing between $min$ and \ + $max$ elements. <dt>'Subsets[$list$, $spec$, $n$]' <dd>finds a list of the first $n$ possible subsets. @@ -451,16 +503,16 @@ class Subsets(Builtin): summary_text = "list all the subsets" - def apply_list(self, list, evaluation): + def eval_list(self, list, evaluation): "Subsets[list_]" return ( evaluation.message("Subsets", "normal", Expression(SymbolSubsets, list)) if isinstance(list, Atom) - else self.apply_list_n(list, Integer(len(list.elements)), evaluation) + else self.eval_list_n(list, Integer(len(list.elements)), evaluation) ) - def apply_list_n(self, list, n, evaluation): + def eval_list_n(self, list, n, evaluation): "Subsets[list_, n_]" expr = Expression(SymbolSubsets, list, n) @@ -483,7 +535,7 @@ def apply_list_n(self, list, n, evaluation): return ListExpression(*nested_list) - def apply_list_pattern(self, list, n, evaluation): + def eval_list_pattern(self, list, n, evaluation): "Subsets[list_, Pattern[n,_List|All|DirectedInfinity[1]]]" expr = Expression(SymbolSubsets, list, n) @@ -493,7 +545,7 @@ def apply_list_pattern(self, list, n, evaluation): else: head_t = list.head if n.get_name() == "System`All" or n.has_form("DirectedInfinity", 1): - return self.apply_list(list, evaluation) + return self.eval_list(list, evaluation) n_len = len(n.elements) @@ -557,7 +609,7 @@ def apply_list_pattern(self, list, n, evaluation): return ListExpression(*nested_list) - def apply_atom_pattern(self, list, n, spec, evaluation): + def eval_atom_pattern(self, list, n, spec, evaluation): "Subsets[list_?AtomQ, Pattern[n,_List|All|DirectedInfinity[1]], spec_]" return evaluation.message( @@ -571,7 +623,11 @@ class YuleDissimilarity(_BooleanDissimilarity): <dl> <dt>'YuleDissimilarity[$u$, $v$]' - <dd>returns the Yule dissimilarity between the two boolean 1-D lists $u$ and $v$, which is defined as R / (c_tt * c_ff + R / 2) where n is len($u$), c_ij is the number of occurrences of $u$[k]=i and $v$[k]=j for $k$<$n$, and $R$ = 2 * c_tf * c_ft. + <dd>returns the Yule dissimilarity between the two boolean 1-D lists $u$ \ + and $v$, which is defined as $R$ / ($c_tt$ * $c_ff$ + $R$ / 2) \ + where $n$ is len($u$), $c_ij$ is the number of occurrences of \ + $u$[$k$]=$i$ and $v$[$k$]=$j$ for $k$<$n$, \ + and $R$ = 2 * $c_tf$ * $c_ft$. </dl> >> YuleDissimilarity[{1, 0, 1, 1, 0, 1, 1}, {0, 1, 1, 0, 0, 0, 1}] diff --git a/mathics/packages/DiscreteMath/CombinatoricaV0.9.m b/mathics/packages/DiscreteMath/CombinatoricaV0.9.m index 2c713d7f4..eda004224 100644 --- a/mathics/packages/DiscreteMath/CombinatoricaV0.9.m +++ b/mathics/packages/DiscreteMath/CombinatoricaV0.9.m @@ -1365,7 +1365,12 @@ NumberOfTableaux[n_Integer] := Apply[Plus, Map[NumberOfTableaux, Partitions[n]]] -CatalanNumber[n_] := Binomial[2n,n]/(n+1) /; (n>=0) + +(* Mathics now provides CatalanNumber via SymPy. And + there is something broken with respect to Context that + would cause this definition to interfere with the Builtin definition. + *) +(* CatalanNumber[n_] := Binomial[2n,n]/(n+1) /; (n>=0) *) RandomTableau[shape_List] := Module[{i=j=n=Apply[Plus,shape],done,l,m,h=1,k,y,p=shape}, From 1e7806422e4b38b8a0a9a934c4a4a1b034cb37e7 Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Wed, 21 Dec 2022 06:03:55 -0300 Subject: [PATCH 069/121] docstr url for files_io, intfns, list and trace --- mathics/builtin/files_io/files.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index eea507877..f693d6476 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -183,8 +183,12 @@ def eval_path(self, path, evaluation, options): class Character(Builtin): """ +<<<<<<< HEAD <url>:WMA link: https://reference.wolfram.com/language/ref/Character.html</url> +======= + <url>:WMA link:https://reference.wolfram.com/language/ref/Character.html</url> +>>>>>>> f6040b82 (docstr url for files_io, intfns, list and trace) <dl> <dt>'Character' @@ -482,9 +486,13 @@ def eval_default(self, filename, evaluation): class InputFileName_(Predefined): """ +<<<<<<< HEAD <url> :WMA link: https://reference.wolfram.com/language/ref/$InputFileName.html</url> +======= + <url>:WMA link:https://reference.wolfram.com/language/ref/$InputFileName.html</url> +>>>>>>> f6040b82 (docstr url for files_io, intfns, list and trace) <dl> <dt>'$InputFileName' @@ -526,9 +534,13 @@ class InputStream(Builtin): class OpenRead(_OpenAction): """ +<<<<<<< HEAD <url> :WMA link: https://reference.wolfram.com/language/ref/OpenRead.html</url> +======= + <url>:WMA link:https://reference.wolfram.com/language/ref/OpenRead.html</url> +>>>>>>> f6040b82 (docstr url for files_io, intfns, list and trace) <dl> <dt>'OpenRead["file"]' @@ -719,9 +731,13 @@ def eval_default(self, exprs, filename, evaluation): class PutAppend(BinaryOperator): """ +<<<<<<< HEAD <url> :WMA link: https://reference.wolfram.com/language/ref/PutAppend.html</url> +======= + <url>:WMA link:https://reference.wolfram.com/language/ref/PutAppend.html</url> +>>>>>>> f6040b82 (docstr url for files_io, intfns, list and trace) <dl> <dt>'$expr$ >>> $filename$' From 3fd8caa45eaeaa13f9581f906de8b5aeab4170ba Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Wed, 21 Dec 2022 07:16:28 -0300 Subject: [PATCH 070/121] docstr url for logic, optiondoc, sparse, tensor layout, evaluation, logic, comparison, datentime, attributes and binary --- mathics/builtin/binary/io.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mathics/builtin/binary/io.py b/mathics/builtin/binary/io.py index 9dcd5fb41..82d13a360 100644 --- a/mathics/builtin/binary/io.py +++ b/mathics/builtin/binary/io.py @@ -31,6 +31,7 @@ class _BinaryFormat: """ + Container for BinaryRead readers and BinaryWrite writers """ @@ -359,9 +360,13 @@ def _UnsignedInteger128_writer(s, x): class BinaryRead(Builtin): """ +<<<<<<< HEAD <url> :WMA link: https://reference.wolfram.com/language/ref/BinaryRead.html</url> +======= + <url>:WMA link:https://reference.wolfram.com/language/ref/BinaryRead.html</url> +>>>>>>> ee219dad (docstr url for logic, optiondoc, sparse, tensor layout, evaluation, logic, comparison, datentime, attributes and binary) <dl> <dt>'BinaryRead[$stream$]' From aaa69478f412ec73eed6513376fde3942e5bb282 Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Wed, 21 Dec 2022 07:23:39 -0300 Subject: [PATCH 071/121] another bunch of docstring urls --- mathics/builtin/compilation.py | 6 +++- mathics/builtin/compress.py | 4 +++ mathics/builtin/graphics.py | 61 +++++++++++++++++++++++++++++++++ mathics/builtin/inout.py | 4 +++ mathics/builtin/manipulate.py | 2 ++ mathics/builtin/messages.py | 16 +++++++++ mathics/builtin/optimization.py | 4 +++ mathics/builtin/options.py | 8 +++++ mathics/builtin/patterns.py | 55 +++++++++++++++++++++++++++++ mathics/builtin/physchemdata.py | 2 ++ mathics/builtin/quantities.py | 12 ++++++- mathics/builtin/recurrence.py | 2 ++ mathics/builtin/scoping.py | 25 +++++++++++++- mathics/builtin/structure.py | 26 ++++++++++++++ mathics/builtin/system.py | 48 ++++++++++++++++++++++++++ 15 files changed, 272 insertions(+), 3 deletions(-) mode change 100755 => 100644 mathics/builtin/manipulate.py diff --git a/mathics/builtin/compilation.py b/mathics/builtin/compilation.py index 5783fe86c..57e6daa9b 100644 --- a/mathics/builtin/compilation.py +++ b/mathics/builtin/compilation.py @@ -36,6 +36,8 @@ class Compile(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Compile.html</url> + <dl> <dt>'Compile[{$x1$, $x2$, ...}, $expr$]' <dd>Compiles $expr$ assuming each $xi$ is a $Real$ number. @@ -182,7 +184,9 @@ def atom_to_boxes(self, f, evaluation): class CompiledFunction(Builtin): - """' + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/CompiledFunction.html</url> + <dl> <dt>'CompiledFunction[$args$...]' <dd>represents compiled code for evaluating a compiled function. diff --git a/mathics/builtin/compress.py b/mathics/builtin/compress.py index 69b6868aa..ac8da73e9 100644 --- a/mathics/builtin/compress.py +++ b/mathics/builtin/compress.py @@ -10,6 +10,8 @@ class Compress(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Compress.html</url> + <dl> <dt>'Compress[$expr$]' <dd>gives a compressed string representation of $expr$. @@ -45,6 +47,8 @@ def apply(self, expr, evaluation, options): class Uncompress(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Uncompress.html</url> + <dl> <dt>'Uncompress["$string$"]' <dd>recovers an expression from a string generated by 'Compress'. diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 41380562c..fd943c2d3 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -185,6 +185,9 @@ def _extract_graphics(graphics, format, evaluation): class Show(Builtin): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/Show.html</url> + <dl> <dt>'Show[$graphics$, $options$]' <dd>shows a list of graphics with the specified options added. @@ -224,6 +227,8 @@ def eval(self, graphics, evaluation, options): class Graphics(Builtin): r""" + <url>:WMA link:https://reference.wolfram.com/language/ref/Graphics.html</url> + <dl> <dt>'Graphics[$primitives$, $options$]' <dd>represents a graphic. @@ -395,6 +400,8 @@ class _Thickness(_Size): class AbsoluteThickness(_Thickness): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/AbsoluteThickness.html</url> + <dl> <dt>'AbsoluteThickness[$p$]' <dd>sets the line thickness for subsequent graphics primitives to $p$ points. @@ -412,6 +419,8 @@ def get_thickness(self): class Point(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Point.html</url> + <dl> <dt>'Point[{$point_1$, $point_2$ ...}]' <dd>represents the point primitive. @@ -441,6 +450,8 @@ class Point(Builtin): class PointSize(_Size): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/PointSize.html</url> + <dl> <dt>'PointSize[$t$]' <dd>sets the diameter of points to $t$, which is relative to the overall width. @@ -468,6 +479,9 @@ def get_absolute_size(self): # is kind of wrong. class Line(Builtin): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/Line.html</url> + <dl> <dt>'Line[{$point_1$, $point_2$ ...}]' <dd>represents the line primitive. @@ -520,6 +534,9 @@ def path(max_degree, p): class FilledCurve(Builtin): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/FilledCurve.html</url> + <dl> <dt>'FilledCurve[{$segment1$, $segment2$ ...}]' <dd>represents a filled curve. @@ -537,6 +554,8 @@ class FilledCurve(Builtin): class Polygon(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Polygon.html</url> + <dl> <dt>'Polygon[{$point_1$, $point_2$ ...}]' <dd>represents the filled polygon primitive. @@ -564,6 +583,9 @@ class Polygon(Builtin): class RegularPolygon(Builtin): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/RegularPolygon.html</url> + <dl> <dt>'RegularPolygon[$n$]' <dd>gives the regular polygon with $n$ edges. @@ -587,6 +609,8 @@ class RegularPolygon(Builtin): class Arrow(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Arrow.html</url> + <dl> <dt>'Arrow[{$p1$, $p2$}]' <dd>represents a line from $p1$ to $p2$ that ends with an arrow at $p2$. @@ -623,6 +647,9 @@ class Arrow(Builtin): class Arrowheads(_GraphicsDirective): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/Arrowheads.html</url> + <dl> <dt>'Arrowheads[$s$]' <dd>specifies that Arrow[] draws one arrow of size $s$ (relative to width of image, defaults to 0.04). @@ -1150,6 +1177,8 @@ def set_size( class Circle(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Circle.html</url> + <dl> <dt>'Circle[{$cx$, $cy$}, $r$]' <dd>draws a circle with center '($cx$, $cy$)' and radius $r$. @@ -1180,6 +1209,8 @@ class Circle(Builtin): class Disk(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Disk.html</url> + <dl> <dt>'Disk[{$cx$, $cy$}, $r$]' <dd>fills a circle with center '($cx$, $cy$)' and radius $r$. @@ -1216,6 +1247,8 @@ class Disk(Builtin): class Directive(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Directive.html</url> + <dl> <dt> 'Directive'[$g_1$, $g_2$, ...] <dd> represents a single graphics directive composed of the directives $g_1$, $g_2$, ... @@ -1228,6 +1261,8 @@ class Directive(Builtin): class EdgeForm(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/EdgeForm.html</url> + <dl> <dt> 'EdgeForm[$g$]' <dd> is a graphics directive that specifies that edges of filled graphics objects are to be drawn using the graphics directive or list of directives $g$. @@ -1244,6 +1279,8 @@ class EdgeForm(Builtin): class FaceForm(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/FaceForm.html</url> + <dl> <dt> 'FaceForm[$g$]' <dd> is a graphics directive that specifies that faces of filled graphics objects are to be drawn using the graphics directive or list of directives $ g$. @@ -1255,6 +1292,8 @@ class FaceForm(Builtin): class FontColor(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/FontColor.html</url> + <dl> <dt>'FontColor' <dd>is an option for Style to set the font color. @@ -1266,6 +1305,8 @@ class FontColor(Builtin): class Inset(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Inset.html</url> + <dl> <dt>'Text[$obj$]' <dd>represents an object $obj$ inset in a graphic. @@ -1284,6 +1325,8 @@ class Inset(Builtin): class Large(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Large.html</url> + <dl> <dt>'ImageSize' -> 'Large' <dd>produces a large image. @@ -1295,6 +1338,8 @@ class Large(Builtin): class Medium(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Medium.html</url> + <dl> <dt>'ImageSize' -> 'Medium' <dd>produces a medium-sized image. @@ -1306,6 +1351,8 @@ class Medium(Builtin): class Offset(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Offset.html</url> + <dl> <dt>'Offset[{$dx$, $dy$}, $position$]' <dd>gives the position of a graphical object obtained by starting at the specified $position$ and then moving by absolute offset {$dx$,$dy$}. @@ -1317,6 +1364,8 @@ class Offset(Builtin): class Rectangle(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Rectangle.html</url> + <dl> <dt>'Rectangle[{$xmin$, $ymin$}]' <dd>represents a unit square with bottom-left corner at {$xmin$, $ymin$}. @@ -1338,6 +1387,8 @@ class Rectangle(Builtin): class Small(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Small.html</url> + <dl> <dt>'ImageSize' -> 'Small' <dd>produces a small image. @@ -1349,6 +1400,8 @@ class Small(Builtin): class Text(Inset): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Text.html</url> + <dl> <dt>'Text["$text$", {$x$, $y$}]' <dd>draws $text$ centered on position '{$x$, $y$}'. @@ -1366,6 +1419,8 @@ class Text(Inset): class Thick(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Thick.html</url> + <dl> <dt>'Thick' <dd>sets the line width for subsequent graphics primitives to 2pt. @@ -1378,6 +1433,8 @@ class Thick(Builtin): class Thin(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Thin.html</url> + <dl> <dt>'Thin' <dd>sets the line width for subsequent graphics primitives to 0.5pt. @@ -1390,6 +1447,8 @@ class Thin(Builtin): class Thickness(_Thickness): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Thickness.html</url> + <dl> <dt>'Thickness[$t$]' <dd>sets the line thickness for subsequent graphics primitives to $t$ times the size of the plot area. @@ -1407,6 +1466,8 @@ def get_thickness(self): class Tiny(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Tiny.html</url> + <dl> <dt>'ImageSize' -> 'Tiny' <dd>produces a tiny image. diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index 111da47d6..3efbd1f2a 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -30,6 +30,8 @@ class Echo_(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Echo_.html</url> + <dl> <dt>'$Echo' <dd>gives a list of files and pipes to which all input is echoed. @@ -45,6 +47,8 @@ class Echo_(Predefined): class Print(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Print.html</url> + <dl> <dt>'Print[$expr$, ...]' <dd>prints each $expr$ in string form. diff --git a/mathics/builtin/manipulate.py b/mathics/builtin/manipulate.py old mode 100755 new mode 100644 index 7fdd1c8bf..7c3b8dfeb --- a/mathics/builtin/manipulate.py +++ b/mathics/builtin/manipulate.py @@ -241,6 +241,8 @@ def display_data(self, result): class Manipulate(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Manipulate.html</url> + <dl> <dt>'Manipulate[$expr1$, {$u$, $u_min$, $u_max$}]' <dd>interactively compute and display an expression with different values of $u$. diff --git a/mathics/builtin/messages.py b/mathics/builtin/messages.py index 5bf63c56d..4549a8670 100644 --- a/mathics/builtin/messages.py +++ b/mathics/builtin/messages.py @@ -34,6 +34,8 @@ class Message(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Message.html</url> + <dl> <dt>'Message[$symbol$::$msg$, $expr1$, $expr2$, ...]' <dd>displays the specified message, replacing placeholders in @@ -75,6 +77,8 @@ def check_message(expr) -> bool: class Check(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Check.html</url> + <dl> <dt>'Check[$expr$, $failexpr$]' <dd>evaluates $expr$, and returns the result, unless messages were generated, in which case it evaluates and $failexpr$ will be returned. @@ -207,6 +211,8 @@ def get_msg_list(exprs): class Quiet(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Quiet.html</url> + <dl> <dt>'Quiet[$expr$, {$s1$::$t1$, ...}]' <dd>evaluates $expr$, without messages '{$s1$::$t1$, ...}' being displayed. @@ -335,6 +341,8 @@ def get_msg_list(expr): class Off(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Off.html</url> + <dl> <dt>'Off[$symbol$::$tag$]' <dd>turns a message off so it is no longer printed. @@ -382,6 +390,8 @@ def apply(self, expr, evaluation): class On(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/On.html</url> + <dl> <dt>'On[$symbol$::$tag$]' <dd>turns a message on for printing. @@ -429,6 +439,8 @@ def apply(self, expr, evaluation): class MessageName(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/MessageName.html</url> + <dl> <dt>'MessageName[$symbol$, $tag$]' <dt>'$symbol$::$tag$' @@ -472,6 +484,8 @@ def apply(self, symbol, tag, evaluation): class Syntax(Builtin): r""" + <url>:WMA link:https://reference.wolfram.com/language/ref/Syntax.html</url> + <dl> <dt>'Syntax' <dd>is a symbol to which all syntax messages are assigned. @@ -576,6 +590,8 @@ class Syntax(Builtin): class General(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/General.html</url> + <dl> <dt>'General' <dd>is a symbol to which all general-purpose messages are assigned. diff --git a/mathics/builtin/optimization.py b/mathics/builtin/optimization.py index 5ef3cae7f..52e6c2f24 100644 --- a/mathics/builtin/optimization.py +++ b/mathics/builtin/optimization.py @@ -29,6 +29,8 @@ class Maximize(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Maximize.html</url> + <dl> <dt>'Maximize[$f$, $x$]' <dd>compute the maximum of $f$ respect $x$ that change between $a$ and $b$ @@ -85,6 +87,8 @@ def apply_constraints(self, f, vars, evaluation): class Minimize(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Minimize.html</url> + <dl> <dt>'Minimize[$f$, $x$]' <dd>compute the minimum of $f$ respect $x$ that change between $a$ and $b$ diff --git a/mathics/builtin/options.py b/mathics/builtin/options.py index 9df8bee11..d7b716c59 100644 --- a/mathics/builtin/options.py +++ b/mathics/builtin/options.py @@ -33,6 +33,8 @@ class Default(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Default.html</url> + <url> :WMA link: https://reference.wolfram.com/language/ref/Default.html</url> @@ -134,6 +136,8 @@ def matched(): # Has this been removed from WL? I cannot find a WMA link. class NotOptionQ(Test): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/NotOptionQ.html</url> + <dl> <dt>'NotOptionQ[$expr$]' <dd>returns 'True' if $expr$ does not have the form of a valid \ @@ -168,6 +172,8 @@ def test(self, expr): # Has this been removed from WL? I cannot find a WMA link. class OptionQ(Test): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/OptionQ.html</url> + <dl> <dt>'OptionQ[$expr$]' <dd>returns 'True' if $expr$ has the form of a valid option \ @@ -304,6 +310,8 @@ def eval(self, f, evaluation): class OptionValue(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/OptionValue.html</url> + <url> :WMA link: https://reference.wolfram.com/language/ref/OptionValue.html</url> diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index f38f8757e..b881328d4 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -84,6 +84,9 @@ class Rule_(BinaryOperator): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/Rule_.html</url> + <dl> <dt>'Rule[$x$, $y$]' <dt>'$x$ -> $y$' @@ -115,6 +118,8 @@ class Rule_(BinaryOperator): class RuleDelayed(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/RuleDelayed.html</url> + <dl> <dt>'RuleDelayed[$x$, $y$]' <dt>'$x$ :> $y$' @@ -192,6 +197,9 @@ def create_rules(rules_expr, expr, name, evaluation, extra_args=[]): class Replace(Builtin): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/Replace.html</url> + <dl> <dt>'Replace[$expr$, $x$ -> $y$]' <dd>yields the result of replacing $expr$ with $y$ if it @@ -276,6 +284,8 @@ def apply_levelspec(self, expr, rules, ls, evaluation, options): class ReplaceAll(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ReplaceAll.html</url> + <dl> <dt>'ReplaceAll[$expr$, $x$ -> $y$]' <dt>'$expr$ /. $x$ -> $y$' @@ -344,6 +354,9 @@ def apply(self, expr, rules, evaluation): class ReplaceRepeated(BinaryOperator): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/ReplaceRepeated.html</url> + <dl> <dt>'ReplaceRepeated[$expr$, $x$ -> $y$]' <dt>'$expr$ //. $x$ -> $y$' @@ -422,6 +435,8 @@ def apply_list(self, expr, rules, evaluation, options): class ReplaceList(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ReplaceList.html</url> + <dl> <dt>'ReplaceList[$expr$, $rules$]' <dd>returns a list of all possible results of applying $rules$ @@ -489,6 +504,9 @@ def apply(self, expr, rules, max, evaluation): class PatternTest(BinaryOperator, PatternObject): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/PatternTest.html</url> + <dl> <dt>'PatternTest[$pattern$, $test$]' <dt>'$pattern$ ? $test$' @@ -702,6 +720,8 @@ def get_match_count(self, vars={}): class Alternatives(BinaryOperator, PatternObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Alternatives.html</url> + <dl> <dt>'Alternatives[$p1$, $p2$, ..., $p_i$]' <dt>'$p1$ | $p2$ | ... | $p_i$' @@ -757,6 +777,8 @@ class _StopGeneratorExcept(StopGenerator): class Except(PatternObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Except.html</url> + <dl> <dt>'Except[$c$]' <dd>represents a pattern object that matches any expression except those matching $c$. @@ -829,6 +851,9 @@ def match(expr, form, evaluation): class MatchQ(Builtin): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/MatchQ.html</url> + <dl> <dt>'MatchQ[$expr$, $form$]' <dd>tests whether $expr$ matches $form$. @@ -862,6 +887,8 @@ def apply(self, expr, form, evaluation): class Verbatim(PatternObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Verbatim.html</url> + <dl> <dt>'Verbatim[$expr$]' <dd>prevents pattern constructs in $expr$ from taking effect, @@ -893,6 +920,9 @@ def match(self, yield_func, expression, vars, evaluation, **kwargs): class HoldPattern(PatternObject): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/HoldPattern.html</url> + <dl> <dt>'HoldPattern[$expr$]' <dd>is equivalent to $expr$ for pattern matching, but @@ -926,6 +956,8 @@ def match(self, yield_func, expression, vars, evaluation, **kwargs): class Pattern_(PatternObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Pattern.html</url> + <dl> <dt>'Pattern[$symb$, $patt$]' <dt>'$symb$ : $patt$' @@ -1043,6 +1075,9 @@ def get_match_candidates( class Optional(BinaryOperator, PatternObject): """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/Optional.html</url> + <dl> <dt>'Optional[$patt$, $default$]' <dt>'$patt$ : $default$' @@ -1183,6 +1218,8 @@ def init(self, expr): class Blank(_Blank): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Blank.html</url> + <dl> <dt>'Blank[]' <dt>'_' @@ -1229,6 +1266,8 @@ def match(self, yield_func, expression, vars, evaluation, **kwargs): class BlankSequence(_Blank): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/BlankSequence.html</url> + <dl> <dt>'BlankSequence[]' <dt>'__' @@ -1293,6 +1332,8 @@ def get_match_count(self, vars={}): class BlankNullSequence(_Blank): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/BlankNullSequence.html</url> + <dl> <dt>'BlankNullSequence[]' <dt>'___' @@ -1346,6 +1387,8 @@ def get_match_count(self, vars={}): class Repeated(PostfixOperator, PatternObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Repeated.html</url> + <dl> <dt>'Repeated[$pattern$]' <dd>matches one or more occurrences of $pattern$. @@ -1432,6 +1475,8 @@ def get_match_count(self, vars={}): class RepeatedNull(Repeated): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/RepeatedNull.html</url> + <dl> <dt>'RepeatedNull[$pattern$]' <dd>matches zero or more occurrences of $pattern$. @@ -1461,6 +1506,8 @@ def init(self, expr): class Shortest(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Shortest.html</url> + <dl> <dt>'Shortest[$pat$]' <dd>is a pattern object that matches the shortest sequence consistent with the pattern $p$. @@ -1478,6 +1525,8 @@ class Shortest(Builtin): class Longest(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Longest.html</url> + <dl> <dt>'Longest[$pat$]' <dd>is a pattern object that matches the longest sequence consistent with the pattern $p$. @@ -1494,6 +1543,8 @@ class Longest(Builtin): class Condition(BinaryOperator, PatternObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Condition.html</url> + <dl> <dt>'Condition[$pattern$, $expr$]' <dt>'$pattern$ /; $expr$' @@ -1547,6 +1598,8 @@ def yield_match(new_vars, rest): class OptionsPattern(PatternObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/OptionsPattern.html</url> + <dl> <dt>'OptionsPattern[$f$]' <dd>is a pattern that stands for a sequence of options given @@ -1686,6 +1739,8 @@ def atom_to_boxes(self, f, evaluation): class DispatchAtom(AtomBuiltin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/DispatchAtom.html</url> + <dl> <dt>'Dispatch[$rulelist$]' <dd>Introduced for compatibility. Currently, it just return $rulelist$. diff --git a/mathics/builtin/physchemdata.py b/mathics/builtin/physchemdata.py index d6e9893a8..ddafa95eb 100644 --- a/mathics/builtin/physchemdata.py +++ b/mathics/builtin/physchemdata.py @@ -52,6 +52,8 @@ def load_element_data(): class ElementData(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ElementData.html</url> + <dl> <dt>'ElementData["$name$", "$property$"]' <dd>gives the value of the $property$ for the chemical diff --git a/mathics/builtin/quantities.py b/mathics/builtin/quantities.py index 7cf9ecd1f..bdb20f6ff 100644 --- a/mathics/builtin/quantities.py +++ b/mathics/builtin/quantities.py @@ -31,8 +31,9 @@ class KnownUnitQ(Test): - """ + <url>:WMA link:https://reference.wolfram.com/language/ref/KnownUnitQ.html</url> + <dl> <dt>'KnownUnitQ[$unit$]' <dd>returns True if $unit$ is a canonical unit, and False otherwise. @@ -61,6 +62,8 @@ def validate(unit): class Quantity(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Quantity.html</url> + <dl> <dt>'Quantity[$magnitude$, $unit$]' <dd>represents a quantity with size $magnitude$ and unit specified by $unit$. @@ -144,6 +147,8 @@ def apply_1(self, unit, evaluation): class QuantityMagnitude(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/QuantityMagnitude.html</url> + <dl> <dt>'QuantityMagnitude[$quantity$]' <dd>gives the amount of the specified $quantity$. @@ -245,6 +250,7 @@ def get_magnitude(elements, targetUnit, evaluation): class QuantityQ(Test): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/QuantityQ.html</url> <dl> <dt>'QuantityQ[$expr$]' <dd>return True if $expr$ is a valid Association object, and False otherwise. @@ -294,6 +300,8 @@ def validate(elements): class QuantityUnit(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/QuantityUnit.html</url> + <dl> <dt>'QuantityUnit[$quantity$]' <dd>returns the unit associated with the specified $quantity$. @@ -338,6 +346,8 @@ def get_unit(elements): class UnitConvert(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/UnitConvert.html</url> + <dl> <dt>'UnitConvert[$quantity$, $targetunit$] ' <dd> converts the specified $quantity$ to the specified $targetunit$. diff --git a/mathics/builtin/recurrence.py b/mathics/builtin/recurrence.py index 311da9044..3e1b51f91 100644 --- a/mathics/builtin/recurrence.py +++ b/mathics/builtin/recurrence.py @@ -24,6 +24,8 @@ class RSolve(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/RSolve.html</url> + <dl> <dt>'RSolve[$eqn$, $a$[$n$], $n$]' <dd>solves a recurrence equation for the function '$a$[$n$]'. diff --git a/mathics/builtin/scoping.py b/mathics/builtin/scoping.py index 1d251bfd8..be69c2e93 100644 --- a/mathics/builtin/scoping.py +++ b/mathics/builtin/scoping.py @@ -78,6 +78,8 @@ def dynamic_scoping(func, vars, evaluation: Evaluation): class Begin(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Begin.html</url> + <dl> <dt>'Begin'[$context$] <dd>temporarily sets the current context to $context$. @@ -116,6 +118,8 @@ class Begin(Builtin): class BeginPackage(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/BeginPackage.html</url> + <dl> <dt>'BeginPackage'[$context$] <dd>starts the package given by $context$. @@ -148,6 +152,8 @@ class BeginPackage(Builtin): class Block(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Block.html</url> + <dl> <dt>'Block[{$x$, $y$, ...}, $expr$]' <dd>temporarily removes the definitions of the given variables, evaluates $expr$, and restores the original definitions afterwards. @@ -205,6 +211,7 @@ def apply(self, vars, expr, evaluation): class Context_(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/$Context.html</url> <dl> <dt>'$Context' <dd>is the current context. @@ -233,6 +240,8 @@ class Context_(Predefined): class Contexts(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Contexts.html</url> + <dl> <dt>'Contexts[]' <dd>yields a list of all contexts. @@ -257,6 +266,7 @@ def apply(self, evaluation): class ContextPath_(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/$ContextPath.html</url> <dl> <dt>'$ContextPath' <dd>is the search path for contexts. @@ -284,6 +294,8 @@ class ContextPath_(Predefined): class ContextPathStack(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ContextPathStack.html</url> + <dl> <dt>'System`Private`$ContextPathStack' <dd>is an internal variable tracking the values of '$ContextPath' saved by 'Begin' and 'BeginPackage'. @@ -301,6 +313,7 @@ class ContextPathStack(Builtin): class ContextStack(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ContextStack.html</url> <dl> <dt>'System`Private`$ContextStack' <dd>is an internal variable tracking the values of '$Context' @@ -319,6 +332,8 @@ class ContextStack(Builtin): class End(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/End.html</url> + <dl> <dt>'End[]' <dd>ends a context started by 'Begin'. @@ -347,6 +362,8 @@ class End(Builtin): class EndPackage(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/EndPackage.html</url> + <dl> <dt>'EndPackage[]' <dd>marks the end of a package, undoing a previous 'BeginPackage'. @@ -378,6 +395,8 @@ class EndPackage(Builtin): class Module(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Module.html</url> + <dl> <dt>'Module[{$vars$}, $expr$]' <dd>localizes variables by giving them a temporary name of the form 'name$number', where number is the current value of '$ModuleNumber'. Each time a module is evaluated, '$ModuleNumber' is incremented. @@ -447,6 +466,7 @@ def apply(self, vars, expr, evaluation): class ModuleNumber_(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/$ModuleNumber.html</url> <dl> <dt>'$ModuleNumber' <dd>is the current "serial number" to be used for local module variables. @@ -489,6 +509,8 @@ class ModuleNumber_(Predefined): class Unique(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Unique.html</url> + <dl> <dt>'Unique[]' <dd>generates a new symbol and gives a name of the form '$number'. @@ -640,8 +662,9 @@ def apply_symbol(self, vars, attributes, evaluation): class With(Builtin): """ - <dl> + <url>:WMA link:https://reference.wolfram.com/language/ref/With.html</url> + <dl> <dt>'With[{$x$=$x0$, $y$=$y0$, ...}, $expr$]' <dd>specifies that all occurrences of the symbols $x$, $y$, ... in $expr$ should be replaced by $x0$, $y0$, ... </dl> diff --git a/mathics/builtin/structure.py b/mathics/builtin/structure.py index f8012911c..c416a1fca 100644 --- a/mathics/builtin/structure.py +++ b/mathics/builtin/structure.py @@ -44,6 +44,8 @@ class ApplyLevel(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ApplyLevel.html</url> + <dl> <dt>'ApplyLevel[$f$, $expr$]' @@ -68,6 +70,8 @@ class ApplyLevel(BinaryOperator): class BinarySearch(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/BinarySearch.html</url> + <dl> <dt>'CombinatoricaOld`BinarySearch[$l$, $k$]' <dd>searches the list $l$, which has to be sorted, for key $k$ and returns its index in $l$. If $k$ does not @@ -157,6 +161,8 @@ def transform(x): class ByteCount(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ByteCount.html</url> + <dl> <dt>'ByteCount[$expr$]' <dd>gives the internal memory space used by $expr$, in bytes. @@ -177,6 +183,8 @@ def apply(self, expression, evaluation): class Depth(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Depth.html</url> + <dl> <dt>'Depth[$expr$]' <dd>gives the depth of $expr$. @@ -212,6 +220,8 @@ def apply(self, expr, evaluation): class Flatten(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Flatten.html</url> + <dl> <dt>'Flatten[$expr$]' <dd>flattens out nested lists in $expr$. @@ -405,6 +415,8 @@ def apply(self, expr, n, h, evaluation): class FreeQ(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/FreeQ.html</url> + <dl> <dt>'FreeQ[$expr$, $x$]' <dd>returns 'True' if $expr$ does not contain the expression $x$. @@ -444,6 +456,8 @@ def apply(self, expr, form, evaluation): class Null(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Null.html</url> + <dl> <dt>'Null' <dd>is the implicit result of expressions that do not yield a result. @@ -464,6 +478,8 @@ class Null(Predefined): class Operate(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Operate.html</url> + <dl> <dt>'Operate[$p$, $expr$]' <dd>applies $p$ to the head of $expr$. @@ -529,6 +545,8 @@ def apply(self, p, expr, n, evaluation): class Order(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Order.html</url> + <dl> <dt>'Order[$x$, $y$]' <dd>returns a number indicating the canonical ordering of $x$ and $y$. 1 indicates that $x$ is before $y$, @@ -562,6 +580,8 @@ def apply(self, x, y, evaluation): class OrderedQ(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/OrderedQ.html</url> + <dl> <dt>'OrderedQ[{$a$, $b$}]' <dd>is 'True' if $a$ sorts before $b$ according to canonical @@ -589,6 +609,8 @@ def apply(self, expr, evaluation): class PatternsOrderedQ(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/PatternsOrderedQ.html</url> + <dl> <dt>'PatternsOrderedQ[$patt1$, $patt2$]' <dd>returns 'True' if pattern $patt1$ would be applied before @@ -616,6 +638,8 @@ def apply(self, p1, p2, evaluation): class SortBy(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/SortBy.html</url> + <dl> <dt>'SortBy[$list$, $f$]' <dd>sorts $list$ (or the elements of any other expression) according to canonical ordering of the keys that are @@ -688,6 +712,8 @@ def __gt__(self, other): class Through(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Through.html</url> + <dl> <dt>'Through[$p$[$f$][$x$]]' <dd>gives $p$[$f$[$x$]]. diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index 03384c0d9..f9abb144a 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -40,6 +40,8 @@ class Aborted(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Aborted.html</url> + <dl> <dt>'$Aborted' <dd>is returned by a calculation that has been aborted. @@ -52,6 +54,7 @@ class Aborted(Predefined): class CommandLine(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/$CommandLine.html</url> <dl> <dt>'$CommandLine' <dd>is a list of strings passed on the command line to launch the Mathics session. @@ -69,6 +72,8 @@ def evaluate(self, evaluation) -> Expression: class Environment(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Environment.html</url> + <dl> <dt>'Environment[$var$]' <dd>gives the value of an operating system environment variable. @@ -90,6 +95,7 @@ def apply(self, var, evaluation): class Failed(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/$Failed.html</url> <dl> <dt>'$Failed' <dd>is returned by some functions in the event of an error. @@ -106,6 +112,8 @@ class Failed(Predefined): class GetEnvironment(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/GetEnvironment.html</url> + <dl> <dt>'GetEnvironment["$var$"]' <dd>gives the setting corresponding to the variable "var" in the operating system environment. @@ -141,6 +149,8 @@ def apply(self, var, evaluation): class Machine(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/$Machine.html</url> + <dl> <dt>'$Machine' <dd>returns a string describing the type of computer system on which the Mathics is being run. @@ -158,6 +168,8 @@ def evaluate(self, evaluation) -> String: class MachineName(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/MachineName.html</url> + <dl> <dt>'$MachineName' <dd>is a string that gives the assigned name of the computer on which Mathics is being run, if such a name is defined. @@ -175,6 +187,8 @@ def evaluate(self, evaluation) -> String: class MathicsVersion(Predefined): r""" + <url>:mathics native:</url> + <dl> <dt>'MathicsVersion' <dd>this string is the version of Mathics we are running. @@ -191,6 +205,8 @@ def evaluate(self, evaluation) -> String: class Packages(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Packages.html</url> + <dl> <dt>'$Packages' <dd>returns a list of the contexts corresponding to all packages which have been loaded into Mathics. @@ -211,6 +227,8 @@ class Packages(Predefined): class ParentProcessID(Predefined): r""" + <url>:WMA link:https://reference.wolfram.com/language/ref/$ParentProcessID.html</url> + <dl> <dt>'$ParentProcesID' <dd>gives the ID assigned to the process which invokes the \Mathics by the operating system under which it is run. @@ -231,6 +249,8 @@ def evaluate(self, evaluation) -> Integer: class ProcessID(Predefined): r""" + <url>:WMA link:https://reference.wolfram.com/language/ref/ProcessID.html</url> + <dl> <dt>'$ProcessID' <dd>gives the ID assigned to the \Mathics process by the operating system under which it is run. @@ -251,6 +271,8 @@ def evaluate(self, evaluation) -> Integer: class ProcessorType(Predefined): r""" + <url>:WMA link:https://reference.wolfram.com/language/ref/ProcessorType.html</url> + <dl> <dt>'$ProcessorType' <dd>gives a string giving the architecture of the processor on which the \Mathics is being run. @@ -269,6 +291,8 @@ def evaluate(self, evaluation): class ScriptCommandLine(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ScriptCommandLine.html</url> + <dl> <dt>'$ScriptCommandLine' <dd>is a list of string arguments when running the kernel is script mode. @@ -293,6 +317,8 @@ def evaluate(self, evaluation): class Run(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Run.html</url> + <dl> <dt>'Run[$command$]' <dd>runs command as an external operating system command, returning the exit code obtained. @@ -311,6 +337,8 @@ def apply(self, command, evaluation): class SystemID(Predefined): r""" + <url>:WMA link:https://reference.wolfram.com/language/ref/SystemID.html</url> + <dl> <dt>'$SystemID' <dd>is a short string that identifies the type of computer system on which the \Mathics is being run. @@ -327,6 +355,8 @@ def evaluate(self, evaluation) -> String: class SystemWordLength(Predefined): r""" + <url>:WMA link:https://reference.wolfram.com/language/ref/SystemWordLength.html</url> + <dl> <dt>'$SystemWordLength' <dd>gives the effective number of bits in raw machine words on the computer system where \Mathics is running. @@ -352,6 +382,8 @@ def evaluate(self, evaluation) -> Integer: class UserName(Predefined): r""" + <url>:WMA link:https://reference.wolfram.com/language/ref/UserName.html</url> + <dl> <dt>$UserName <dd>returns the login name, according to the operative system, of the user that started the current @@ -376,6 +408,8 @@ def evaluate(self, evaluation) -> String: class Version(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Version.html</url> + <dl> <dt>'$Version' <dd>returns a string with the current Mathics version and the versions of relevant libraries. @@ -394,6 +428,8 @@ def evaluate(self, evaluation) -> String: class VersionNumber(Predefined): r""" + <url>:WMA link:https://reference.wolfram.com/language/ref/VersionNumber.html</url> + <dl> <dt>'$VersionNumber' <dd>is a real number which gives the current Wolfram Language version that \Mathics tries to be compatible with. @@ -416,6 +452,8 @@ def evaluate(self, evaluation) -> Real: class SystemMemory(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/SystemMemory.html</url> + <dl> <dt>'$SystemMemory' <dd>Returns the total amount of physical memory. @@ -434,6 +472,8 @@ def evaluate(self, evaluation) -> Integer: class MemoryAvailable(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/MemoryAvailable.html</url> + <dl> <dt>'MemoryAvailable' <dd>Returns the amount of the available physical memory. @@ -458,6 +498,8 @@ def apply(self, evaluation) -> Integer: class SystemMemory(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/SystemMemory.html</url> + <dl> <dt>'$SystemMemory' <dd>Returns the total amount of physical memory when Python module "psutil" is installed. @@ -476,6 +518,8 @@ def evaluate(self, evaluation) -> Integer: class MemoryAvailable(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/MemoryAvailable.html</url> + <dl> <dt>'MemoryAvailable' <dd>Returns the amount of the available physical when Python module "psutil" is installed. @@ -495,6 +539,8 @@ def apply(self, evaluation) -> Integer: class MemoryInUse(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/MemoryInUse.html</url> + <dl> <dt>'MemoryInUse[]' <dd>Returns the amount of memory used by all of the definitions objects if we can determine that; -1 otherwise. @@ -543,6 +589,8 @@ def sizeof(obj): class Share(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Share.html</url> + <dl> <dt>'Share[]' <dd>release memory forcing Python to do garbage collection. If Python package is 'psutil' installed is the amount of released memoryis returned. Otherwise returns $0$. This function differs from WMA which tries to reduce the amount of memory required to store definitions, by reducing duplicated definitions. From 056c1310b9432b2aa51c90e78f3115de3dbd0c52 Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Wed, 21 Dec 2022 08:12:48 -0300 Subject: [PATCH 072/121] comment out missing urls --- mathics/builtin/binary/io.py | 5 ----- mathics/builtin/files_io/files.py | 16 ---------------- mathics/builtin/system.py | 2 +- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/mathics/builtin/binary/io.py b/mathics/builtin/binary/io.py index 82d13a360..9dcd5fb41 100644 --- a/mathics/builtin/binary/io.py +++ b/mathics/builtin/binary/io.py @@ -31,7 +31,6 @@ class _BinaryFormat: """ - Container for BinaryRead readers and BinaryWrite writers """ @@ -360,13 +359,9 @@ def _UnsignedInteger128_writer(s, x): class BinaryRead(Builtin): """ -<<<<<<< HEAD <url> :WMA link: https://reference.wolfram.com/language/ref/BinaryRead.html</url> -======= - <url>:WMA link:https://reference.wolfram.com/language/ref/BinaryRead.html</url> ->>>>>>> ee219dad (docstr url for logic, optiondoc, sparse, tensor layout, evaluation, logic, comparison, datentime, attributes and binary) <dl> <dt>'BinaryRead[$stream$]' diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index f693d6476..eea507877 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -183,12 +183,8 @@ def eval_path(self, path, evaluation, options): class Character(Builtin): """ -<<<<<<< HEAD <url>:WMA link: https://reference.wolfram.com/language/ref/Character.html</url> -======= - <url>:WMA link:https://reference.wolfram.com/language/ref/Character.html</url> ->>>>>>> f6040b82 (docstr url for files_io, intfns, list and trace) <dl> <dt>'Character' @@ -486,13 +482,9 @@ def eval_default(self, filename, evaluation): class InputFileName_(Predefined): """ -<<<<<<< HEAD <url> :WMA link: https://reference.wolfram.com/language/ref/$InputFileName.html</url> -======= - <url>:WMA link:https://reference.wolfram.com/language/ref/$InputFileName.html</url> ->>>>>>> f6040b82 (docstr url for files_io, intfns, list and trace) <dl> <dt>'$InputFileName' @@ -534,13 +526,9 @@ class InputStream(Builtin): class OpenRead(_OpenAction): """ -<<<<<<< HEAD <url> :WMA link: https://reference.wolfram.com/language/ref/OpenRead.html</url> -======= - <url>:WMA link:https://reference.wolfram.com/language/ref/OpenRead.html</url> ->>>>>>> f6040b82 (docstr url for files_io, intfns, list and trace) <dl> <dt>'OpenRead["file"]' @@ -731,13 +719,9 @@ def eval_default(self, exprs, filename, evaluation): class PutAppend(BinaryOperator): """ -<<<<<<< HEAD <url> :WMA link: https://reference.wolfram.com/language/ref/PutAppend.html</url> -======= - <url>:WMA link:https://reference.wolfram.com/language/ref/PutAppend.html</url> ->>>>>>> f6040b82 (docstr url for files_io, intfns, list and trace) <dl> <dt>'$expr$ >>> $filename$' diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index f9abb144a..711857241 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -187,7 +187,7 @@ def evaluate(self, evaluation) -> String: class MathicsVersion(Predefined): r""" - <url>:mathics native:</url> + ## <url>:mathics native:</url> <dl> <dt>'MathicsVersion' From f1d2efdf8a84353eb97a0e98f083d0691cae9c73 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 09:25:12 -0500 Subject: [PATCH 073/121] Some URL shorting apply->def, isort --- mathics/builtin/compress.py | 14 +++++++++----- mathics/builtin/graphics.py | 23 +++++++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/mathics/builtin/compress.py b/mathics/builtin/compress.py index ac8da73e9..86be1caea 100644 --- a/mathics/builtin/compress.py +++ b/mathics/builtin/compress.py @@ -10,10 +10,12 @@ class Compress(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Compress.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/Compress.html</url> <dl> - <dt>'Compress[$expr$]' + <dt>'Compress[$expr$]' <dd>gives a compressed string representation of $expr$. </dl> @@ -27,7 +29,7 @@ class Compress(Builtin): } summary_text = "compress an expression" - def apply(self, expr, evaluation, options): + def eval(self, expr, evaluation, options): "Compress[expr_, OptionsPattern[Compress]]" if isinstance(expr, String): string = '"' + expr.value + '"' @@ -47,7 +49,9 @@ def apply(self, expr, evaluation, options): class Uncompress(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Uncompress.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/Uncompress.html</url> <dl> <dt>'Uncompress["$string$"]' @@ -67,7 +71,7 @@ class Uncompress(Builtin): summary_text = "recover a compressed expression" - def apply(self, string, evaluation): + def eval(self, string, evaluation): "Uncompress[string_String]" string = string.get_string_value() # .encode("utf-8") string = base64.b64decode(string) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index fd943c2d3..9d96fd319 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -400,11 +400,14 @@ class _Thickness(_Size): class AbsoluteThickness(_Thickness): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/AbsoluteThickness.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/AbsoluteThickness.html</url> <dl> <dt>'AbsoluteThickness[$p$]' - <dd>sets the line thickness for subsequent graphics primitives to $p$ points. + <dd>sets the line thickness for subsequent graphics primitives to $p$ \ + points. </dl> >> Graphics[Table[{AbsoluteThickness[t], Line[{{20 t, 10}, {20 t, 80}}], Text[ToString[t]<>"pt", {20 t, 0}]}, {t, 0, 10}]] @@ -535,7 +538,9 @@ def path(max_degree, p): class FilledCurve(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/FilledCurve.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/FilledCurve.html</url> <dl> <dt>'FilledCurve[{$segment1$, $segment2$ ...}]' @@ -584,7 +589,9 @@ class Polygon(Builtin): class RegularPolygon(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/RegularPolygon.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/RegularPolygon.html</url> <dl> <dt>'RegularPolygon[$n$]' @@ -609,7 +616,9 @@ class RegularPolygon(Builtin): class Arrow(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Arrow.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/Arrow.html</url> <dl> <dt>'Arrow[{$p1$, $p2$}]' @@ -648,7 +657,9 @@ class Arrow(Builtin): class Arrowheads(_GraphicsDirective): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Arrowheads.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/Arrowheads.html</url> <dl> <dt>'Arrowheads[$s$]' From 3e9f093a858f1dd1d12f1c39128842b0bf55653d Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Wed, 21 Dec 2022 07:16:28 -0300 Subject: [PATCH 074/121] docstr url for logic, optiondoc, sparse, tensor layout, evaluation, logic, comparison, datentime, attributes and binary --- mathics/builtin/binary/io.py | 1 + mathics/builtin/comparison.py | 3 +- mathics/builtin/datentime.py | 82 +++-- mathics/builtin/evaluation.py | 22 +- mathics/builtin/layout.py | 63 ++-- mathics/builtin/logic.py | 601 +++++++++++++++++----------------- mathics/core/systemsymbols.py | 1 + 7 files changed, 391 insertions(+), 382 deletions(-) diff --git a/mathics/builtin/binary/io.py b/mathics/builtin/binary/io.py index 9dcd5fb41..077ad3ccc 100644 --- a/mathics/builtin/binary/io.py +++ b/mathics/builtin/binary/io.py @@ -31,6 +31,7 @@ class _BinaryFormat: """ + Container for BinaryRead readers and BinaryWrite writers """ diff --git a/mathics/builtin/comparison.py b/mathics/builtin/comparison.py index 76c663a91..297c0101f 100644 --- a/mathics/builtin/comparison.py +++ b/mathics/builtin/comparison.py @@ -4,7 +4,8 @@ There are a number of functions for testing Expressions. -Functions that "ask a question" have names that end in "Q". They return 'True' for an explicit answer, and 'False' otherwise. +Functions that "ask a question" have names that end in "Q". \ +They return 'True' for an explicit answer, and 'False' otherwise. """ # This tells documentation how to sort this module diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index 5e8fb54d1..b21303307 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -25,8 +25,8 @@ ) from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.convert.python import from_python -from mathics.core.evaluation import TimeoutInterrupt, run_with_timeout_and_stack from mathics.core.element import ImmutableValueMixin +from mathics.core.evaluation import TimeoutInterrupt, run_with_timeout_and_stack from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolNull @@ -36,7 +36,6 @@ SymbolInfinity, SymbolRowBox, ) - from mathics.settings import TIME_12HOUR START_TIME = time.time() @@ -381,12 +380,12 @@ class AbsoluteTime(_DateFormat): summary_text = "absolute time in seconds" - def apply_now(self, evaluation): + def eval_now(self, evaluation): "AbsoluteTime[]" return Real(total_seconds(datetime.now() - EPOCH_START)) - def apply_spec(self, epochtime, evaluation): + def eval_spec(self, epochtime, evaluation): "AbsoluteTime[epochtime_]" datelist = self.to_datelist(epochtime, evaluation) @@ -420,7 +419,7 @@ class AbsoluteTiming(Builtin): summary_text = "total wall-clock time to run a Mathics command" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "AbsoluteTiming[expr_]" start = time.time() @@ -479,7 +478,7 @@ class DateDifference(Builtin): summary_text = "find the difference in days, weeks, etc. between two dates" - def apply(self, date1, date2, units, evaluation): + def eval(self, date1, date2, units, evaluation): "DateDifference[date1_, date2_, units_]" # Process dates @@ -626,7 +625,7 @@ class DateObject(_DateFormat, ImmutableValueMixin): " an object representing a date of any granularity (year, hour, instant, ...)" ) - def apply_any(self, args, evaluation, options): + def eval_any(self, args, evaluation, options): "DateObject[args_, OptionsPattern[]]" datelist = None tz = None @@ -684,7 +683,7 @@ def apply_any(self, args, evaluation, options): fmt, ) - def apply_makeboxes(self, datetime, gran, cal, tz, fmt, evaluation): + def eval_makeboxes(self, datetime, gran, cal, tz, fmt, evaluation): "MakeBoxes[DateObject[datetime_List, gran_, cal_, tz_, fmt_], StandardForm|TraditionalForm|OutputForm]" # TODO: if fmt.sameQ(SymbolAutomatic): @@ -742,7 +741,7 @@ class DatePlus(Builtin): summary_text = "add or subtract days, weeks, etc. in a date list or string" - def apply(self, date, off, evaluation): + def eval(self, date, off, evaluation): "DatePlus[date_, off_]" # Process date @@ -857,7 +856,7 @@ class DateList(_DateFormat): summary_text = "date elements as numbers in {y,m,d,h,m,s} format" - def apply(self, epochtime, evaluation): + def eval(self, epochtime, evaluation): "%(name)s[epochtime_]" datelist = self.to_datelist(epochtime, evaluation) @@ -940,7 +939,7 @@ class DateString(_DateFormat): summary_text = "current or specified date as a string in many possible formats" - def apply(self, epochtime, form, evaluation): + def eval(self, epochtime, form, evaluation): "DateString[epochtime_, form_]" datelist = self.to_datelist(epochtime, evaluation) @@ -1005,11 +1004,13 @@ def evaluate(self, evaluation): class EasterSunday(Builtin): # Calendar`EasterSunday """ - <url>:Date of Easter:https://en.wikipedia.org/wiki/Date_of_Easter</url> \ - (<url>:WMA link:https://reference.wolfram.com/language/Calendar/ref/EasterSunday.html</url>) + <url>:Date of Easter: + https://en.wikipedia.org/wiki/Date_of_Easter</url> (<url> + :WMA link: + https://reference.wolfram.com/language/Calendar/ref/EasterSunday.html</url>) <dl> - <dt>'EasterSunday[$year$]' + <dt>'EasterSunday[$year$]' <dd>returns the date of the Gregorian Easter Sunday as {year, month, day}. </dl> @@ -1022,7 +1023,7 @@ class EasterSunday(Builtin): # Calendar`EasterSunday summary_text = "find the date of Easter Sunday for a given year" - def apply(self, year, evaluation): + def eval(self, year, evaluation): "EasterSunday[year_Integer]" y = year.value @@ -1065,7 +1066,7 @@ class Pause(Builtin): summary_text = "pause for a number of seconds" - def apply(self, n, evaluation): + def eval(self, n, evaluation): "Pause[n_]" sleeptime = n.to_python() if not isinstance(sleeptime, (int, float)) or sleeptime < 0: @@ -1080,7 +1081,9 @@ def apply(self, n, evaluation): class SystemTimeZone(Predefined): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/$SystemTimeZone.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/$SystemTimeZone.html</url> <dl> <dt>'$SystemTimeZone' @@ -1164,11 +1167,11 @@ class TimeConstrained(Builtin): summary_text = "run a command for at most a specified time" - def apply_2(self, expr, t, evaluation): + def eval_2(self, expr, t, evaluation): "TimeConstrained[expr_, t_]" - return self.apply_3(expr, t, SymbolAborted, evaluation) + return self.eval_3(expr, t, SymbolAborted, evaluation) - def apply_3(self, expr, t, failexpr, evaluation): + def eval_3(self, expr, t, failexpr, evaluation): "TimeConstrained[expr_, t_, failexpr_]" t = t.evaluate(evaluation) if not t.is_numeric(evaluation): @@ -1191,8 +1194,9 @@ def apply_3(self, expr, t, failexpr, evaluation): class TimeZone(Predefined): """ - <url>:Time Zone:https://en.wikipedia.org/wiki/Time_zone</url> \ - (<url>:WMA link:https://reference.wolfram.com/language/ref/TimeZone.html</url>) + <url>:Time Zone:https://en.wikipedia.org/wiki/Time_zone</url> (<url> + :WMA link: + https://reference.wolfram.com/language/ref/$TimeZone.html</url>) <dl> <dt>'$TimeZone' @@ -1213,7 +1217,7 @@ class TimeZone(Predefined): summary_text = "resettable default time zone" - def apply(self, lhs, rhs, evaluation): + def eval(self, lhs, rhs, evaluation): "lhs_ = rhs_" self.assign(lhs, rhs, evaluation) @@ -1240,7 +1244,7 @@ class TimeUsed(Builtin): "the total number of seconds of CPU time in the current Mathics session" ) - def apply(self, evaluation): + def eval(self, evaluation): "TimeUsed[]" # time.process_time() is better than # time.clock(). See https://bugs.python.org/issue31803 @@ -1252,9 +1256,10 @@ class Timing(Builtin): <url>:WMA link:https://reference.wolfram.com/language/ref/Timing.html</url> <dl> - <dt>'Timing[$expr$]' + <dt>'Timing[$expr$]' <dd>measures the processor time taken to evaluate $expr$. - It returns a list containing the measured time in seconds and the result of the evaluation. + It returns a list containing the measured time in seconds and \ + the result of the evaluation. </dl> >> Timing[50!] @@ -1267,7 +1272,7 @@ class Timing(Builtin): summary_text = "CPU time to run a Mathics command" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "Timing[expr_]" start = time.process_time() @@ -1278,10 +1283,12 @@ def apply(self, expr, evaluation): class SessionTime(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/SessionTime.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/SessionTime.html</url> <dl> - <dt>'SessionTime[]' + <dt>'SessionTime[]' <dd>returns the total time in seconds since this session started. </dl> @@ -1293,20 +1300,23 @@ class SessionTime(Builtin): "total elapsed time in seconds since the beginning of your Mathics session" ) - def apply(self, evaluation): + def eval(self, evaluation): "SessionTime[]" return Real(time.time() - START_TIME) class TimeRemaining(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/TimeRemaining.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/TimeRemaining.html</url> <dl> - <dt>'TimeRemaining[]' - <dd>Gives the number of seconds remaining until the earliest enclosing 'TimeConstrained' will request the current computation to stop. - <dt>'TimeConstrained[$expr$, $t$, $failexpr$]' - <dd>returns $failexpr$ if the time constraint is not met. + <dt>'TimeRemaining[]' + <dd>Gives the number of seconds remaining until the earliest enclosing 'TimeConstrained' will request the current computation to stop. + + <dt>'TimeConstrained[$expr$, $t$, $failexpr$]' + <dd>returns $failexpr$ if the time constraint is not met. </dl> If TimeConstrained is called out of a TimeConstrained expression, returns `Infinity` @@ -1320,7 +1330,7 @@ class TimeRemaining(Builtin): summary_text = "time before a time constraint in a running program" - def apply(self, evaluation): + def eval(self, evaluation): "TimeRemaining[]" if len(evaluation.timeout_queue) > 0: t, start_time = evaluation.timeout_queue[-1] diff --git a/mathics/builtin/evaluation.py b/mathics/builtin/evaluation.py index 883fd7653..d4ca1c06c 100644 --- a/mathics/builtin/evaluation.py +++ b/mathics/builtin/evaluation.py @@ -1,20 +1,22 @@ # -*- coding: utf-8 -*- -from mathics.builtin.base import Predefined, Builtin +from mathics.builtin.base import Builtin, Predefined from mathics.core.atoms import Integer -from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit - from mathics.core.attributes import A_HOLD_ALL, A_HOLD_ALL_COMPLETE, A_PROTECTED +from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit class RecursionLimit(Predefined): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/$RecursionLimit.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/$RecursionLimit.html</url> <dl> <dt>'$RecursionLimit' - <dd>specifies the maximum allowable recursion depth after which a calculation is terminated. + <dd>specifies the maximum allowable recursion depth after which a \ + calculation is terminated. </dl> Calculations terminated by '$RecursionLimit' return '$Aborted': @@ -352,16 +354,18 @@ class Quit(Builtin): <url>:WMA link:https://reference.wolfram.com/language/ref/Quit.html</url> <dl> - <dt>'Quit'[] + <dt>'Quit'[] <dd> Terminates the Mathics session. - <dt>'Quit[$n$]' + + <dt>'Quit[$n$]' <dd> Terminates the mathics session with exit code $n$. </dl> <dl> - <dt>'Exit'[] + <dt>'Exit'[] <dd> Terminates the Mathics session. - <dt>'Exit[$n$]' + + <dt>'Exit[$n$]' <dd> Terminates the mathics session with exit code $n$. </dl> diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index 155ccaf19..9b68b9ce9 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -10,23 +10,16 @@ """ -from mathics.builtin.base import ( - Builtin, - BinaryOperator, - Operator, -) +from mathics.builtin.base import BinaryOperator, Builtin, Operator from mathics.builtin.box.layout import GridBox, RowBox, to_boxes from mathics.builtin.lists import list_boxes from mathics.builtin.makeboxes import MakeBoxes from mathics.builtin.options import options_to_rules - from mathics.core.atoms import Real, String - from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Symbol from mathics.core.systemsymbols import SymbolMakeBoxes - from mathics.eval.makeboxes import format_element SymbolSubscriptBox = Symbol("System`SubscriptBox") @@ -172,13 +165,14 @@ class Left(Builtin): class NonAssociative(Builtin): """ - ## For some reason, this is a Builtin symbol in WMA, but it is not available in WR. - ## <url>:WMA link:https://reference.wolfram.com/language/ref/NonAssociative.html</url> - - <dl> - <dt>'NonAssociative' - <dd>is used with operator formatting constructs to specify a non-associative operator. - </dl> + ## For some reason, this is a Builtin symbol in WMA, but it is not available in WR. + ## <url>:WMA link:https://reference.wolfram.com/language/ref/NonAssociative.html</url> + on, logic, comparison, datentime, attributes and binary) + + <dl> + <dt>'NonAssociative' + <dd>is used with operator formatting constructs to specify a non-associative operator. + </dl> """ summary_text = "non-associative operator" @@ -212,25 +206,26 @@ class Postfix(BinaryOperator): class Precedence(Builtin): """ - ## As NonAssociative, this is a Builtin in WMA that does not have an entry in WR. - ## <url>:WMA link:https://reference.wolfram.com/language/ref/Precedence.html</url> - - <dl> - <dt>'Precedence[$op$]' - <dd>returns the precedence of the built-in operator $op$. - </dl> - - >> Precedence[Plus] - = 310. - >> Precedence[Plus] < Precedence[Times] - = True - - Unknown symbols have precedence 670: - >> Precedence[f] - = 670. - Other expressions have precedence 1000: - >> Precedence[a + b] - = 1000. + ## As NonAssociative, this is a Builtin in WMA that does not have an entry in WR. + ## <url>:WMA link:https://reference.wolfram.com/language/ref/Precedence.html</url> + on, logic, comparison, datentime, attributes and binary) + + <dl> + <dt>'Precedence[$op$]' + <dd>returns the precedence of the built-in operator $op$. + </dl> + + >> Precedence[Plus] + = 310. + >> Precedence[Plus] < Precedence[Times] + = True + + Unknown symbols have precedence 670: + >> Precedence[f] + = 670. + Other expressions have precedence 1000: + >> Precedence[a + b] + = 1000. """ summary_text = "an object to be parenthesized with a given precedence level" diff --git a/mathics/builtin/logic.py b/mathics/builtin/logic.py index 397c6bca2..fa3ec2b54 100644 --- a/mathics/builtin/logic.py +++ b/mathics/builtin/logic.py @@ -1,86 +1,66 @@ # -*- coding: utf-8 -*- -from mathics.builtin.base import BinaryOperator, Predefined, PrefixOperator, Builtin +from mathics.builtin.base import BinaryOperator, Builtin, Predefined, PrefixOperator from mathics.builtin.lists import InvalidLevelspecError, python_levelspec, walk_levels -from mathics.core.expression import Expression - from mathics.core.attributes import ( A_FLAT, A_HOLD_ALL, + A_LOCKED, A_ONE_IDENTITY, A_ORDERLESS, A_PROTECTED, - A_LOCKED, ) - - -from mathics.core.symbols import ( - Symbol, - SymbolTrue, - SymbolFalse, -) - +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue from mathics.core.systemsymbols import ( SymbolAnd, + SymbolEquivalent, SymbolImplies, SymbolNot, SymbolOr, SymbolXor, ) -SymbolEquivalent = Symbol("Equivalent") +class _ShortCircuit(Exception): + def __init__(self, result): + self.result = result -class Or(BinaryOperator): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Or.html</url> - <dl> - <dt>'Or[$expr1$, $expr2$, ...]' - <dt>'$expr1$ || $expr2$ || ...' - <dd>evaluates each expression in turn, returning 'True' - as soon as an expression evaluates to 'True'. If all - expressions evaluate to 'False', 'Or' returns 'False'. - </dl> +class _ManyTrue(Builtin): + rules = { + "%(name)s[list_List, test_]": "%(name)s[list, test, 1]", + "%(name)s[test_][list_List]": "%(name)s[list, test]", + } - >> False || True - = True + def _short_circuit(self, what): + raise NotImplementedError - If an expression does not evaluate to 'True' or 'False', 'Or' - returns a result in symbolic form: - >> a || False || b - = a || b - """ + def _no_short_circuit(self): + raise NotImplementedError - attributes = A_FLAT | A_HOLD_ALL | A_ONE_IDENTITY | A_PROTECTED - operator = "||" - precedence = 215 - summary_text = "logic (inclusive) disjunction" + def apply(self, expr, test, level, evaluation): + "%(name)s[expr_, test_, level_]" - # rules = { - # "Or[a_]": "a", - # "Or[a_, a_]": "a", - # "Or[pred1___, a_, pred2___, a_, pred3___]": "Or[pred1, a, pred2, pred3]", - # } - def apply(self, args, evaluation): - "Or[args___]" + try: + start, stop = python_levelspec(level) + except InvalidLevelspecError: + evaluation.message("Level", "level", level) + return - args = args.get_sequence() - elements = [] - for arg in args: - result = arg.evaluate(evaluation) - if result is SymbolTrue: - return SymbolTrue - elif result != SymbolFalse: - elements.append(result) - if elements: - if len(elements) == 1: - return elements[0] - else: - return Expression(SymbolOr, *elements) - else: - return SymbolFalse + def callback(node): + self._short_circuit( + Expression(test, node).evaluate(evaluation) is SymbolTrue + ) + return node + + try: + walk_levels(expr, start, stop, callback=callback) + except _ShortCircuit as e: + return e.result + + return self._no_short_circuit() class And(BinaryOperator): @@ -88,17 +68,17 @@ class And(BinaryOperator): <url>:WMA link:https://reference.wolfram.com/language/ref/And.html</url> <dl> - <dt>'And[$expr1$, $expr2$, ...]' - <dt>'$expr1$ && $expr2$ && ...' - <dd>evaluates each expression in turn, returning 'False' - as soon as an expression evaluates to 'False'. If all - expressions evaluate to 'True', 'And' returns 'True'. + <dt>'And[$expr1$, $expr2$, ...]' + <dt>'$expr1$ && $expr2$ && ...' + <dd>evaluates each expression in turn, returning 'False' \ + as soon as an expression evaluates to 'False'. If all \ + expressions evaluate to 'True', 'And' returns 'True'. </dl> >> True && True && False = False - If an expression does not evaluate to 'True' or 'False', 'And' + If an expression does not evaluate to 'True' or 'False', 'And' \ returns a result in symbolic form: >> a && b && True && c = a && b && c @@ -134,119 +114,81 @@ def apply(self, args, evaluation): return SymbolTrue -class Not(PrefixOperator): +class AnyTrue(_ManyTrue): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Not.html</url> + <url>:WMA link:https://reference.wolfram.com/language/ref/AnyTrue.html</url> <dl> - <dt>'Not[$expr$]' - <dt>'!$expr$' - <dd>negates the logical expression $expr$. - </dl> - - >> !True - = False - >> !False - = True - >> !b - = !b - """ + <dt>'AnyTrue[{$expr1$, $expr2$, ...}, $test$]' + <dd>returns True if any application of $test$ to \ + $expr1$, $expr2$, ... evaluates to True. - operator = "!" - precedence = 230 - - rules = { - "Not[True]": "False", - "Not[False]": "True", - "Not[Not[expr_]]": "expr", - } - summary_text = "logic negation" - - -class Nand(Builtin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Nand.html</url> + <dt>'AnyTrue[$list$, $test$, $level$]' + <dd>returns True if any application of $test$ to items of \ + $list$ at $level$ evaluates to True. - <dl> - <dt>'Nand[$expr1$, $expr2$, ...]' - <dt>$expr1$ \u22BC $expr2$ \u22BC ... - <dd> Implements the logical NAND function. The same as 'Not[And['$expr1$, $expr2$, ...']]' + <dt>'AnyTrue[$test$]' + <dd>gives an operator that may be applied to expressions. </dl> - >> Nand[True, False] - = True - """ - - operator = "\u22BC" - rules = { - "Nand[expr___]": "Not[And[expr]]", - } - summary_text = "negation of logic conjunction" + >> AnyTrue[{1, 3, 5}, EvenQ] + = False -class Nor(Builtin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Nor.html</url> + >> AnyTrue[{1, 4, 5}, EvenQ] + = True - <dl> - <dt>'Nor[$expr1$, $expr2$, ...]' - <dt>$expr1$ \u22BD $expr2$ \u22BD ... - <dd>Implements the logical NOR function. The same as 'Not[Or['$expr1$, $expr2$, ...']]' - </dl> - >> Nor[True, False] + #> AnyTrue[{}, EvenQ] = False """ - operator = "\u22BD" - rules = { - "Nor[expr___]": "Not[Or[expr]]", - } - summary_text = "negation of logic (inclusive) disjunction" + summary_text = "some of the elements are True" + + def _short_circuit(self, what): + if what: + raise _ShortCircuit(SymbolTrue) + + def _no_short_circuit(self): + return SymbolFalse -class Implies(BinaryOperator): +class AllTrue(_ManyTrue): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Implies.html</url> + <url>:WMA link:https://reference.wolfram.com/language/ref/AllTrue.html</url> <dl> - <dt>'Implies[$expr1$, $expr2$]' - <dt>$expr1$ \u21D2 $expr2$ - <dd>evaluates each expression in turn, returning 'True' - as soon as the first expression evaluates to 'False'. If the - first expression evaluates to 'True', 'Implies' returns the - second expression. + <dt>'AllTrue[{$expr1$, $expr2$, ...}, $test$]' + <dd>returns True if all applications of $test$ to $expr1$, $expr2$, ... evaluate to True. + <dt>'AllTrue[$list$, $test$, $level$]' + <dd>returns True if all applications of $test$ to items of $list$ at $level$ evaluate to True. + <dt>'AllTrue[$test$]' + <dd>gives an operator that may be applied to expressions. </dl> - >> Implies[False, a] + >> AllTrue[{2, 4, 6}, EvenQ] = True - >> Implies[True, a] - = a - If an expression does not evaluate to 'True' or 'False', 'Implies' - returns a result in symbolic form: - >> Implies[a, Implies[b, Implies[True, c]]] - = a \u21D2 b \u21D2 c + >> AllTrue[{2, 4, 7}, EvenQ] + = False + + #> AllTrue[{}, EvenQ] + = True """ - operator = "\u21D2" - precedence = 200 - grouping = "Right" - summary_text = "logic implication" + summary_text = "all the elements are True" - def apply(self, x, y, evaluation): - "Implies[x_, y_]" + def _short_circuit(self, what): + if not what: + raise _ShortCircuit(SymbolFalse) - result0 = x.evaluate(evaluation) - if result0 is SymbolFalse: - return SymbolTrue - elif result0 is SymbolTrue: - return y.evaluate(evaluation) - else: - return Expression(SymbolImplies, result0, y.evaluate(evaluation)) + def _no_short_circuit(self): + return SymbolTrue class Equivalent(BinaryOperator): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Equivalent.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/Equivalent.html</url> <dl> <dt>'Equivalent[$expr1$, $expr2$, ...]' @@ -259,7 +201,7 @@ class Equivalent(BinaryOperator): >> Equivalent[True, True, False] = False - If all expressions do not evaluate to 'True' or 'False', 'Equivalent' + If all expressions do not evaluate to 'True' or 'False', 'Equivalent' \ returns a result in symbolic form: >> Equivalent[a, b, c] = a \u29E6 b \u29E6 c @@ -300,139 +242,60 @@ def apply(self, args, evaluation): return Expression(SymbolEquivalent, *args) -class Xor(BinaryOperator): +class False_(Predefined): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Xor.html</url> + <url>:WMA link:https://reference.wolfram.com/language/ref/False.html</url> <dl> - <dt>'Xor[$expr1$, $expr2$, ...]' - <dt>$expr1$ \u22BB $expr2$ \u22BB ... + <dt>'False' + <dd>represents the Boolean false value. + </dl> + """ - <dd>evaluates each expression in turn, returning 'True' - as soon as not all expressions evaluate to the same value. If all - expressions evaluate to the same value, 'Xor' returns 'False'. + attributes = A_LOCKED | A_PROTECTED + name = "False" + summary_text = "boolean constant for False" + + +class Implies(BinaryOperator): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Implies.html</url> + + <dl> + <dt>'Implies[$expr1$, $expr2$]' + <dt>$expr1$ \u21D2 $expr2$ + <dd>evaluates each expression in turn, returning 'True' \ + as soon as the first expression evaluates to 'False'. If the \ + first expression evaluates to 'True', 'Implies' returns the \ + second expression. </dl> - >> Xor[False, True] + >> Implies[False, a] = True - >> Xor[True, True] - = False + >> Implies[True, a] + = a - If an expression does not evaluate to 'True' or 'False', 'Xor' + If an expression does not evaluate to 'True' or 'False', 'Implies' returns a result in symbolic form: - >> Xor[a, False, b] - = a \u22BB b - #> Xor[] - = False - #> Xor[a] - = a - #> Xor[False] - = False - #> Xor[True] - = True - #> Xor[a, b] - = a \u22BB b + >> Implies[a, Implies[b, Implies[True, c]]] + = a \u21D2 b \u21D2 c """ - attributes = A_FLAT | A_ONE_IDENTITY | A_ORDERLESS | A_PROTECTED - operator = "\u22BB" - precedence = 215 - summary_text = "logic (exclusive) disjunction" + operator = "\u21D2" + precedence = 200 + grouping = "Right" + summary_text = "logic implication" - def apply(self, args, evaluation): - "Xor[args___]" + def apply(self, x, y, evaluation): + "Implies[x_, y_]" - args = args.get_sequence() - elements = [] - flag = True - for arg in args: - result = arg.evaluate(evaluation) - if result is SymbolTrue: - flag = not flag - elif result != SymbolFalse: - elements.append(result) - if elements and flag: - if len(elements) == 1: - return elements[0] - else: - return Expression(SymbolXor, *elements) - elif elements and not flag: - if len(elements) == 1: - return Expression(SymbolNot, elements[0]) - else: - return Expression(SymbolNot, Expression(SymbolXor, *elements)) + result0 = x.evaluate(evaluation) + if result0 is SymbolFalse: + return SymbolTrue + elif result0 is SymbolTrue: + return y.evaluate(evaluation) else: - return Symbol(repr(not flag)) - - -class True_(Predefined): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/True.html</url> - - <dl> - <dt>'True' - <dd>represents the Boolean true value. - </dl> - """ - - attributes = A_LOCKED | A_PROTECTED - name = "True" - summary_text = "boolean constant for True" - - -class False_(Predefined): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/False.html</url> - - <dl> - <dt>'False' - <dd>represents the Boolean false value. - </dl> - """ - - attributes = A_LOCKED | A_PROTECTED - name = "False" - summary_text = "boolean constant for False" - - -class _ShortCircuit(Exception): - def __init__(self, result): - self.result = result - - -class _ManyTrue(Builtin): - rules = { - "%(name)s[list_List, test_]": "%(name)s[list, test, 1]", - "%(name)s[test_][list_List]": "%(name)s[list, test]", - } - - def _short_circuit(self, what): - raise NotImplementedError - - def _no_short_circuit(self): - raise NotImplementedError - - def apply(self, expr, test, level, evaluation): - "%(name)s[expr_, test_, level_]" - - try: - start, stop = python_levelspec(level) - except InvalidLevelspecError: - evaluation.message("Level", "level", level) - return - - def callback(node): - self._short_circuit( - Expression(test, node).evaluate(evaluation) is SymbolTrue - ) - return node - - try: - walk_levels(expr, start, stop, callback=callback) - except _ShortCircuit as e: - return e.result - - return self._no_short_circuit() + return Expression(SymbolImplies, result0, y.evaluate(evaluation)) class NoneTrue(_ManyTrue): @@ -468,67 +331,201 @@ def _no_short_circuit(self): return SymbolTrue -class AnyTrue(_ManyTrue): +class Or(BinaryOperator): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/AnyTrue.html</url> + <url>:WMA link:https://reference.wolfram.com/language/ref/Or.html</url> <dl> - <dt>'AnyTrue[{$expr1$, $expr2$, ...}, $test$]' - <dd>returns True if any application of $test$ to $expr1$, $expr2$, ... evaluates to True. - <dt>'AnyTrue[$list$, $test$, $level$]' - <dd>returns True if any application of $test$ to items of $list$ at $level$ evaluates to True. - <dt>'AnyTrue[$test$]' - <dd>gives an operator that may be applied to expressions. + <dt>'Or[$expr1$, $expr2$, ...]' + <dt>'$expr1$ || $expr2$ || ...' + <dd>evaluates each expression in turn, returning 'True' + as soon as an expression evaluates to 'True'. If all + expressions evaluate to 'False', 'Or' returns 'False'. </dl> - >> AnyTrue[{1, 3, 5}, EvenQ] - = False + >> False || True + = True - >> AnyTrue[{1, 4, 5}, EvenQ] + If an expression does not evaluate to 'True' or 'False', 'Or' + returns a result in symbolic form: + >> a || False || b + = a || b + """ + + attributes = A_FLAT | A_HOLD_ALL | A_ONE_IDENTITY | A_PROTECTED + operator = "||" + precedence = 215 + summary_text = "logic (inclusive) disjunction" + + # rules = { + # "Or[a_]": "a", + # "Or[a_, a_]": "a", + # "Or[pred1___, a_, pred2___, a_, pred3___]": "Or[pred1, a, pred2, pred3]", + # } + def apply(self, args, evaluation): + "Or[args___]" + + args = args.get_sequence() + elements = [] + for arg in args: + result = arg.evaluate(evaluation) + if result is SymbolTrue: + return SymbolTrue + elif result != SymbolFalse: + elements.append(result) + if elements: + if len(elements) == 1: + return elements[0] + else: + return Expression(SymbolOr, *elements) + else: + return SymbolFalse + + +class Nand(Builtin): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Nand.html</url> + + <dl> + <dt>'Nand[$expr1$, $expr2$, ...]' + <dt>$expr1$ \u22BC $expr2$ \u22BC ... + <dd> Implements the logical NAND function. The same as 'Not[And['$expr1$, $expr2$, ...']]' + </dl> + >> Nand[True, False] = True + """ - #> AnyTrue[{}, EvenQ] + operator = "\u22BC" + rules = { + "Nand[expr___]": "Not[And[expr]]", + } + summary_text = "negation of logic conjunction" + + +class Nor(Builtin): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Nor.html</url> + + <dl> + <dt>'Nor[$expr1$, $expr2$, ...]' + <dt>$expr1$ \u22BD $expr2$ \u22BD ... + <dd>Implements the logical NOR function. The same as 'Not[Or['$expr1$, $expr2$, ...']]' + </dl> + >> Nor[True, False] = False """ - summary_text = "some of the elements are True" + operator = "\u22BD" + rules = { + "Nor[expr___]": "Not[Or[expr]]", + } + summary_text = "negation of logic (inclusive) disjunction" - def _short_circuit(self, what): - if what: - raise _ShortCircuit(SymbolTrue) - def _no_short_circuit(self): - return SymbolFalse +class Not(PrefixOperator): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Not.html</url> + <dl> + <dt>'Not[$expr$]' + <dt>'!$expr$' + <dd>negates the logical expression $expr$. + </dl> -class AllTrue(_ManyTrue): + >> !True + = False + >> !False + = True + >> !b + = !b """ - <url>:WMA link:https://reference.wolfram.com/language/ref/AllTrue.html</url> + + operator = "!" + precedence = 230 + + rules = { + "Not[True]": "False", + "Not[False]": "True", + "Not[Not[expr_]]": "expr", + } + summary_text = "logic negation" + + +class True_(Predefined): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/True.html</url> <dl> - <dt>'AllTrue[{$expr1$, $expr2$, ...}, $test$]' - <dd>returns True if all applications of $test$ to $expr1$, $expr2$, ... evaluate to True. - <dt>'AllTrue[$list$, $test$, $level$]' - <dd>returns True if all applications of $test$ to items of $list$ at $level$ evaluate to True. - <dt>'AllTrue[$test$]' - <dd>gives an operator that may be applied to expressions. + <dt>'True' + <dd>represents the Boolean true value. </dl> + """ - >> AllTrue[{2, 4, 6}, EvenQ] - = True + attributes = A_LOCKED | A_PROTECTED + name = "True" + summary_text = "boolean constant for True" - >> AllTrue[{2, 4, 7}, EvenQ] + +class Xor(BinaryOperator): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Xor.html</url> + + <dl> + <dt>'Xor[$expr1$, $expr2$, ...]' + <dt>$expr1$ \u22BB $expr2$ \u22BB ... + + <dd>evaluates each expression in turn, returning 'True' + as soon as not all expressions evaluate to the same value. If all + expressions evaluate to the same value, 'Xor' returns 'False'. + </dl> + + >> Xor[False, True] + = True + >> Xor[True, True] = False - #> AllTrue[{}, EvenQ] + If an expression does not evaluate to 'True' or 'False', 'Xor' + returns a result in symbolic form: + >> Xor[a, False, b] + = a \u22BB b + #> Xor[] + = False + #> Xor[a] + = a + #> Xor[False] + = False + #> Xor[True] = True + #> Xor[a, b] + = a \u22BB b """ - summary_text = "all the elements are True" + attributes = A_FLAT | A_ONE_IDENTITY | A_ORDERLESS | A_PROTECTED + operator = "\u22BB" + precedence = 215 + summary_text = "logic (exclusive) disjunction" - def _short_circuit(self, what): - if not what: - raise _ShortCircuit(SymbolFalse) + def apply(self, args, evaluation): + "Xor[args___]" - def _no_short_circuit(self): - return SymbolTrue + args = args.get_sequence() + elements = [] + flag = True + for arg in args: + result = arg.evaluate(evaluation) + if result is SymbolTrue: + flag = not flag + elif result != SymbolFalse: + elements.append(result) + if elements and flag: + if len(elements) == 1: + return elements[0] + else: + return Expression(SymbolXor, *elements) + elif elements and not flag: + if len(elements) == 1: + return Expression(SymbolNot, elements[0]) + else: + return Expression(SymbolNot, Expression(SymbolXor, *elements)) + else: + return Symbol(repr(not flag)) diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 37383dc5b..f1bc2431d 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -69,6 +69,7 @@ SymbolE = Symbol("System`E") SymbolEdgeForm = Symbol("System`EdgeForm") SymbolEqual = Symbol("System`Equal") +SymbolEquivalent = Symbol("System`Equivalent") SymbolEulerGamma = Symbol("System`EulerGamma") SymbolExpandAll = Symbol("System`ExpandAll") SymbolExport = Symbol("System`Export") From f79c88fd26fcfd49141c2dbbc3a0c7f2d1f1b427 Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Wed, 21 Dec 2022 07:45:19 -0300 Subject: [PATCH 075/121] last bunch and tests --- mathics/builtin/binary/io.py | 1 - mathics/builtin/box/layout.py | 14 ++++++++++ mathics/builtin/colors/color_directives.py | 20 +++++++++++++ mathics/builtin/colors/named_colors.py | 23 +++++++++++++++ mathics/builtin/drawing/graphics3d.py | 12 ++++++++ mathics/builtin/drawing/image.py | 2 -- mathics/builtin/drawing/splines.py | 4 +++ mathics/builtin/drawing/uniform_polyhedra.py | 10 +++++++ mathics/builtin/makeboxes.py | 6 ++++ mathics/builtin/procedural.py | 28 +++++++++++++++++++ .../test_summary_text.py | 7 +++++ 11 files changed, 124 insertions(+), 3 deletions(-) diff --git a/mathics/builtin/binary/io.py b/mathics/builtin/binary/io.py index 077ad3ccc..9dcd5fb41 100644 --- a/mathics/builtin/binary/io.py +++ b/mathics/builtin/binary/io.py @@ -31,7 +31,6 @@ class _BinaryFormat: """ - Container for BinaryRead readers and BinaryWrite writers """ diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index 7337e3894..fa1b7a4cc 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -57,6 +57,8 @@ def to_boxes(x, evaluation: Evaluation, options={}) -> BoxElementMixin: class BoxData(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/BoxData.html</url> + <dl> <dt>'BoxData[...]' <dd>is a low-level representation of the contents of a typesetting @@ -88,6 +90,8 @@ def is_constant_list(list): class FractionBox(BoxExpression): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/FractionBox.html</url> + <dl> <dt>'FractionBox[$x$, $y$]' <dd> FractionBox[x, y] is a low-level formatting construct that represents $\frac{x}{y}$. @@ -159,6 +163,8 @@ def get_array(self, elements, evaluation): class InterpretationBox(BoxExpression): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/InterpretationBox.html</url> + <dl> <dt>'InterpretationBox[{...}, expr]' <dd> is a low-level box construct that displays as boxes, but is interpreted on input as expr. @@ -257,6 +263,7 @@ def to_expression(self) -> Expression: class ShowStringCharacters(Builtin): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/ShowStringCharacters.html</url> <dl> <dt>'ShowStringCharacters' @@ -315,6 +322,7 @@ def to_expression(self): class StyleBox(BoxExpression): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/StyleBox.html</url> <dl> <dt>'StyleBox[boxes, options]' @@ -408,6 +416,8 @@ def to_expression(self): class SubsuperscriptBox(BoxExpression): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/SubsuperscriptBox.html</url> + <dl> <dt>'SubsuperscriptBox[$a$, $b$, $c$]' <dd>is a box construct that represents $a_b^c$. @@ -481,6 +491,8 @@ def to_expression(self): class TagBox(BoxExpression): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/TagBox.html</url> + <dl> <dt>'TagBox[boxes, tag]' <dd> is a low-level box construct that displays as @@ -506,6 +518,8 @@ class TemplateBox(BoxExpression): class TextData(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/TextData.html</url> + <dl> <dt>'TextData[...]' <dd>is a low-level representation of the contents of a textual diff --git a/mathics/builtin/colors/color_directives.py b/mathics/builtin/colors/color_directives.py index 872e93982..9fbe746ae 100644 --- a/mathics/builtin/colors/color_directives.py +++ b/mathics/builtin/colors/color_directives.py @@ -209,6 +209,8 @@ def to_color_space(self, color_space): class CMYKColor(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/CMYKColor.html</url> + <dl> <dt>'CMYKColor[$c$, $m$, $y$, $k$]' <dd>represents a color with the specified cyan, magenta, @@ -226,6 +228,8 @@ class CMYKColor(_ColorObject): class ColorDistance(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ColorDistance.html</url> + <dl> <dt>'ColorDistance[$c1$, $c2$]' <dd>returns a measure of color distance between the colors $c1$ and $c2$. @@ -433,6 +437,8 @@ class ColorError(BoxExpressionError): class GrayLevel(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/GrayLevel.html</url> + <dl> <dt>'GrayLevel[$g$]' <dd>represents a shade of gray specified by $g$, ranging from @@ -449,6 +455,8 @@ class GrayLevel(_ColorObject): class Hue(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Hue.html</url> + <dl> <dt>'Hue[$h$, $s$, $l$, $a$]' <dd>represents the color with hue $h$, saturation $s$, lightness $l$ and opacity $a$. @@ -507,6 +515,8 @@ def trans(t): class LABColor(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/LABColor.html</url> + <dl> <dt>'LABColor[$l$, $a$, $b$]' <dd>represents a color with the specified lightness, red/green and yellow/blue @@ -521,6 +531,8 @@ class LABColor(_ColorObject): class LCHColor(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/LCHColor.html</url> + <dl> <dt>'LCHColor[$l$, $c$, $h$]' <dd>represents a color with the specified lightness, chroma and hue @@ -535,6 +547,8 @@ class LCHColor(_ColorObject): class LUVColor(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/LUVColor.html</url> + <dl> <dt>'LCHColor[$l$, $u$, $v$]' <dd>represents a color with the specified components in the CIE 1976 L*u*v* (CIELUV) color space. @@ -548,6 +562,8 @@ class LUVColor(_ColorObject): class Opacity(_GraphicsDirective): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Opacity.html</url> + <dl> <dt>'Opacity[$level$]' <dd> is a graphics directive that sets the opacity to $level$. @@ -582,6 +598,8 @@ def create_as_style(klass, graphics, item): class RGBColor(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/RGBColor.html</url> + <dl> <dt>'RGBColor[$r$, $g$, $b$]' <dd>represents a color with the specified red, green and blue @@ -608,6 +626,8 @@ def to_rgba(self): class XYZColor(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/XYZColor.html</url> + <dl> <dt>'XYZColor[$x$, $y$, $z$]' <dd>represents a color with the specified components in the CIE 1931 XYZ color space. diff --git a/mathics/builtin/colors/named_colors.py b/mathics/builtin/colors/named_colors.py index f6e4992b7..c5d64c8dc 100644 --- a/mathics/builtin/colors/named_colors.py +++ b/mathics/builtin/colors/named_colors.py @@ -19,6 +19,7 @@ def __init__(self, *args, **kwargs): else: text_name = self.text_name doc = """ + <url>:WMA link:https://reference.wolfram.com/language/ref/%(text_name)s.html</url> <dl> <dt>'%(name)s' <dd>represents the color %(text_name)s in graphics. @@ -42,6 +43,8 @@ def __init__(self, *args, **kwargs): class Black(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Black.html</url> + >> Black = RGBColor[0, 0, 0] """ @@ -51,6 +54,8 @@ class Black(_ColorObject): class Blue(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Blue.html</url> + >> Blue = RGBColor[0, 0, 1] """ @@ -60,6 +65,8 @@ class Blue(_ColorObject): class Brown(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Brown.html</url> + >> Brown = RGBColor[0.6, 0.4, 0.2] """ @@ -69,6 +76,8 @@ class Brown(_ColorObject): class Cyan(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Cyan.html</url> + >> Cyan = RGBColor[0, 1, 1] """ @@ -78,6 +87,8 @@ class Cyan(_ColorObject): class Gray(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Gray.html</url> + >> Gray = GrayLevel[0.5] """ @@ -87,6 +98,8 @@ class Gray(_ColorObject): class Green(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Green.html</url> + >> Green = RGBColor[0, 1, 0] """ @@ -96,6 +109,8 @@ class Green(_ColorObject): class Magenta(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Magenta.html</url> + >> Magenta = RGBColor[1, 0, 1] """ @@ -105,6 +120,8 @@ class Magenta(_ColorObject): class LightBlue(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/LightBlue.html</url> + >> Graphics[{LightBlue, EdgeForm[Black], Disk[]}] = -Graphics- @@ -182,6 +199,8 @@ class Orange(_ColorObject): class Red(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Red.html</url> + >> Red = RGBColor[1, 0, 0] """ @@ -191,6 +210,8 @@ class Red(_ColorObject): class Yellow(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Yellow.html</url> + >> Yellow = RGBColor[1, 1, 0] """ @@ -200,6 +221,8 @@ class Yellow(_ColorObject): class White(_ColorObject): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/White.html</url> + >> White = GrayLevel[1] """ diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 78f2114f2..32a7746ad 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -65,6 +65,8 @@ def get_default_face_color(self): class Graphics3D(Graphics): r""" + <url>:WMA link:https://reference.wolfram.com/language/ref/Graphics3D.html</url> + <dl> <dt>'Graphics3D[$primitives$, $options$]' <dd>represents a three-dimensional graphic. @@ -184,6 +186,8 @@ def _apply_boxscaling(self, boxscale): class Sphere(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Sphere.html</url> + <dl> <dt>'Sphere[{$x$, $y$, $z$}]' <dd>is a sphere of radius 1 centered at the point {$x$, $y$, $z$}. @@ -209,6 +213,8 @@ class Sphere(Builtin): class Cone(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Cone.html</url> + <dl> <dt>'Cone[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}]' <dd>represents a cone of radius 1. @@ -254,6 +260,8 @@ def apply_check(self, positions, radius, evaluation): class Cuboid(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Cuboid.html</url> + Cuboid also known as interval, rectangle, square, cube, rectangular parallelepiped, tesseract, orthotope, and box. <dl> <dt>'Cuboid[$p_min$]' @@ -306,6 +314,8 @@ def apply_check(self, positions, evaluation): class Cylinder(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Cylinder.html</url> + <dl> <dt>'Cylinder[{{$x1$, $y1$, $z1$}, {$x2$, $y2$, $z2$}}]' <dd>represents a cylinder of radius 1. @@ -351,6 +361,8 @@ def apply_check(self, positions, radius, evaluation): class Tube(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Tube.html</url> + <dl> <dt>'Tube[{$p1$, $p2$, ...}]' <dd>represents a tube passing through $p1$, $p2$, ... with radius 1. diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index efd1da5bf..43be18a5e 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -93,8 +93,6 @@ class _ImageTest(Test): class _SkimageBuiltin(_ImageBuiltin): """ - ## <url>:native:</url> - Image Builtins that require scikit-image. """ diff --git a/mathics/builtin/drawing/splines.py b/mathics/builtin/drawing/splines.py index 299862871..1bc7628cc 100644 --- a/mathics/builtin/drawing/splines.py +++ b/mathics/builtin/drawing/splines.py @@ -19,6 +19,7 @@ # https://github.com/Tarheel-Formal-Methods/kaa class BernsteinBasis(Builtin): """ + <url>:Bernstein polynomial basis: https://en.wikipedia.org/wiki/Bernstein_polynomial</url> (<url>:SciPy: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.BPoly.html</url> :WMA: A Bernstein is a polynomial that is a linear combination of Bernstein basis polynomials. @@ -44,6 +45,7 @@ class BernsteinBasis(Builtin): class BezierFunction(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/BezierFunction.html</url> <dl> <dt>'BezierFunction[{$pt_1$, $pt_2$, ...}]' <dd>returns a Bézier function for the curve defined by points $pt_i$. @@ -74,6 +76,8 @@ class BezierFunction(Builtin): class BezierCurve(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/BezierCurve.html</url> + <dl> <dt>'BezierCurve[{$pt_1$, $pt_2$ ...}]' <dd>represents a Bézier curve with control points $p_i$. diff --git a/mathics/builtin/drawing/uniform_polyhedra.py b/mathics/builtin/drawing/uniform_polyhedra.py index f45b40306..2c69fb93d 100644 --- a/mathics/builtin/drawing/uniform_polyhedra.py +++ b/mathics/builtin/drawing/uniform_polyhedra.py @@ -18,6 +18,8 @@ class UniformPolyhedron(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/UniformPolyhedron.html</url> + <dl> <dt>'UniformPolyhedron["name"]' <dd>return a uniform polyhedron with the given name. @@ -54,6 +56,8 @@ def apply(self, name, positions, edgelength, evaluation): class Dodecahedron(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Dodecahedron.html</url> + <dl> <dt>'Dodecahedron[]' <dd>a regular dodecahedron centered at the origin with unit edge length. @@ -73,6 +77,8 @@ class Dodecahedron(Builtin): class Icosahedron(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Icosahedron.html</url> + <dl> <dt>'Icosahedron[]' <dd>a regular Icosahedron centered at the origin with unit edge length. @@ -92,6 +98,8 @@ class Icosahedron(Builtin): class Octahedron(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Octahedron.html</url> + <dl> <dt>'Octahedron[]' <dd>a regular octahedron centered at the origin with unit edge length. @@ -111,6 +119,8 @@ class Octahedron(Builtin): class Tetrahedron(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Tetrahedron.html</url> + <dl> <dt>'Tetrahedron[]' <dd>a regular tetrahedron centered at the origin with unit edge length. diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index 7c00d4dd9..6890ad69d 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -310,6 +310,8 @@ def split_string(s, start, step): class BoxForms_(Predefined): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/$BoxForms.html</url> + <dl> <dt> <dd>$BoxForms is the list of box formats. @@ -327,6 +329,8 @@ class BoxForms_(Predefined): class MakeBoxes(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/MakeBoxes.html</url> + <dl> <dt>'MakeBoxes[$expr$]' <dd>is a low-level formatting primitive that converts $expr$ @@ -547,6 +551,8 @@ def format_operator(operator) -> Union[String, BaseElement]: class ToBoxes(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ToBoxes.html</url> + <dl> <dt>'ToBoxes[$expr$]' <dd>evaluates $expr$ and converts the result to box form. diff --git a/mathics/builtin/procedural.py b/mathics/builtin/procedural.py index 31434471e..4910554d9 100644 --- a/mathics/builtin/procedural.py +++ b/mathics/builtin/procedural.py @@ -42,6 +42,8 @@ class Abort(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Abort.html</url> + <dl> <dt>'Abort[]' <dd>aborts an evaluation completely and returns '$Aborted'. @@ -61,6 +63,8 @@ def apply(self, evaluation): class Break(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Break.html</url> + <dl> <dt>'Break[]' <dd>exits a 'For', 'While', or 'Do' loop. @@ -85,6 +89,8 @@ def apply(self, evaluation): class Catch(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Catch.html</url> + <dl> <dt>'Catch[$expr$]' <dd> returns the argument of the first 'Throw' generated in the evaluation of $expr$. @@ -143,6 +149,8 @@ def apply_with_form_and_fn(self, expr, form, f, evaluation): class CompoundExpression(BinaryOperator): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/CompoundExpression.html</url> + <dl> <dt>'CompoundExpression[$e1$, $e2$, ...]' <dt>'$e1$; $e2$; ...' @@ -214,6 +222,8 @@ def apply(self, expr, evaluation): class Continue(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Continue.html</url> + <dl> <dt>'Continue[]' <dd>continues with the next iteration in a 'For', 'While', or 'Do' loop. @@ -240,6 +250,8 @@ def apply(self, evaluation): class Do(_IterationFunction): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Do.html</url> + <dl> <dt>'Do[$expr$, {$max$}]' <dd>evaluates $expr$ $max$ times. @@ -290,6 +302,8 @@ def get_result(self, items): class For(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/For.html</url> + <dl> <dt>'For[$start$, $test$, $incr$, $body$]' <dd>evaluates $start$, and then iteratively $body$ and $incr$ as long as $test$ evaluates to 'True'. @@ -345,6 +359,8 @@ def apply(self, start, test, incr, body, evaluation): class If(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/If.html</url> + <dl> <dt>'If[$cond$, $pos$, $neg$]' <dd>returns $pos$ if $cond$ evaluates to 'True', and $neg$ if it evaluates to 'False'. @@ -402,6 +418,8 @@ def apply_4(self, condition, t, f, u, evaluation): class Interrupt(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Interrupt.html</url> + <dl> <dt>'Interrupt[]' <dd>Interrupt an evaluation and returns '$Aborted'. @@ -421,6 +439,8 @@ def apply(self, evaluation): class Return(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Return.html</url> + <dl> <dt>'Return[$expr$]' <dd>aborts a function call and returns $expr$. @@ -468,6 +488,8 @@ def apply(self, expr, evaluation): class Switch(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Switch.html</url> + <dl> <dt>'Switch[$expr$, $pattern1$, $value1$, $pattern2$, $value2$, ...]' <dd>yields the first $value$ for which $expr$ matches the corresponding $pattern$. @@ -521,6 +543,8 @@ def apply(self, expr, rules, evaluation): class Which(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Which.html</url> + <dl> <dt>'Which[$cond1$, $expr1$, $cond2$, $expr2$, ...]' <dd>yields $expr1$ if $cond1$ evaluates to 'True', $expr2$ if $cond2$ evaluates to 'True', etc. @@ -579,6 +603,8 @@ def apply(self, items, evaluation): class While(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/While.html</url> + <dl> <dt>'While[$test$, $body$]' <dd>evaluates $body$ as long as $test$ evaluates to 'True'. @@ -621,6 +647,8 @@ def apply(self, test, body, evaluation): class Throw(Builtin): """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Throw.html</url> + <dl> <dt>'Throw[`value`]' <dd> stops evaluation and returns `value` as the value of the nearest enclosing 'Catch'. diff --git a/test/consistency-and-style/test_summary_text.py b/test/consistency-and-style/test_summary_text.py index e4c7f9f71..680663c4e 100644 --- a/test/consistency-and-style/test_summary_text.py +++ b/test/consistency-and-style/test_summary_text.py @@ -167,6 +167,13 @@ def check_well_formatted_docstring(docstr: str, instance: Builtin, module_name: docstr.count("</dd>") == 0 ), f"unnecesary </dd> field {instance.get_name()} from {module_name}" + assert ( + docstr.count("<url>") > 0 + ), f"missing <url> field {instance.get_name()} from {module_name}" + assert docstr.count("<url>") == docstr.lower().count( + "</url>" + ), f"unbalanced <url> </url> tags in {instance.get_name()} from {module_name}" + def is_builtin(var: object) -> bool: return ( From e96a0d654968136f593625cced695b9a411e2c19 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 10:26:38 -0500 Subject: [PATCH 076/121] url line shortening apply->eval, isort, etc. --- mathics/builtin/box/layout.py | 43 ++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index fa1b7a4cc..d09146a8c 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -90,7 +90,9 @@ def is_constant_list(list): class FractionBox(BoxExpression): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/FractionBox.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/FractionBox.html</url> <dl> <dt>'FractionBox[$x$, $y$]' @@ -163,11 +165,14 @@ def get_array(self, elements, evaluation): class InterpretationBox(BoxExpression): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/InterpretationBox.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/InterpretationBox.html</url> <dl> <dt>'InterpretationBox[{...}, expr]' - <dd> is a low-level box construct that displays as boxes, but is interpreted on input as expr. + <dd> is a low-level box construct that displays as boxes, but is \ + interpreted on input as expr. </dl> >> A = InterpretationBox["Pepe", 4] @@ -192,9 +197,13 @@ def apply_display(boxexpr, evaluation): class RowBox(BoxExpression): """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/RowBox.html</url> <dl> <dt>'RowBox[{...}]' - <dd>is a box construct that represents a sequence of boxes arranged in a horizontal row. + <dd>is a box construct that represents a sequence of boxes arranged in \ + a horizontal row. </dl> """ @@ -263,8 +272,9 @@ def to_expression(self) -> Expression: class ShowStringCharacters(Builtin): """ - - <url>:WMA link: https://reference.wolfram.com/language/ref/ShowStringCharacters.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ShowStringCharacters.html</url> <dl> <dt>'ShowStringCharacters' <dd>is an option for Cell that directs whether to display '"' in strings. @@ -283,6 +293,9 @@ class ShowStringCharacters(Builtin): class SqrtBox(BoxExpression): """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/SqrtData.html</url> <dl> <dt>'SqrtBox[$x$]' <dd> is a low-level formatting construct that represents $\\sqrt{x}$. @@ -416,7 +429,9 @@ def to_expression(self): class SubsuperscriptBox(BoxExpression): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/SubsuperscriptBox.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/SubsuperscriptBox.html</url> <dl> <dt>'SubsuperscriptBox[$a$, $b$, $c$]' @@ -456,6 +471,9 @@ def to_expression(self): class SuperscriptBox(BoxExpression): """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/SuperscriptBox.html</url> <dl> <dt>'SuperscriptBox[$a$, $b$]' <dd>is a box construct that represents $a^b$. @@ -506,6 +524,9 @@ class TagBox(BoxExpression): class TemplateBox(BoxExpression): """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/TemplateBox.html</url> <dl> <dt>'TemplateBox[{$box_1$, $box_2$,...}, tag]' <dd>is a low-level box structure that parameterizes the display and evaluation of the boxes $box_i$ . @@ -518,7 +539,9 @@ class TemplateBox(BoxExpression): class TextData(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/TextData.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/TextData.html</url> <dl> <dt>'TextData[...]' @@ -532,6 +555,10 @@ class TextData(Builtin): class TooltipBox(BoxExpression): """ + ## <url> + ## :WMA link: + ## https://reference.wolfram.com/language/ref/TooltipBox.html</url> + <dl> <dt>'TooltipBox[{...}]' <dd>undocumented... From 690908ca1472622cabcab219feb9a66d3cd3b512 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 10:50:32 -0500 Subject: [PATCH 077/121] Add isort as a pre-commit hook and as a CI check --- .../{autoblack.yml => isort-and-black-checks.yml} | 12 ++++++------ .isort.cfg | 11 +++++++++++ .pre-commit-config.yaml | 5 +++++ 3 files changed, 22 insertions(+), 6 deletions(-) rename .github/workflows/{autoblack.yml => isort-and-black-checks.yml} (81%) create mode 100644 .isort.cfg diff --git a/.github/workflows/autoblack.yml b/.github/workflows/isort-and-black-checks.yml similarity index 81% rename from .github/workflows/autoblack.yml rename to .github/workflows/isort-and-black-checks.yml index 40d705068..3c6c41b38 100644 --- a/.github/workflows/autoblack.yml +++ b/.github/workflows/isort-and-black-checks.yml @@ -3,7 +3,7 @@ # Othewrwise, Black is run and its changes are committed back to the incoming pull request. # https://github.com/cclauss/autoblack -name: autoblack +name: isort and black check on: [pull_request] jobs: build: @@ -14,11 +14,11 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.9 - - name: Install click - run: pip install 'click==8.0.4' - - name: Install Black - run: pip install 'black==22.3.0' - - name: Run black --check . + - name: Install click, black and isort + run: pip install 'click==8.0.4' 'black==22.3.0' 'isort==5.10.1' + - name: Run isort --check . + run: isort --check . + - name: Run isort --check . run: black --check . - name: If needed, commit black changes to the pull request if: failure() diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 000000000..b865cda23 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,11 @@ +[settings] +multi_line_output = 3 +include_trailing_comma = True +force_grid_wrap = 0 +use_parentheses = True +line_length = 88 +known_crunch = cr, zz9d, zz9lib, pycrunch, silhouette +sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,CRUNCH,LOCALFOLDER +default_section = THIRDPARTY +combine_as_imports = 1 +profile = black diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 77a20ff26..3d0c39a88 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,6 +9,11 @@ repos: stages: [commit] - id: end-of-file-fixer stages: [commit] +- repo: https://github.com/pycqa/isort + rev: 5.10.1 + hooks: + - id: isort + stages: [commit] - repo: https://github.com/psf/black rev: 22.3.0 hooks: From c727fc0066b4b0490a0d9472b0a3c913390ee3ef Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 10:57:08 -0500 Subject: [PATCH 078/121] isort imports on all Python files --- admin-tools/build_and_check_manifest.py | 3 +- admin-tools/time-mathmp-sympy-fns.py | 3 +- mathics/__init__.py | 5 ++- mathics/algorithm/clusters.py | 4 +- mathics/algorithm/integrators.py | 11 +---- mathics/algorithm/introselect.py | 2 +- mathics/algorithm/optimizers.py | 16 ++------ mathics/algorithm/parts.py | 3 +- mathics/algorithm/series.py | 11 +---- mathics/benchmark.py | 7 +--- mathics/builtin/arithfns/basic.py | 17 ++------ mathics/builtin/arithmetic.py | 38 +++++++---------- mathics/builtin/assignments/assignment.py | 2 - mathics/builtin/assignments/clear.py | 18 ++------ mathics/builtin/assignments/upvalues.py | 2 +- mathics/builtin/box/graphics.py | 21 +++------- mathics/builtin/box/graphics3d.py | 20 ++++----- mathics/builtin/box/layout.py | 9 +--- mathics/builtin/box/uniform_polyhedra.py | 7 ++-- mathics/builtin/colors/color_directives.py | 8 +--- mathics/builtin/colors/color_internals.py | 25 +++++------ mathics/builtin/colors/named_colors.py | 1 - mathics/builtin/compilation.py | 11 ++--- mathics/builtin/distance/numeric.py | 1 - mathics/builtin/distance/stringdata.py | 3 -- mathics/builtin/drawing/graphics3d.py | 6 +-- mathics/builtin/drawing/graphics_internals.py | 8 +--- mathics/builtin/drawing/plot.py | 2 +- mathics/builtin/drawing/splines.py | 1 - mathics/builtin/files_io/files.py | 41 ++++++++----------- mathics/builtin/files_io/filesystem.py | 3 +- mathics/builtin/files_io/importexport.py | 28 +++---------- mathics/builtin/forms/base.py | 1 - mathics/builtin/forms/other.py | 3 +- mathics/builtin/forms/output.py | 17 ++------ mathics/builtin/forms/variables.py | 5 +-- mathics/builtin/functional/application.py | 10 +---- .../builtin/functional/apply_fns_to_lists.py | 20 ++------- mathics/builtin/functional/composition.py | 7 +--- mathics/builtin/inference.py | 13 ++---- mathics/builtin/inout.py | 14 +------ mathics/builtin/intfns/divlike.py | 15 ++++--- mathics/builtin/intfns/misc.py | 1 - mathics/builtin/intfns/recurrence.py | 4 +- mathics/builtin/list/associations.py | 11 +---- mathics/builtin/list/constructing.py | 11 +---- mathics/builtin/list/rearrange.py | 13 +----- mathics/builtin/lists.py | 26 ++---------- mathics/builtin/makeboxes.py | 37 ++++------------- mathics/builtin/messages.py | 23 ++--------- mathics/builtin/numbers/diffeqns.py | 1 + mathics/builtin/numbers/exp.py | 4 +- mathics/builtin/numbers/hyperbolic.py | 2 +- mathics/builtin/numbers/linalg.py | 10 ++--- mathics/builtin/numbers/numbertheory.py | 2 +- mathics/builtin/numbers/trig.py | 11 ++--- mathics/builtin/numeric.py | 2 +- mathics/builtin/numpy_utils/__init__.py | 1 + mathics/builtin/numpy_utils/with_numpy.py | 6 +-- mathics/builtin/numpy_utils/without_numpy.py | 15 +++---- mathics/builtin/optimization.py | 1 - mathics/builtin/options.py | 14 +------ mathics/builtin/patterns.py | 26 +++--------- mathics/builtin/physchemdata.py | 8 +--- mathics/builtin/procedural.py | 10 +---- mathics/builtin/pymimesniffer/magic.py | 4 +- mathics/builtin/pympler/asizeof.py | 7 ++-- mathics/builtin/quantities.py | 23 ++++------- mathics/builtin/quantum_mechanics/angular.py | 11 ++--- mathics/builtin/recurrence.py | 3 +- mathics/builtin/scipy_utils/integrators.py | 3 +- mathics/builtin/scipy_utils/optimizers.py | 17 +++----- mathics/builtin/scoping.py | 16 ++------ mathics/builtin/sparse.py | 3 -- mathics/builtin/specialfns/erf.py | 7 +--- mathics/builtin/specialfns/zeta.py | 1 - mathics/builtin/statistics/dependency.py | 4 +- mathics/builtin/statistics/location.py | 2 +- mathics/builtin/string/characters.py | 1 - mathics/builtin/string/charcodes.py | 8 +--- mathics/builtin/string/operations.py | 29 +++---------- mathics/builtin/structure.py | 28 +++---------- mathics/builtin/system.py | 15 ++----- mathics/builtin/tensors.py | 15 ++----- mathics/builtin/trace.py | 17 +++----- .../vectors/vector_space_operations.py | 4 +- mathics/compile/__init__.py | 4 +- mathics/compile/compile.py | 6 +-- mathics/compile/ir.py | 8 ++-- mathics/compile/utils.py | 5 ++- mathics/core/assignment.py | 9 ++-- mathics/core/convert/expression.py | 1 - mathics/core/convert/function.py | 18 +++----- mathics/core/convert/mpmath.py | 11 ++--- mathics/core/convert/op.py | 4 +- mathics/core/convert/python.py | 12 ++---- mathics/core/convert/sympy.py | 18 ++++---- mathics/core/definitions.py | 28 ++++--------- mathics/core/evaluation.py | 17 ++------ mathics/core/expression.py | 10 ++--- mathics/core/formatter.py | 3 +- mathics/core/number.py | 13 +++--- mathics/core/parser/__init__.py | 2 +- mathics/core/parser/convert.py | 17 +++----- mathics/core/parser/operators.py | 1 - mathics/core/parser/parser.py | 19 ++++----- mathics/core/parser/util.py | 4 +- mathics/core/pattern.py | 7 ++-- mathics/core/pymathics.py | 7 +--- mathics/core/read.py | 4 +- mathics/core/rules.py | 5 +-- mathics/core/streams.py | 7 ++-- mathics/core/structure.py | 4 +- mathics/doc/common_doc.py | 4 +- mathics/doc/latex/doc2latex.py | 6 +-- mathics/docpipeline.py | 8 +--- mathics/eval/makeboxes.py | 12 +++--- mathics/eval/nevaluator.py | 6 +-- mathics/eval/numerify.py | 3 +- mathics/eval/plot.py | 2 +- mathics/format/__init__.py | 3 +- mathics/format/asy.py | 11 ++--- mathics/format/json.py | 13 ++---- mathics/format/latex.py | 11 ++--- mathics/format/mathml.py | 17 ++++---- mathics/format/svg.py | 6 +-- mathics/format/text.py | 12 ++---- mathics/main.py | 10 ++--- mathics/session.py | 7 ++-- mathics/settings.py | 5 ++- mathics/timing.py | 4 +- setup.py | 2 +- test/builtin/arithmetic/test_assumptions.py | 3 +- test/builtin/atomic/test_strings.py | 1 + test/builtin/colors/test_colors.py | 2 - test/builtin/files_io/test_files.py | 3 +- test/builtin/files_io/test_importexport.py | 8 ++-- test/builtin/image/test_image.py | 3 +- test/builtin/numbers/test_calculus.py | 7 ++-- test/builtin/numbers/test_nintegrate.py | 1 - test/builtin/numbers/test_randomnumbers.py | 3 +- test/builtin/test_assignment.py | 2 +- test/builtin/test_attributes.py | 4 +- test/builtin/test_comparison.py | 3 +- test/builtin/test_datentime.py | 5 +-- test/builtin/test_makeboxes.py | 3 +- test/builtin/test_procedural.py | 4 +- .../test_duplicate_builtins.py | 6 ++- .../test_summary_text.py | 10 ++--- test/core/parser/test_convert.py | 12 +----- test/core/parser/test_parser.py | 4 +- test/core/parser/test_util.py | 1 - test/core/test_arithmetic.py | 4 +- test/core/test_atoms.py | 14 +++---- test/core/test_expression.py | 5 ++- test/core/test_expression_constructor.py | 4 +- test/core/test_flatten_head.py | 13 +----- test/core/test_sympy_python_convert.py | 6 +-- test/format/test_asy.py | 7 ++-- test/format/test_format.py | 5 ++- test/format/test_svg.py | 9 ++-- test/helper.py | 3 +- test/package/test_combinatorica.py | 3 +- test/test_constrmatrix.py | 3 +- test/test_context.py | 3 +- test/test_custom_boxexpression.py | 4 +- test/test_deletecases.py | 4 +- test/test_evaluation.py | 5 ++- test/test_evaluators.py | 2 +- test/test_help.py | 3 +- test/test_inout.py | 3 +- test/test_main.py | 6 +-- test/test_numericq.py | 3 +- test/test_numpy_utils.py | 22 +++++----- test/test_returncode.py | 3 +- test/test_series.py | 3 +- test/test_settings.py | 3 +- test/test_structure.py | 3 +- test/test_system_info.py | 4 +- 179 files changed, 502 insertions(+), 1015 deletions(-) diff --git a/admin-tools/build_and_check_manifest.py b/admin-tools/build_and_check_manifest.py index 7b44e0728..9057a0f34 100755 --- a/admin-tools/build_and_check_manifest.py +++ b/admin-tools/build_and_check_manifest.py @@ -1,8 +1,9 @@ #!/usr/bin/env python -from mathics.builtin import name_is_builtin_symbol, modules, Builtin import sys +from mathics.builtin import Builtin, modules, name_is_builtin_symbol + def generate_available_builtins_names(): msg = "" diff --git a/admin-tools/time-mathmp-sympy-fns.py b/admin-tools/time-mathmp-sympy-fns.py index 823224433..f6e4b2014 100644 --- a/admin-tools/time-mathmp-sympy-fns.py +++ b/admin-tools/time-mathmp-sympy-fns.py @@ -3,9 +3,10 @@ Program to time mpmath pi vs sympy pi """ +import math from timeit import timeit + import mpmath -import math import sympy PRECISION = 100 diff --git a/mathics/__init__.py b/mathics/__init__.py index 5cf496658..d7f66d8bc 100644 --- a/mathics/__init__.py +++ b/mathics/__init__.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- -import sys import platform -import sympy +import sys + import mpmath import numpy +import sympy from mathics.version import __version__ diff --git a/mathics/algorithm/clusters.py b/mathics/algorithm/clusters.py index c5e8d0495..203224577 100644 --- a/mathics/algorithm/clusters.py +++ b/mathics/algorithm/clusters.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -import random -from itertools import chain, islice import bisect import math +import random +from itertools import chain, islice from mpmath import fsum diff --git a/mathics/algorithm/integrators.py b/mathics/algorithm/integrators.py index ca6813287..cd0c777a9 100644 --- a/mathics/algorithm/integrators.py +++ b/mathics/algorithm/integrators.py @@ -2,18 +2,11 @@ import numpy as np -from mathics.core.number import machine_epsilon +from mathics.core.atoms import Integer, Integer0, Number from mathics.core.expression import Expression -from mathics.core.atoms import ( - Integer, - Integer0, - Number, -) - from mathics.core.list import ListExpression +from mathics.core.number import machine_epsilon from mathics.core.symbols import Symbol, SymbolPlus, SymbolSequence, SymbolTimes - - from mathics.core.systemsymbols import ( SymbolBlank, SymbolComplex, diff --git a/mathics/algorithm/introselect.py b/mathics/algorithm/introselect.py index 32e37e9e7..c64b905c7 100644 --- a/mathics/algorithm/introselect.py +++ b/mathics/algorithm/introselect.py @@ -164,8 +164,8 @@ def introselect(a, k): # changes a if __name__ == "__main__": - import random import itertools + import random def test_algorithm(l, r_max, name, f): a = [random.randint(-r_max, r_max) for _ in range(l)] diff --git a/mathics/algorithm/optimizers.py b/mathics/algorithm/optimizers.py index 6ad599e94..71d5a9981 100644 --- a/mathics/algorithm/optimizers.py +++ b/mathics/algorithm/optimizers.py @@ -3,31 +3,22 @@ from typing import Optional from mathics.builtin.scoping import dynamic_scoping - - from mathics.core.atoms import ( - String, Integer, Integer0, - IntegerM1, Integer1, Integer2, Integer3, Integer10, + IntegerM1, Number, Real, + String, ) from mathics.core.convert.python import from_python from mathics.core.evaluation import Evaluation -from mathics.eval.nevaluator import eval_N from mathics.core.expression import Expression -from mathics.core.symbols import ( - BaseElement, - SymbolPlus, - SymbolTimes, - SymbolTrue, -) - +from mathics.core.symbols import BaseElement, SymbolPlus, SymbolTimes, SymbolTrue from mathics.core.systemsymbols import ( SymbolAutomatic, SymbolD, @@ -37,6 +28,7 @@ SymbolLog, SymbolNone, ) +from mathics.eval.nevaluator import eval_N def find_minimum_newton1d(f, x0, x, opts, evaluation) -> (Number, bool): diff --git a/mathics/algorithm/parts.py b/mathics/algorithm/parts.py index 4d2cba436..0ea7fefd7 100644 --- a/mathics/algorithm/parts.py +++ b/mathics/algorithm/parts.py @@ -17,10 +17,9 @@ ) from mathics.core.expression import Expression from mathics.core.list import ListExpression +from mathics.core.subexpression import SubExpression from mathics.core.symbols import Atom, Symbol, SymbolList from mathics.core.systemsymbols import SymbolDirectedInfinity, SymbolInfinity -from mathics.core.subexpression import SubExpression - SymbolNothing = Symbol("Nothing") diff --git a/mathics/algorithm/series.py b/mathics/algorithm/series.py index f445d2983..c627fd7ec 100644 --- a/mathics/algorithm/series.py +++ b/mathics/algorithm/series.py @@ -1,16 +1,9 @@ -from mathics.core.symbols import ( - Atom, - Symbol, - SymbolPlus, - SymbolPower, - SymbolTimes, -) - from mathics.core.atoms import Integer, Integer0, Rational -from mathics.core.expression import Expression from mathics.core.convert.expression import to_mathics_list +from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.rules import Pattern +from mathics.core.symbols import Atom, Symbol, SymbolPlus, SymbolPower, SymbolTimes from mathics.core.systemsymbols import ( SymbolComplexInfinity, SymbolD, diff --git a/mathics/benchmark.py b/mathics/benchmark.py index cd26ae7e4..c2f1bc44c 100644 --- a/mathics/benchmark.py +++ b/mathics/benchmark.py @@ -5,20 +5,17 @@ import time from argparse import ArgumentParser - try: - from statistics import mean - from statistics import median_low as median + from statistics import mean, median_low as median except ImportError: mean = lambda l: sum(l) / len(l) median = lambda l: sorted(l)[len(l) // 2] import mathics -from mathics.core.parser import parse, MathicsMultiLineFeeder, MathicsSingleLineFeeder from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation - +from mathics.core.parser import MathicsMultiLineFeeder, MathicsSingleLineFeeder, parse # Default number of times to repeat each benchmark. None -> Automatic TESTS_PER_BENCHMARK = None diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index 5900ac3df..df11da85b 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -7,18 +7,11 @@ """ -import sympy import mpmath +import sympy from mathics.builtin.arithmetic import _MPMathFunction, create_infix -from mathics.builtin.base import ( - Builtin, - BinaryOperator, - PrefixOperator, - SympyFunction, -) - - +from mathics.builtin.base import BinaryOperator, Builtin, PrefixOperator, SympyFunction from mathics.core.atoms import ( Complex, Integer, @@ -43,14 +36,12 @@ A_PROTECTED, A_READ_PROTECTED, ) - from mathics.core.convert.expression import to_expression from mathics.core.convert.mpmath import from_mpmath from mathics.core.convert.sympy import from_sympy - from mathics.core.expression import ElementsProperties, Expression from mathics.core.list import ListExpression -from mathics.core.number import min_prec, dps +from mathics.core.number import dps, min_prec from mathics.core.symbols import ( Symbol, SymbolDivide, @@ -73,8 +64,6 @@ SymbolPattern, SymbolSequence, ) - - from mathics.eval.nevaluator import eval_N from mathics.eval.numerify import numerify diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 82bb512c2..d1d09e451 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -13,32 +13,15 @@ sort_order = "mathics.builtin.mathematical-functions" -import sympy -import mpmath from functools import lru_cache -from mathics.core.attributes import ( - A_HOLD_ALL, - A_HOLD_REST, - A_LISTABLE, - A_NO_ATTRIBUTES, - A_NUMERIC_FUNCTION, - A_PROTECTED, -) - -from mathics.eval.nevaluator import eval_N - -from mathics.builtin.base import ( - Builtin, - Predefined, - SympyFunction, - Test, -) +import mpmath +import sympy -from mathics.builtin.inference import get_assumptions_list, evaluate_predicate +from mathics.builtin.base import Builtin, Predefined, SympyFunction, Test +from mathics.builtin.inference import evaluate_predicate, get_assumptions_list from mathics.builtin.lists import _IterationFunction from mathics.builtin.scoping import dynamic_scoping - from mathics.core.atoms import ( Complex, Integer, @@ -50,13 +33,21 @@ Real, String, ) +from mathics.core.attributes import ( + A_HOLD_ALL, + A_HOLD_REST, + A_LISTABLE, + A_NO_ATTRIBUTES, + A_NUMERIC_FUNCTION, + A_PROTECTED, +) from mathics.core.convert.expression import to_expression from mathics.core.convert.mpmath import from_mpmath from mathics.core.convert.python import from_python -from mathics.core.convert.sympy import from_sympy, SympyExpression, sympy_symbol_prefix +from mathics.core.convert.sympy import SympyExpression, from_sympy, sympy_symbol_prefix from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.number import min_prec, dps, SpecialValueError +from mathics.core.number import SpecialValueError, dps, min_prec from mathics.core.symbols import ( Atom, Symbol, @@ -82,6 +73,7 @@ SymbolTable, SymbolUndefined, ) +from mathics.eval.nevaluator import eval_N @lru_cache(maxsize=4096) diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py index 7d36acadd..88149fcaf 100644 --- a/mathics/builtin/assignments/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -11,14 +11,12 @@ assign_store_rules_by_tag, normalize_lhs, ) - from mathics.core.attributes import ( A_HOLD_ALL, A_HOLD_FIRST, A_PROTECTED, A_SEQUENCE_HOLD, ) - from mathics.core.pymathics import PyMathicsLoadException, eval_load_module from mathics.core.symbols import SymbolNull from mathics.core.systemsymbols import SymbolFailed diff --git a/mathics/builtin/assignments/clear.py b/mathics/builtin/assignments/clear.py index 8dbda4ff5..0e6a069cc 100644 --- a/mathics/builtin/assignments/clear.py +++ b/mathics/builtin/assignments/clear.py @@ -4,10 +4,9 @@ """ -from mathics.builtin.base import ( - Builtin, - PostfixOperator, -) +from mathics.builtin.base import Builtin, PostfixOperator +from mathics.core.assignment import is_protected +from mathics.core.atoms import String from mathics.core.attributes import ( A_HOLD_ALL, A_HOLD_FIRST, @@ -18,13 +17,7 @@ A_READ_PROTECTED, ) from mathics.core.expression import Expression -from mathics.core.symbols import ( - Atom, - Symbol, - SymbolNull, - symbol_set, -) - +from mathics.core.symbols import Atom, Symbol, SymbolNull, symbol_set from mathics.core.systemsymbols import ( SymbolContext, SymbolContextPath, @@ -38,9 +31,6 @@ SymbolUpValues, ) -from mathics.core.assignment import is_protected -from mathics.core.atoms import String - class Clear(Builtin): """ diff --git a/mathics/builtin/assignments/upvalues.py b/mathics/builtin/assignments/upvalues.py index 10ad84860..596947a5e 100644 --- a/mathics/builtin/assignments/upvalues.py +++ b/mathics/builtin/assignments/upvalues.py @@ -10,7 +10,7 @@ """ from mathics.builtin.assignments.assignment import _SetOperator -from mathics.builtin.base import Builtin, BinaryOperator +from mathics.builtin.base import BinaryOperator, Builtin from mathics.core.assignment import get_symbol_values from mathics.core.attributes import ( A_HOLD_ALL, diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index bd586e1ad..102c2a576 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -6,39 +6,31 @@ from math import atan2, ceil, cos, degrees, floor, log10, pi, sin - from mathics.builtin.base import BoxExpression from mathics.builtin.colors.color_directives import ( - _ColorObject, ColorError, Opacity, RGBColor, + _ColorObject, ) -from mathics.builtin.drawing.graphics_internals import _GraphicsElementBox, GLOBALS +from mathics.builtin.drawing.graphics_internals import GLOBALS, _GraphicsElementBox from mathics.builtin.graphics import ( + DEFAULT_POINT_FACTOR, Arrowheads, Coords, - DEFAULT_POINT_FACTOR, Graphics, GraphicsElements, PointSize, _BezierCurve, - _Line, - _Polyline, _data_and_options, _extract_graphics, + _Line, _norm, + _Polyline, _to_float, coords, ) - - -from mathics.core.atoms import ( - Integer, - Real, - String, -) - +from mathics.core.atoms import Integer, Real, String from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED, A_READ_PROTECTED from mathics.core.exceptions import BoxExpressionError from mathics.core.expression import Expression @@ -46,7 +38,6 @@ from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolTrue from mathics.core.systemsymbols import SymbolAutomatic, SymbolTraditionalForm - from mathics.eval.makeboxes import format_element SymbolRegularPolygonBox = Symbol("RegularPolygonBox") diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index ad1d58a50..3b6ffeee4 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -7,28 +7,22 @@ import numbers from mathics.builtin.box.graphics import ( - GraphicsBox, ArrowBox, + GraphicsBox, LineBox, PointBox, PolygonBox, ) - -from mathics.builtin.colors.color_directives import _ColorObject, Opacity, RGBColor -from mathics.builtin.drawing.graphics_internals import GLOBALS3D, _GraphicsElementBox - -from mathics.builtin.drawing.graphics3d import ( - Coords3D, - Graphics3DElements, - Style3D, +from mathics.builtin.colors.color_directives import Opacity, RGBColor, _ColorObject +from mathics.builtin.drawing.graphics3d import Coords3D, Graphics3DElements, Style3D +from mathics.builtin.drawing.graphics_internals import ( + GLOBALS3D, + _GraphicsElementBox, + get_class, ) - -from mathics.builtin.drawing.graphics_internals import get_class - from mathics.core.exceptions import BoxExpressionError from mathics.core.formatter import lookup_method from mathics.core.symbols import Symbol, SymbolTrue - from mathics.eval.nevaluator import eval_N diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index d09146a8c..7c643df16 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -8,13 +8,8 @@ from mathics.builtin.base import BoxExpression, Builtin from mathics.builtin.options import options_to_rules - from mathics.core.atoms import Atom, String -from mathics.core.attributes import ( - A_HOLD_ALL_COMPLETE, - A_PROTECTED, - A_READ_PROTECTED, -) +from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_PROTECTED, A_READ_PROTECTED from mathics.core.element import BoxElementMixin from mathics.core.evaluation import Evaluation from mathics.core.exceptions import BoxConstructError @@ -26,8 +21,8 @@ SymbolRowBox, SymbolSqrtBox, SymbolStandardForm, - SymbolSubsuperscriptBox, SymbolSubscriptBox, + SymbolSubsuperscriptBox, SymbolSuperscriptBox, ) diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py index 374b1fa59..bf5552ca5 100644 --- a/mathics/builtin/box/uniform_polyhedra.py +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -1,9 +1,8 @@ -from mathics.builtin.box.graphics3d import Coords3D +import numbers -from mathics.builtin.colors.color_directives import _ColorObject, Opacity +from mathics.builtin.box.graphics3d import Coords3D +from mathics.builtin.colors.color_directives import Opacity, _ColorObject from mathics.builtin.drawing.graphics_internals import GLOBALS3D, _GraphicsElementBox - -import numbers from mathics.core.exceptions import BoxExpressionError from mathics.core.symbols import Symbol diff --git a/mathics/builtin/colors/color_directives.py b/mathics/builtin/colors/color_directives.py index 9fbe746ae..b9fe2c174 100644 --- a/mathics/builtin/colors/color_directives.py +++ b/mathics/builtin/colors/color_directives.py @@ -6,16 +6,10 @@ from math import atan2, cos, exp, pi, radians, sin, sqrt - from mathics.builtin.base import Builtin from mathics.builtin.colors.color_internals import convert_color from mathics.builtin.drawing.graphics_internals import _GraphicsDirective, get_class -from mathics.core.atoms import ( - Integer, - Real, - MachineReal, - String, -) +from mathics.core.atoms import Integer, MachineReal, Real, String from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.convert.python import from_python from mathics.core.element import ImmutableValueMixin diff --git a/mathics/builtin/colors/color_internals.py b/mathics/builtin/colors/color_internals.py index 6f8b9dc37..13ac617dd 100644 --- a/mathics/builtin/colors/color_internals.py +++ b/mathics/builtin/colors/color_internals.py @@ -6,26 +6,23 @@ from math import pi - from mathics.builtin.numpy_utils import ( - sqrt, - floor, - mod, - cos, - sin, arctan2, - minimum, - maximum, - dot_t, -) -from mathics.builtin.numpy_utils import ( - stack, - unstack, array, + choose, clip, conditional, - choose, + cos, + dot_t, + floor, + maximum, + minimum, + mod, + sin, + sqrt, + stack, stacked, + unstack, ) # in the long run, we might want to implement these functions using Compile[]. until Compile[] is available for all diff --git a/mathics/builtin/colors/named_colors.py b/mathics/builtin/colors/named_colors.py index c5d64c8dc..cd5008c0e 100644 --- a/mathics/builtin/colors/named_colors.py +++ b/mathics/builtin/colors/named_colors.py @@ -5,7 +5,6 @@ """ from mathics.builtin.base import Builtin - from mathics.core.symbols import strip_context diff --git a/mathics/builtin/compilation.py b/mathics/builtin/compilation.py index 57e6daa9b..0a3570121 100644 --- a/mathics/builtin/compilation.py +++ b/mathics/builtin/compilation.py @@ -14,19 +14,14 @@ from mathics.builtin.base import Builtin from mathics.builtin.box.compilation import CompiledCodeBox - - -from mathics.core.atoms import ( - Integer, - String, -) +from mathics.core.atoms import Integer, String from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.function import ( - expression_to_callable_and_args, - CompileError, CompileDuplicateArgName, + CompileError, CompileWrongArgType, + expression_to_callable_and_args, ) from mathics.core.convert.python import from_python from mathics.core.element import ImmutableValueMixin diff --git a/mathics/builtin/distance/numeric.py b/mathics/builtin/distance/numeric.py index 7a9e004f6..491aaf4db 100644 --- a/mathics/builtin/distance/numeric.py +++ b/mathics/builtin/distance/numeric.py @@ -5,7 +5,6 @@ from mathics.builtin.base import Builtin from mathics.core.atoms import Integer1, Integer2 from mathics.core.expression import Expression - from mathics.core.symbols import ( SymbolAbs, SymbolDivide, diff --git a/mathics/builtin/distance/stringdata.py b/mathics/builtin/distance/stringdata.py index e6affb476..7b44523d0 100644 --- a/mathics/builtin/distance/stringdata.py +++ b/mathics/builtin/distance/stringdata.py @@ -4,12 +4,9 @@ """ import unicodedata - from typing import Callable - from mathics.builtin.base import Builtin - from mathics.core.atoms import Integer, String, Symbol from mathics.core.expression import Expression from mathics.core.symbols import SymbolTrue diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 32a7746ad..55b5b876c 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -12,16 +12,14 @@ from mathics.builtin.base import Builtin from mathics.builtin.colors.color_directives import RGBColor from mathics.builtin.graphics import ( - _GraphicsElements, CoordinatesError, Graphics, Style, + _GraphicsElements, ) - -from mathics.core.atoms import Real, Integer, Rational +from mathics.core.atoms import Integer, Rational, Real from mathics.core.expression import Expression from mathics.core.symbols import SymbolN - from mathics.eval.nevaluator import eval_N diff --git a/mathics/builtin/drawing/graphics_internals.py b/mathics/builtin/drawing/graphics_internals.py index 62145d300..27dc5c9e1 100644 --- a/mathics/builtin/drawing/graphics_internals.py +++ b/mathics/builtin/drawing/graphics_internals.py @@ -4,17 +4,13 @@ # Also no docstring which may confuse the doc system -from mathics.builtin.base import ( - BuiltinElement, - BoxExpression, - split_name, -) +from mathics.builtin.base import BoxExpression, BuiltinElement, split_name # Signals to Mathics doc processing not to include this module in its documentation. no_doc = True from mathics.core.exceptions import BoxExpressionError -from mathics.core.symbols import system_symbols_dict, Symbol +from mathics.core.symbols import Symbol, system_symbols_dict class _GraphicsDirective(BuiltinElement): diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index 6254cf1fa..e4e556c09 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -34,8 +34,8 @@ SymbolGraphics, SymbolGraphics3D, SymbolGrid, - SymbolLog10, SymbolLine, + SymbolLog10, SymbolMap, SymbolPolygon, SymbolRGBColor, diff --git a/mathics/builtin/drawing/splines.py b/mathics/builtin/drawing/splines.py index 1bc7628cc..5f98f7614 100644 --- a/mathics/builtin/drawing/splines.py +++ b/mathics/builtin/drawing/splines.py @@ -10,7 +10,6 @@ sort_order = "mathics.builtin.splines" from mathics.builtin.base import Builtin - from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py index eea507877..80577da0c 100644 --- a/mathics/builtin/files_io/files.py +++ b/mathics/builtin/files_io/files.py @@ -7,52 +7,46 @@ import io import os +import os.path as osp import tempfile - from io import BytesIO -import os.path as osp - from mathics_scanner import TranslateError import mathics -from mathics.builtin.base import Builtin, Predefined, BinaryOperator, PrefixOperator -from mathics.builtin.base import MessageException - -from mathics.core import read -from mathics.core.atoms import ( - Integer, - String, - SymbolString, +from mathics.builtin.base import ( + BinaryOperator, + Builtin, + MessageException, + Predefined, + PrefixOperator, ) +from mathics.core import read +from mathics.core.atoms import Integer, String, SymbolString from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.convert.python import from_python from mathics.core.expression import BoxError, Expression from mathics.core.parser import MathicsFileLineFeeder, parse from mathics.core.read import ( + READ_TYPES, + MathicsOpen, + SymbolEndOfFile, channel_to_stream, close_stream, - MathicsOpen, + read_from_stream, read_get_separators, read_name_and_stream_from_channel, - read_from_stream, - READ_TYPES, - SymbolEndOfFile, -) -from mathics.core.streams import ( - path_search, - stream_manager, ) +from mathics.core.streams import path_search, stream_manager from mathics.core.symbols import Symbol, SymbolNull, SymbolTrue from mathics.core.systemsymbols import ( - SymbolReal, SymbolFailed, SymbolHold, SymbolOutputForm, + SymbolReal, ) - -from mathics.eval.makeboxes import format_element, do_format +from mathics.eval.makeboxes import do_format, format_element INITIAL_DIR = os.getcwd() DIRECTORY_STACK = [INITIAL_DIR] @@ -1108,8 +1102,9 @@ def eval(self, channel, types, evaluation, options): ["+", "-", ".", "e", "E", "^", "*"] + [str(i) for i in range(10)], ) - from mathics.core.expression import BaseElement from mathics_scanner.errors import IncompleteSyntaxError, InvalidSyntaxError + + from mathics.core.expression import BaseElement from mathics.core.parser import MathicsMultiLineFeeder, parse for typ in types.elements: diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py index 8a6fa9ff5..43b5fefc0 100644 --- a/mathics/builtin/files_io/filesystem.py +++ b/mathics/builtin/files_io/filesystem.py @@ -17,7 +17,6 @@ from mathics.builtin.files_io.files import INITIAL_DIR # noqa is used via global from mathics.builtin.files_io.files import DIRECTORY_STACK, MathicsOpen from mathics.builtin.string.operations import Hash - from mathics.core.atoms import Integer, Real, String from mathics.core.attributes import ( A_LISTABLE, @@ -28,7 +27,6 @@ ) from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.convert.python import from_python -from mathics.eval.nevaluator import eval_N from mathics.core.expression import Expression from mathics.core.streams import ( HOME_DIR, @@ -53,6 +51,7 @@ SymbolNone, SymbolPackages, ) +from mathics.eval.nevaluator import eval_N SYS_ROOT_DIR = "/" if os.name == "posix" else "\\" TMP_DIR = tempfile.gettempdir() diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index bc73ab2eb..ecc127e08 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -11,35 +11,20 @@ import mimetypes import os import sys - -from itertools import chain - import urllib.request as request +from itertools import chain from urllib.error import HTTPError, URLError -from mathics.builtin.base import ( - Builtin, - Predefined, - String, - Integer, - get_option, -) - +from mathics.builtin.base import Builtin, Integer, Predefined, String, get_option from mathics.builtin.pymimesniffer import magic - from mathics.core.atoms import ByteArrayAtom from mathics.core.attributes import A_NO_ATTRIBUTES, A_PROTECTED, A_READ_PROTECTED -from mathics.core.expression import Expression from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_python +from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.streams import stream_manager -from mathics.core.symbols import ( - Symbol, - SymbolNull, - SymbolTrue, - strip_context, -) +from mathics.core.symbols import Symbol, SymbolNull, SymbolTrue, strip_context from mathics.core.systemsymbols import ( SymbolByteArray, SymbolFailed, @@ -47,7 +32,6 @@ SymbolToString, ) - mimetypes.add_type("application/vnd.wolfram.mathematica.package", ".m") SymbolClose = Symbol("Close") @@ -1245,8 +1229,8 @@ class URLFetch(Builtin): def apply(self, url, elements, evaluation, options={}): "URLFetch[url_String, elements_, OptionsPattern[]]" - import tempfile import os + import tempfile py_url = url.get_string_value() @@ -2061,7 +2045,7 @@ def apply_elements(self, expr, elems, evaluation, **options): else: res = String(str(res)) elif function_channels == ListExpression(String("Streams")): - from io import StringIO, BytesIO + from io import BytesIO, StringIO if is_binary: pystream = BytesIO() diff --git a/mathics/builtin/forms/base.py b/mathics/builtin/forms/base.py index f73a542d8..248c63ba3 100644 --- a/mathics/builtin/forms/base.py +++ b/mathics/builtin/forms/base.py @@ -1,5 +1,4 @@ import mathics.core.definitions as definitions - from mathics.builtin.base import Builtin from mathics.core.symbols import Symbol diff --git a/mathics/builtin/forms/other.py b/mathics/builtin/forms/other.py index eef201e6b..21f5d02b0 100644 --- a/mathics/builtin/forms/other.py +++ b/mathics/builtin/forms/other.py @@ -7,9 +7,8 @@ from mathics.builtin.box.layout import RowBox, to_boxes from mathics.builtin.forms.base import FormBaseClass from mathics.builtin.makeboxes import MakeBoxes -from mathics.core.element import EvalMixin - from mathics.core.atoms import String +from mathics.core.element import EvalMixin class StringForm(FormBaseClass): diff --git a/mathics/builtin/forms/output.py b/mathics/builtin/forms/output.py index 39eb14a7f..06061e9a4 100644 --- a/mathics/builtin/forms/output.py +++ b/mathics/builtin/forms/output.py @@ -11,7 +11,6 @@ Forms which appear in '$OutputForms'. """ import re - from typing import Optional from mathics.builtin.base import Builtin @@ -20,7 +19,6 @@ from mathics.builtin.forms.base import FormBaseClass from mathics.builtin.makeboxes import MakeBoxes, number_form from mathics.builtin.tensors import get_dimensions - from mathics.core.atoms import ( Integer, MachineReal, @@ -29,25 +27,17 @@ String, StringFromPython, ) - -from mathics.core.expression import Expression, BoxError +from mathics.core.expression import BoxError, Expression from mathics.core.list import ListExpression -from mathics.core.number import ( - convert_base, - dps, - machine_precision, - reconstruct_digits, -) - +from mathics.core.number import convert_base, dps, machine_precision, reconstruct_digits from mathics.core.symbols import ( Symbol, + SymbolFalse, SymbolFullForm, SymbolList, - SymbolFalse, SymbolNull, SymbolTrue, ) - from mathics.core.systemsymbols import ( SymbolAutomatic, SymbolInfinity, @@ -59,7 +49,6 @@ SymbolSubscriptBox, SymbolSuperscriptBox, ) - from mathics.eval.makeboxes import format_element MULTI_NEWLINE_RE = re.compile(r"\n{2,}") diff --git a/mathics/builtin/forms/variables.py b/mathics/builtin/forms/variables.py index 0b9b1e3a4..43f1fb749 100644 --- a/mathics/builtin/forms/variables.py +++ b/mathics/builtin/forms/variables.py @@ -3,10 +3,7 @@ """ -from mathics.builtin.base import ( - Predefined, -) - +from mathics.builtin.base import Predefined from mathics.core.attributes import A_LOCKED, A_PROTECTED from mathics.core.list import ListExpression diff --git a/mathics/builtin/functional/application.py b/mathics/builtin/functional/application.py index 4a913be95..c39c29ac6 100644 --- a/mathics/builtin/functional/application.py +++ b/mathics/builtin/functional/application.py @@ -10,16 +10,10 @@ from itertools import chain - from mathics.builtin.base import Builtin, PostfixOperator -from mathics.core.expression import Expression - -from mathics.core.attributes import ( - A_HOLD_ALL, - A_N_HOLD_ALL, - A_PROTECTED, -) +from mathics.core.attributes import A_HOLD_ALL, A_N_HOLD_ALL, A_PROTECTED from mathics.core.convert.sympy import SymbolFunction +from mathics.core.expression import Expression from mathics.core.symbols import Symbol diff --git a/mathics/builtin/functional/apply_fns_to_lists.py b/mathics/builtin/functional/apply_fns_to_lists.py index fa576bd5c..87ccb6380 100644 --- a/mathics/builtin/functional/apply_fns_to_lists.py +++ b/mathics/builtin/functional/apply_fns_to_lists.py @@ -12,10 +12,8 @@ from typing import Iterable -from mathics.builtin.base import ( - Builtin, - BinaryOperator, -) +from mathics.builtin.base import BinaryOperator, Builtin +from mathics.builtin.lists import List, python_levelspec, walk_levels from mathics.core.atoms import Integer from mathics.core.convert.expression import to_mathics_list from mathics.core.exceptions import ( @@ -25,21 +23,9 @@ ) from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import ( - Atom, - Symbol, - SymbolNull, - SymbolTrue, -) - +from mathics.core.symbols import Atom, Symbol, SymbolNull, SymbolTrue from mathics.core.systemsymbols import SymbolRule -from mathics.builtin.lists import ( - python_levelspec, - walk_levels, - List, -) - SymbolMapThread = Symbol("MapThread") diff --git a/mathics/builtin/functional/composition.py b/mathics/builtin/functional/composition.py index 7d7496090..cea920bf0 100644 --- a/mathics/builtin/functional/composition.py +++ b/mathics/builtin/functional/composition.py @@ -15,14 +15,9 @@ sort_order = "mathics.builtin.functional-composition" from mathics.builtin.base import Builtin +from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_PROTECTED from mathics.core.expression import Expression -from mathics.core.attributes import ( - A_FLAT, - A_ONE_IDENTITY, - A_PROTECTED, -) - class Composition(Builtin): """ diff --git a/mathics/builtin/inference.py b/mathics/builtin/inference.py index c2b34ec89..530786f9a 100644 --- a/mathics/builtin/inference.py +++ b/mathics/builtin/inference.py @@ -2,17 +2,10 @@ from mathics.core.expression import Expression -from mathics.core.symbols import ( - Atom, - SymbolTrue, - SymbolFalse, -) - -from mathics.core.rules import Rule -from mathics.core.parser.util import SystemDefinitions - from mathics.core.parser import parse_builtin_rule -from mathics.core.symbols import Symbol +from mathics.core.parser.util import SystemDefinitions +from mathics.core.rules import Rule +from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue from mathics.core.systemsymbols import SymbolAnd, SymbolEqual, SymbolNot, SymbolOr # TODO: Extend these rules? diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index 3efbd1f2a..df6e684c6 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -4,21 +4,11 @@ import re - -from mathics.builtin.base import ( - Builtin, - Predefined, -) - +from mathics.builtin.base import Builtin, Predefined from mathics.core.attributes import A_NO_ATTRIBUTES - from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import ( - Symbol, - SymbolNull, -) - +from mathics.core.symbols import Symbol, SymbolNull from mathics.core.systemsymbols import SymbolRow MULTI_NEWLINE_RE = re.compile(r"\n{2,}") diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index 0e707177c..356263ae9 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -4,20 +4,14 @@ Division-Related Functions """ +from itertools import combinations from typing import List import sympy -from itertools import combinations from sympy import Q, ask -from mathics.builtin.base import Builtin, Test, SympyFunction +from mathics.builtin.base import Builtin, SympyFunction, Test from mathics.core.atoms import Integer -from mathics.core.convert.python import from_bool -from mathics.core.expression import Expression -from mathics.core.convert.expression import to_mathics_list -from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue -from mathics.core.systemsymbols import SymbolComplexInfinity - from mathics.core.attributes import ( A_FLAT, A_LISTABLE, @@ -27,6 +21,11 @@ A_PROTECTED, A_READ_PROTECTED, ) +from mathics.core.convert.expression import to_mathics_list +from mathics.core.convert.python import from_bool +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue +from mathics.core.systemsymbols import SymbolComplexInfinity SymbolQuotient = Symbol("Quotient") SymbolQuotientRemainder = Symbol("QuotientRemainder") diff --git a/mathics/builtin/intfns/misc.py b/mathics/builtin/intfns/misc.py index fb6c3254f..c8e058826 100644 --- a/mathics/builtin/intfns/misc.py +++ b/mathics/builtin/intfns/misc.py @@ -1,5 +1,4 @@ from mathics.builtin.arithmetic import _MPMathFunction - from mathics.core.attributes import ( A_LISTABLE, A_NUMERIC_FUNCTION, diff --git a/mathics/builtin/intfns/recurrence.py b/mathics/builtin/intfns/recurrence.py index d9927b35b..45aa017bf 100644 --- a/mathics/builtin/intfns/recurrence.py +++ b/mathics/builtin/intfns/recurrence.py @@ -8,11 +8,9 @@ from sympy.functions.combinatorial.numbers import stirling - +from mathics.builtin.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin from mathics.core.atoms import Integer -from mathics.builtin.arithmetic import _MPMathFunction - from mathics.core.attributes import ( A_LISTABLE, A_NUMERIC_FUNCTION, diff --git a/mathics/builtin/list/associations.py b/mathics/builtin/list/associations.py index 9a10127ff..4426376b7 100644 --- a/mathics/builtin/list/associations.py +++ b/mathics/builtin/list/associations.py @@ -7,10 +7,7 @@ """ -from mathics.builtin.base import ( - Builtin, - Test, -) +from mathics.builtin.base import Builtin, Test from mathics.builtin.box.layout import RowBox from mathics.builtin.lists import list_boxes from mathics.core.atoms import Integer @@ -18,11 +15,7 @@ from mathics.core.convert.expression import to_mathics_list from mathics.core.expression import Expression from mathics.core.symbols import Symbol, SymbolTrue -from mathics.core.systemsymbols import ( - SymbolAssociation, - SymbolMakeBoxes, - SymbolMissing, -) +from mathics.core.systemsymbols import SymbolAssociation, SymbolMakeBoxes, SymbolMissing class Association(Builtin): diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index dcfc576c5..499bd5156 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -10,21 +10,14 @@ from itertools import permutations - from mathics.builtin.base import Builtin, Pattern -from mathics.builtin.lists import ( - _IterationFunction, - get_tuples, -) +from mathics.builtin.lists import _IterationFunction, get_tuples from mathics.core.atoms import Integer, Symbol from mathics.core.attributes import A_HOLD_FIRST, A_LISTABLE, A_PROTECTED from mathics.core.convert.expression import to_expression from mathics.core.convert.sympy import from_sympy from mathics.core.element import ElementsProperties -from mathics.core.expression import ( - Expression, - structure, -) +from mathics.core.expression import Expression, structure from mathics.core.list import ListExpression from mathics.core.symbols import Atom diff --git a/mathics/builtin/list/rearrange.py b/mathics/builtin/list/rearrange.py index 42a65bfdb..6650ab103 100644 --- a/mathics/builtin/list/rearrange.py +++ b/mathics/builtin/list/rearrange.py @@ -6,23 +6,14 @@ """ import functools - from collections import defaultdict from itertools import chain from typing import Callable - -from mathics.builtin.base import ( - Builtin, - MessageException, -) - +from mathics.builtin.base import Builtin, MessageException from mathics.core.atoms import Integer from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_PROTECTED -from mathics.core.expression import ( - Expression, - structure, -) +from mathics.core.expression import Expression, structure from mathics.core.list import ListExpression from mathics.core.symbols import Atom, Symbol, SymbolTrue from mathics.core.systemsymbols import SymbolMap diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index f811fe1cf..d38396e82 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -6,10 +6,9 @@ """ import heapq -import sympy - from itertools import chain +import sympy from mathics.algorithm.clusters import ( AutomaticMergeCriterion, @@ -20,11 +19,7 @@ kmeans, optimize, ) -from mathics.algorithm.parts import ( - python_levelspec, - walk_levels, -) - +from mathics.algorithm.parts import python_levelspec, walk_levels from mathics.builtin.base import ( Builtin, CountableInteger, @@ -33,12 +28,10 @@ SympyFunction, Test, ) - from mathics.builtin.box.layout import RowBox from mathics.builtin.numbers.algebra import cancel from mathics.builtin.options import options_to_rules from mathics.builtin.scoping import dynamic_scoping - from mathics.core.atoms import ( Integer, Integer0, @@ -50,13 +43,7 @@ machine_precision, min_prec, ) - -from mathics.core.attributes import ( - A_HOLD_ALL, - A_LOCKED, - A_PROTECTED, - A_READ_PROTECTED, -) +from mathics.core.attributes import A_HOLD_ALL, A_LOCKED, A_PROTECTED, A_READ_PROTECTED from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.convert.sympy import from_sympy from mathics.core.exceptions import ( @@ -67,7 +54,6 @@ PartRangeError, ) from mathics.core.expression import Expression, structure - from mathics.core.interrupt import BreakInterrupt, ContinueInterrupt, ReturnInterrupt from mathics.core.list import ListExpression from mathics.core.symbols import ( @@ -78,7 +64,6 @@ SymbolTrue, strip_context, ) - from mathics.core.systemsymbols import ( SymbolAlternatives, SymbolFailed, @@ -91,9 +76,6 @@ SymbolSequence, SymbolSubsetQ, ) - -from mathics.eval.nevaluator import eval_N - from mathics.eval.nevaluator import eval_N from mathics.eval.numerify import numerify @@ -815,7 +797,7 @@ class LeafCount(Builtin): def eval(self, expr, evaluation): "LeafCount[expr___]" - from mathics.core.atoms import Rational, Complex + from mathics.core.atoms import Complex, Rational elements = [] diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index 6890ad69d..ed906e3ef 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -6,44 +6,21 @@ """ from typing import Union -import mpmath +import mpmath from mathics.builtin.base import Builtin, Predefined from mathics.builtin.box.layout import RowBox, to_boxes - -from mathics.core.convert.op import operator_to_unicode, operator_to_ascii -from mathics.core.atoms import ( - Integer, - Integer1, - Real, - String, -) - -from mathics.core.attributes import ( - A_HOLD_ALL_COMPLETE, - A_READ_PROTECTED, -) +from mathics.core.atoms import Integer, Integer1, Real, String +from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_READ_PROTECTED +from mathics.core.convert.op import operator_to_ascii, operator_to_unicode from mathics.core.element import BaseElement, BoxElementMixin from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.number import dps - -from mathics.core.symbols import ( - Atom, - Symbol, -) - -from mathics.core.systemsymbols import ( - SymbolInputForm, - SymbolOutputForm, - SymbolRowBox, -) - -from mathics.eval.makeboxes import ( - _boxed_string, - format_element, -) +from mathics.core.symbols import Atom, Symbol +from mathics.core.systemsymbols import SymbolInputForm, SymbolOutputForm, SymbolRowBox +from mathics.eval.makeboxes import _boxed_string, format_element def int_to_s_exp(expr, n): diff --git a/mathics/builtin/messages.py b/mathics/builtin/messages.py index 4549a8670..f91a5b495 100644 --- a/mathics/builtin/messages.py +++ b/mathics/builtin/messages.py @@ -7,29 +7,14 @@ import typing from typing import Any - -from mathics.builtin.base import ( - Builtin, - BinaryOperator, -) - +from mathics.builtin.base import BinaryOperator, Builtin from mathics.core.atoms import String -from mathics.core.attributes import ( - A_HOLD_ALL, - A_HOLD_FIRST, - A_PROTECTED, -) +from mathics.core.attributes import A_HOLD_ALL, A_HOLD_FIRST, A_PROTECTED from mathics.core.evaluation import Message as EvaluationMessage from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import ( - Symbol, - SymbolNull, -) -from mathics.core.systemsymbols import ( - SymbolMessageName, - SymbolQuiet, -) +from mathics.core.symbols import Symbol, SymbolNull +from mathics.core.systemsymbols import SymbolMessageName, SymbolQuiet class Message(Builtin): diff --git a/mathics/builtin/numbers/diffeqns.py b/mathics/builtin/numbers/diffeqns.py index 4c3752b2c..17a5c5c40 100644 --- a/mathics/builtin/numbers/diffeqns.py +++ b/mathics/builtin/numbers/diffeqns.py @@ -5,6 +5,7 @@ """ import sympy + from mathics.builtin.base import Builtin from mathics.core.convert.sympy import from_sympy from mathics.core.expression import Expression diff --git a/mathics/builtin/numbers/exp.py b/mathics/builtin/numbers/exp.py index cd6412218..9ad654462 100644 --- a/mathics/builtin/numbers/exp.py +++ b/mathics/builtin/numbers/exp.py @@ -7,12 +7,12 @@ """ import math -import mpmath - from collections import namedtuple from contextlib import contextmanager from itertools import chain +import mpmath + from mathics.builtin.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin from mathics.core.atoms import Real diff --git a/mathics/builtin/numbers/hyperbolic.py b/mathics/builtin/numbers/hyperbolic.py index bdae4742e..b6baac689 100644 --- a/mathics/builtin/numbers/hyperbolic.py +++ b/mathics/builtin/numbers/hyperbolic.py @@ -9,11 +9,11 @@ """ from typing import Optional -from mathics.core.convert.sympy import SympyExpression from mathics.builtin.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin from mathics.core.atoms import IntegerM1 +from mathics.core.convert.sympy import SympyExpression from mathics.core.expression import Expression from mathics.core.symbols import Symbol, SymbolPower diff --git a/mathics/builtin/numbers/linalg.py b/mathics/builtin/numbers/linalg.py index f0446eb7b..516b7a86e 100644 --- a/mathics/builtin/numbers/linalg.py +++ b/mathics/builtin/numbers/linalg.py @@ -6,21 +6,17 @@ import mpmath import sympy -from sympy import re, im - +from sympy import im, re from mathics.builtin.base import Builtin from mathics.core.atoms import Integer, Integer0, Real -from mathics.core.expression import Expression from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.matrix import matrix_data from mathics.core.convert.mpmath import from_mpmath, to_mpmath_matrix from mathics.core.convert.sympy import from_sympy, to_sympy_matrix +from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import ( - Symbol, - SymbolList, -) +from mathics.core.symbols import Symbol, SymbolList class DesignMatrix(Builtin): diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index b6937c7f2..06b012689 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -19,7 +19,6 @@ from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_bool, from_python from mathics.core.convert.sympy import SympyPrime, from_sympy -from mathics.eval.nevaluator import eval_N from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolDivide, SymbolFalse @@ -30,6 +29,7 @@ SymbolIm, SymbolRe, ) +from mathics.eval.nevaluator import eval_N SymbolFractionalPart = Symbol("System`FractionalPart") SymbolMantissaExponent = Symbol("System`MantissaExponent") diff --git a/mathics/builtin/numbers/trig.py b/mathics/builtin/numbers/trig.py index 7ec78a259..0cd88c306 100644 --- a/mathics/builtin/numbers/trig.py +++ b/mathics/builtin/numbers/trig.py @@ -7,20 +7,15 @@ """ import math -import mpmath - from collections import namedtuple from contextlib import contextmanager from itertools import chain +import mpmath + from mathics.builtin.arithmetic import _MPMathFunction from mathics.builtin.base import Builtin -from mathics.core.atoms import ( - Integer, - Integer0, - IntegerM1, - Real, -) +from mathics.core.atoms import Integer, Integer0, IntegerM1, Real from mathics.core.convert.python import from_python from mathics.core.expression import Expression from mathics.core.list import ListExpression diff --git a/mathics/builtin/numeric.py b/mathics/builtin/numeric.py index 0a4cb31a0..d769f4d7a 100644 --- a/mathics/builtin/numeric.py +++ b/mathics/builtin/numeric.py @@ -16,10 +16,10 @@ from mathics.core.atoms import Complex, Integer, Integer0, Rational, Real from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED from mathics.core.convert.sympy import from_sympy -from mathics.eval.nevaluator import eval_nvalues from mathics.core.expression import Expression from mathics.core.number import machine_epsilon from mathics.core.symbols import SymbolDivide, SymbolMachinePrecision, SymbolTimes +from mathics.eval.nevaluator import eval_nvalues def chop(expr, delta=10.0 ** (-10.0)): diff --git a/mathics/builtin/numpy_utils/__init__.py b/mathics/builtin/numpy_utils/__init__.py index 8871442b4..a20511c64 100755 --- a/mathics/builtin/numpy_utils/__init__.py +++ b/mathics/builtin/numpy_utils/__init__.py @@ -4,6 +4,7 @@ try: import numpy + from mathics.builtin.numpy_utils import with_numpy as numpy_layer except ImportError: from mathics.builtin.numpy_utils import without_numpy as numpy_layer diff --git a/mathics/builtin/numpy_utils/with_numpy.py b/mathics/builtin/numpy_utils/with_numpy.py index 9cb29d0d4..264de9bcf 100755 --- a/mathics/builtin/numpy_utils/with_numpy.py +++ b/mathics/builtin/numpy_utils/with_numpy.py @@ -4,12 +4,12 @@ A couple of helper functions for doing numpy-like stuff with numpy. """ -from functools import reduce - import ast import inspect -import numpy import sys +from functools import reduce + +import numpy from mathics.core.list import ListExpression diff --git a/mathics/builtin/numpy_utils/without_numpy.py b/mathics/builtin/numpy_utils/without_numpy.py index df03482de..15b6e6e32 100755 --- a/mathics/builtin/numpy_utils/without_numpy.py +++ b/mathics/builtin/numpy_utils/without_numpy.py @@ -4,18 +4,19 @@ A couple of helper functions for doing numpy-like stuff without numpy. """ -from mathics.core.list import ListExpression -from itertools import chain +import inspect +import operator from contextlib import contextmanager +from itertools import chain from math import ( - sin as sinf, - cos as cosf, - sqrt as sqrtf, atan2 as atan2f, + cos as cosf, floor as floorf, + sin as sinf, + sqrt as sqrtf, ) -import operator -import inspect + +from mathics.core.list import ListExpression # If numpy is not available, we define the following fallbacks that are useful for implementing a similar # logic in pure python without numpy. They obviously work on regular python array though, not numpy arrays. diff --git a/mathics/builtin/optimization.py b/mathics/builtin/optimization.py index 52e6c2f24..cb2d6025d 100644 --- a/mathics/builtin/optimization.py +++ b/mathics/builtin/optimization.py @@ -14,7 +14,6 @@ import sympy from mathics.builtin.base import Builtin - from mathics.core.atoms import IntegerM1 from mathics.core.attributes import A_CONSTANT, A_PROTECTED, A_READ_PROTECTED from mathics.core.convert.python import from_python diff --git a/mathics/builtin/options.py b/mathics/builtin/options.py index d7b716c59..ce0127c0d 100644 --- a/mathics/builtin/options.py +++ b/mathics/builtin/options.py @@ -13,21 +13,11 @@ from mathics.builtin.base import Builtin, Test, get_option from mathics.builtin.drawing.image import Image - from mathics.core.atoms import String from mathics.core.evaluation import Evaluation -from mathics.core.expression import ( - Expression, - SymbolDefault, - get_default_value, -) +from mathics.core.expression import Expression, SymbolDefault, get_default_value from mathics.core.list import ListExpression -from mathics.core.symbols import ( - Symbol, - SymbolList, - ensure_context, - strip_context, -) +from mathics.core.symbols import Symbol, SymbolList, ensure_context, strip_context from mathics.core.systemsymbols import SymbolRule, SymbolRuleDelayed diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index b881328d4..49521d4fd 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -40,24 +40,16 @@ sort_order = "mathics.builtin.rules-and-patterns" from mathics.algorithm.parts import python_levelspec - from mathics.builtin.base import ( - Builtin, - BinaryOperator, - PostfixOperator, AtomBuiltin, - PatternObject, + BinaryOperator, + Builtin, PatternError, + PatternObject, + PostfixOperator, ) from mathics.builtin.lists import InvalidLevelspecError - -from mathics.core.atoms import ( - String, - Number, - Integer, - Rational, - Real, -) +from mathics.core.atoms import Integer, Number, Rational, Real, String from mathics.core.attributes import ( A_HOLD_ALL, A_HOLD_FIRST, @@ -70,13 +62,7 @@ from mathics.core.list import ListExpression from mathics.core.pattern import Pattern, StopGenerator from mathics.core.rules import Rule -from mathics.core.symbols import ( - Atom, - Symbol, - SymbolFalse, - SymbolList, - SymbolTrue, -) +from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolList, SymbolTrue from mathics.core.systemsymbols import SymbolBlank, SymbolDispatch SymbolDefault = Symbol("Default") diff --git a/mathics/builtin/physchemdata.py b/mathics/builtin/physchemdata.py index ddafa95eb..6f12885c4 100644 --- a/mathics/builtin/physchemdata.py +++ b/mathics/builtin/physchemdata.py @@ -6,16 +6,10 @@ """ import os - from csv import reader as csvreader - from mathics.builtin.base import Builtin - -from mathics.core.atoms import ( - Integer, - String, -) +from mathics.core.atoms import Integer, String from mathics.core.convert.python import from_python from mathics.core.expression import Expression from mathics.core.symbols import Symbol, strip_context diff --git a/mathics/builtin/procedural.py b/mathics/builtin/procedural.py index 4910554d9..735f70675 100644 --- a/mathics/builtin/procedural.py +++ b/mathics/builtin/procedural.py @@ -11,10 +11,9 @@ """ -from mathics.builtin.base import Builtin, BinaryOperator +from mathics.builtin.base import BinaryOperator, Builtin from mathics.builtin.lists import _IterationFunction from mathics.builtin.patterns import match - from mathics.core.attributes import ( A_HOLD_ALL, A_HOLD_REST, @@ -29,12 +28,7 @@ ReturnInterrupt, WLThrowInterrupt, ) -from mathics.core.symbols import ( - Symbol, - SymbolFalse, - SymbolNull, - SymbolTrue, -) +from mathics.core.symbols import Symbol, SymbolFalse, SymbolNull, SymbolTrue from mathics.core.systemsymbols import SymbolMatchQ SymbolWhich = Symbol("Which") diff --git a/mathics/builtin/pymimesniffer/magic.py b/mathics/builtin/pymimesniffer/magic.py index dbe7816e6..38d63b35f 100644 --- a/mathics/builtin/pymimesniffer/magic.py +++ b/mathics/builtin/pymimesniffer/magic.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import sys -import os.path import logging +import os.path +import sys class MagicRule: diff --git a/mathics/builtin/pympler/asizeof.py b/mathics/builtin/pympler/asizeof.py index a56fc1fc8..c80643320 100644 --- a/mathics/builtin/pympler/asizeof.py +++ b/mathics/builtin/pympler/asizeof.py @@ -191,6 +191,10 @@ class and the ``... def`` suffix marks the *definition object*. if sys.version_info < (2, 6, 0): raise NotImplementedError("%s requires Python 2.6 or newer" % ("asizeof",)) +import types as Types +import warnings +import weakref as Weakref + # all imports listed explicitly to help PyChecker from inspect import ( isbuiltin, @@ -205,9 +209,6 @@ class and the ``... def`` suffix marks the *definition object*. from math import log from os import curdir, linesep from struct import calcsize # type/class Struct only in Python 2.5+ -import types as Types -import warnings -import weakref as Weakref __all__ = [ "adict", diff --git a/mathics/builtin/quantities.py b/mathics/builtin/quantities.py index bdb20f6ff..40250f95c 100644 --- a/mathics/builtin/quantities.py +++ b/mathics/builtin/quantities.py @@ -1,28 +1,21 @@ # -*- coding: utf-8 -*- -from mathics.builtin.base import Builtin, Test -from mathics.core.atoms import ( - String, - Integer, - Integer1, - Real, - Number, -) -from mathics.core.convert.expression import to_mathics_list -from mathics.core.expression import Expression -from mathics.core.list import ListExpression -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import SymbolRowBox +from pint import UnitRegistry +from mathics.builtin.base import Builtin, Test +from mathics.core.atoms import Integer, Integer1, Number, Real, String from mathics.core.attributes import ( A_HOLD_REST, A_N_HOLD_REST, A_PROTECTED, A_READ_PROTECTED, ) - -from pint import UnitRegistry +from mathics.core.convert.expression import to_mathics_list +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import SymbolRowBox SymbolQuantity = Symbol("Quantity") diff --git a/mathics/builtin/quantum_mechanics/angular.py b/mathics/builtin/quantum_mechanics/angular.py index b6d299e4c..5f7148a3d 100644 --- a/mathics/builtin/quantum_mechanics/angular.py +++ b/mathics/builtin/quantum_mechanics/angular.py @@ -12,19 +12,16 @@ from mathics.builtin.base import SympyFunction from mathics.core.atoms import Integer +from mathics.core.attributes import ( # A_LISTABLE,; A_NUMERIC_FUNCTION, + A_PROTECTED, + A_READ_PROTECTED, +) from mathics.core.convert.python import from_python from mathics.core.convert.sympy import from_sympy from mathics.core.evaluation import Evaluation from mathics.core.list import ListExpression from mathics.core.symbols import Symbol -from mathics.core.attributes import ( - # A_LISTABLE, - # A_NUMERIC_FUNCTION, - A_PROTECTED, - A_READ_PROTECTED, -) - class ClebschGordan(SympyFunction): """ diff --git a/mathics/builtin/recurrence.py b/mathics/builtin/recurrence.py index 3e1b51f91..afc0cb0d3 100644 --- a/mathics/builtin/recurrence.py +++ b/mathics/builtin/recurrence.py @@ -12,10 +12,9 @@ import sympy from mathics.builtin.base import Builtin - from mathics.core.atoms import IntegerM1 from mathics.core.attributes import A_CONSTANT -from mathics.core.convert.sympy import sympy_symbol_prefix, from_sympy +from mathics.core.convert.sympy import from_sympy, sympy_symbol_prefix from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Atom, Symbol, SymbolPlus, SymbolTimes diff --git a/mathics/builtin/scipy_utils/integrators.py b/mathics/builtin/scipy_utils/integrators.py index e89906157..62f13cebd 100644 --- a/mathics/builtin/scipy_utils/integrators.py +++ b/mathics/builtin/scipy_utils/integrators.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import sys + from mathics.builtin.base import check_requires_list from mathics.core.util import IS_PYPY @@ -43,7 +44,7 @@ def _scipy_proxy_func(fun, a, b, **opts): try: - from scipy.integrate import romberg, quad, nquad + from scipy.integrate import nquad, quad, romberg except Exception: scipy_nintegrate_methods = {} else: diff --git a/mathics/builtin/scipy_utils/optimizers.py b/mathics/builtin/scipy_utils/optimizers.py index d4b7d67b5..4ca04a67e 100644 --- a/mathics/builtin/scipy_utils/optimizers.py +++ b/mathics/builtin/scipy_utils/optimizers.py @@ -1,27 +1,20 @@ # -*- coding: utf-8 -*- from mathics.builtin.base import check_requires_list -from mathics.core.convert.function import expression_to_callable_and_args - from mathics.core.atoms import Number, Real -from mathics.core.expression import Expression +from mathics.core.convert.function import expression_to_callable_and_args from mathics.core.evaluation import Evaluation -from mathics.eval.nevaluator import eval_N -from mathics.core.systemsymbols import SymbolAutomatic, SymbolInfinity, SymbolFailed +from mathics.core.expression import Expression +from mathics.core.systemsymbols import SymbolAutomatic, SymbolFailed, SymbolInfinity from mathics.core.util import IS_PYPY - +from mathics.eval.nevaluator import eval_N if IS_PYPY or not check_requires_list(["scipy", "numpy"]): raise ImportError -from scipy.optimize import ( +from scipy.optimize import ( # minimize,; basinhopping,; least_squares,; curve_fit,; root, minimize_scalar, - # minimize, - # basinhopping, - # least_squares, - # curve_fit, root_scalar, - # root, ) diff --git a/mathics/builtin/scoping.py b/mathics/builtin/scoping.py index be69c2e93..bce039a1b 100644 --- a/mathics/builtin/scoping.py +++ b/mathics/builtin/scoping.py @@ -4,23 +4,13 @@ """ -from mathics.core.attributes import ( - A_HOLD_ALL, - A_PROTECTED, - attribute_string_to_number, -) from mathics.builtin.base import Builtin, Predefined from mathics.core.assignment import get_symbol_list -from mathics.core.atoms import ( - String, - Integer, -) +from mathics.core.atoms import Integer, String +from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED, attribute_string_to_number from mathics.core.evaluation import Evaluation from mathics.core.list import ListExpression -from mathics.core.symbols import ( - Symbol, - fully_qualified_symbol_name, -) +from mathics.core.symbols import Symbol, fully_qualified_symbol_name def get_scoping_vars(var_list, msg_symbol="", evaluation=None): diff --git a/mathics/builtin/sparse.py b/mathics/builtin/sparse.py index 63318ba37..9ce476712 100644 --- a/mathics/builtin/sparse.py +++ b/mathics/builtin/sparse.py @@ -6,10 +6,7 @@ from mathics.algorithm.parts import walk_parts - from mathics.builtin.base import Builtin - - from mathics.core.atoms import Integer, Integer0 from mathics.core.expression import Expression from mathics.core.list import ListExpression diff --git a/mathics/builtin/specialfns/erf.py b/mathics/builtin/specialfns/erf.py index b78aba30e..b88388582 100644 --- a/mathics/builtin/specialfns/erf.py +++ b/mathics/builtin/specialfns/erf.py @@ -6,12 +6,7 @@ from mathics.builtin.arithmetic import _MPMathFunction, _MPMathMultiFunction - -from mathics.core.attributes import ( - A_LISTABLE, - A_NUMERIC_FUNCTION, - A_PROTECTED, -) +from mathics.core.attributes import A_LISTABLE, A_NUMERIC_FUNCTION, A_PROTECTED class Erf(_MPMathMultiFunction): diff --git a/mathics/builtin/specialfns/zeta.py b/mathics/builtin/specialfns/zeta.py index bf3b853a8..a4c10561d 100644 --- a/mathics/builtin/specialfns/zeta.py +++ b/mathics/builtin/specialfns/zeta.py @@ -6,7 +6,6 @@ import mpmath - from mathics.builtin.arithmetic import _MPMathFunction from mathics.core.convert.mpmath import from_mpmath diff --git a/mathics/builtin/statistics/dependency.py b/mathics/builtin/statistics/dependency.py index e583ab315..fdc0e75f3 100644 --- a/mathics/builtin/statistics/dependency.py +++ b/mathics/builtin/statistics/dependency.py @@ -10,9 +10,7 @@ sort_order = "mathics.builtin.special-moments" from mathics.builtin.base import Builtin - -from mathics.builtin.lists import _Rectangular, _NotRectangularException - +from mathics.builtin.lists import _NotRectangularException, _Rectangular from mathics.core.atoms import Integer from mathics.core.expression import Expression from mathics.core.symbols import Symbol, SymbolDivide diff --git a/mathics/builtin/statistics/location.py b/mathics/builtin/statistics/location.py index 0d2750b47..4225cbdab 100644 --- a/mathics/builtin/statistics/location.py +++ b/mathics/builtin/statistics/location.py @@ -4,7 +4,7 @@ from mathics.algorithm.introselect import introselect from mathics.builtin.base import Builtin -from mathics.builtin.lists import _Rectangular, _NotRectangularException +from mathics.builtin.lists import _NotRectangularException, _Rectangular from mathics.core.atoms import Integer2 from mathics.core.expression import Expression from mathics.core.symbols import Symbol, SymbolDivide, SymbolPlus diff --git a/mathics/builtin/string/characters.py b/mathics/builtin/string/characters.py index b1265f839..778266b2c 100644 --- a/mathics/builtin/string/characters.py +++ b/mathics/builtin/string/characters.py @@ -5,7 +5,6 @@ from mathics.builtin.base import Builtin, Test - from mathics.core.atoms import String from mathics.core.attributes import A_LISTABLE, A_PROTECTED, A_READ_PROTECTED from mathics.core.convert.expression import to_mathics_list diff --git a/mathics/builtin/string/charcodes.py b/mathics/builtin/string/charcodes.py index fd3551377..09cdaee6a 100644 --- a/mathics/builtin/string/charcodes.py +++ b/mathics/builtin/string/charcodes.py @@ -5,15 +5,9 @@ import sys - from mathics.builtin.atomic.strings import to_python_encoding from mathics.builtin.base import Builtin - -from mathics.core.atoms import ( - Integer, - Integer1, - String, -) +from mathics.core.atoms import Integer, Integer1, String from mathics.core.convert.expression import to_mathics_list from mathics.core.expression import Expression from mathics.core.list import ListExpression diff --git a/mathics/builtin/string/operations.py b/mathics/builtin/string/operations.py index 884de0cc4..bc3eca85d 100644 --- a/mathics/builtin/string/operations.py +++ b/mathics/builtin/string/operations.py @@ -8,29 +8,16 @@ import re import zlib - -from mathics.algorithm.parts import python_seq, convert_seq - - +from mathics.algorithm.parts import convert_seq, python_seq from mathics.builtin.atomic.strings import ( - _StringFind, _evaluate_match, _parallel_match, + _StringFind, mathics_split, to_regex, ) - -from mathics.builtin.base import ( - BinaryOperator, - Builtin, -) - -from mathics.core.atoms import ( - ByteArrayAtom, - Integer, - Integer1, - String, -) +from mathics.builtin.base import BinaryOperator, Builtin +from mathics.core.atoms import ByteArrayAtom, Integer, Integer1, String from mathics.core.attributes import ( A_FLAT, A_LISTABLE, @@ -41,19 +28,13 @@ from mathics.core.convert.python import from_python from mathics.core.expression import Expression, string_list from mathics.core.list import ListExpression -from mathics.core.symbols import ( - Symbol, - SymbolFalse, - SymbolList, - SymbolTrue, -) +from mathics.core.symbols import Symbol, SymbolFalse, SymbolList, SymbolTrue from mathics.core.systemsymbols import ( SymbolAll, SymbolByteArray, SymbolDirectedInfinity, SymbolOutputForm, ) - from mathics.eval.makeboxes import format_element SymbolStringInsert = Symbol("StringInsert") diff --git a/mathics/builtin/structure.py b/mathics/builtin/structure.py index c416a1fca..66c71f701 100644 --- a/mathics/builtin/structure.py +++ b/mathics/builtin/structure.py @@ -5,32 +5,16 @@ Structural transformations on lists, and general symbolic expressions. """ -from mathics.builtin.base import ( - Builtin, - Predefined, - BinaryOperator, -) -from mathics.core.atoms import ( - Integer, - Integer0, - Integer1, - Rational, -) +import platform + +from mathics.builtin.base import BinaryOperator, Builtin, Predefined +from mathics.builtin.lists import walk_levels +from mathics.core.atoms import Integer, Integer0, Integer1, Rational from mathics.core.expression import Expression from mathics.core.rules import Pattern -from mathics.core.symbols import ( - Atom, - Symbol, - SymbolFalse, - SymbolTrue, -) - +from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue from mathics.core.systemsymbols import SymbolDirectedInfinity, SymbolMap -from mathics.builtin.lists import walk_levels - -import platform - if platform.python_implementation() == "PyPy": bytecount_support = False else: diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index 711857241..f58ff9099 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -9,25 +9,16 @@ import gc import os import platform -import sys import subprocess +import sys from mathics import version_string from mathics.builtin.base import Builtin, Predefined -from mathics.core.atoms import ( - Integer, - Integer0, - IntegerM1, - Real, - String, -) +from mathics.core.atoms import Integer, Integer0, IntegerM1, Real, String from mathics.core.convert.expression import to_mathics_list from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.systemsymbols import ( - SymbolFailed, - SymbolRule, -) +from mathics.core.systemsymbols import SymbolFailed, SymbolRule from mathics.version import __version__ try: diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index ace75681f..f8f620b5f 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -12,22 +12,13 @@ from mathics.algorithm.parts import get_part -from mathics.builtin.base import Builtin, BinaryOperator - -from mathics.core.atoms import ( - Integer, - String, -) +from mathics.builtin.base import BinaryOperator, Builtin +from mathics.core.atoms import Integer, String from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_PROTECTED from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.rules import Pattern -from mathics.core.symbols import ( - Atom, - Symbol, - SymbolFalse, - SymbolTrue, -) +from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue def get_default_distance(p): diff --git a/mathics/builtin/trace.py b/mathics/builtin/trace.py index bd0097eb8..a523baa35 100644 --- a/mathics/builtin/trace.py +++ b/mathics/builtin/trace.py @@ -11,22 +11,17 @@ """ -from mathics.builtin.base import Builtin - +from collections import defaultdict +from time import time +from typing import Callable -from mathics.core.attributes import ( - A_HOLD_ALL, - A_PROTECTED, -) +from mathics.builtin.base import Builtin +from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED from mathics.core.convert.python import from_bool from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation from mathics.core.rules import BuiltinRule -from mathics.core.symbols import strip_context, SymbolTrue, SymbolFalse, SymbolNull - -from time import time -from collections import defaultdict -from typing import Callable +from mathics.core.symbols import SymbolFalse, SymbolNull, SymbolTrue, strip_context def traced_do_replace(self, expression, vars, options, evaluation): diff --git a/mathics/builtin/vectors/vector_space_operations.py b/mathics/builtin/vectors/vector_space_operations.py index eb8059a44..b6175df0b 100644 --- a/mathics/builtin/vectors/vector_space_operations.py +++ b/mathics/builtin/vectors/vector_space_operations.py @@ -8,9 +8,7 @@ from mathics.builtin.base import Builtin, SympyFunction from mathics.core.atoms import Complex, Integer, Integer0, Integer1, Real -from mathics.core.attributes import ( - # A_LISTABLE, - # A_NUMERIC_FUNCTION, +from mathics.core.attributes import ( # A_LISTABLE,; A_NUMERIC_FUNCTION, A_PROTECTED, A_READ_PROTECTED, ) diff --git a/mathics/compile/__init__.py b/mathics/compile/__init__.py index 819f007d8..9c4601445 100644 --- a/mathics/compile/__init__.py +++ b/mathics/compile/__init__.py @@ -16,7 +16,7 @@ if has_llvmlite: - from .ir import IRGenerator - from .compile import _compile from .base import CompileArg, CompileError + from .compile import _compile + from .ir import IRGenerator from .types import * diff --git a/mathics/compile/compile.py b/mathics/compile/compile.py index bf0fd47c5..ac3f3c427 100644 --- a/mathics/compile/compile.py +++ b/mathics/compile/compile.py @@ -1,9 +1,9 @@ -import llvmlite.binding as llvm from ctypes import CFUNCTYPE -from mathics.compile.utils import llvm_to_ctype -from mathics.compile.ir import IRGenerator +import llvmlite.binding as llvm +from mathics.compile.ir import IRGenerator +from mathics.compile.utils import llvm_to_ctype # setup llvm for code generation llvm.initialize() diff --git a/mathics/compile/ir.py b/mathics/compile/ir.py index 31fbcf490..7f1492b1d 100644 --- a/mathics/compile/ir.py +++ b/mathics/compile/ir.py @@ -1,12 +1,12 @@ -from functools import reduce +import ctypes import itertools +from functools import reduce from llvmlite import ir -import ctypes from mathics.compile.base import CompileError -from mathics.compile.types import int_type, real_type, bool_type, void_type -from mathics.compile.utils import pairwise, llvm_to_ctype +from mathics.compile.types import bool_type, int_type, real_type, void_type +from mathics.compile.utils import llvm_to_ctype, pairwise from mathics.core.atoms import Integer, Real from mathics.core.expression import Expression from mathics.core.symbols import Symbol diff --git a/mathics/compile/utils.py b/mathics/compile/utils.py index 80cdd2569..091c5e960 100644 --- a/mathics/compile/utils.py +++ b/mathics/compile/utils.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from mathics.compile.types import int_type, real_type, bool_type, void_type -from ctypes import c_int64, c_double, c_bool, c_void_p +from ctypes import c_bool, c_double, c_int64, c_void_p + +from mathics.compile.types import bool_type, int_type, real_type, void_type def pairwise(args): diff --git a/mathics/core/assignment.py b/mathics/core/assignment.py index dc2c8d73d..0f6c13b91 100644 --- a/mathics/core/assignment.py +++ b/mathics/core/assignment.py @@ -3,10 +3,12 @@ Support for Set and SetDelayed, and other assignment-like builtins """ +from functools import reduce from typing import Optional, Tuple from mathics.algorithm.parts import walk_parts from mathics.core.atoms import Atom, Integer +from mathics.core.attributes import A_LOCKED, A_PROTECTED, attribute_string_to_number from mathics.core.element import BaseElement from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit from mathics.core.expression import Expression, SymbolDefault @@ -16,8 +18,8 @@ Symbol, SymbolFalse, SymbolList, - SymbolMinPrecision, SymbolMaxPrecision, + SymbolMinPrecision, SymbolN, SymbolTrue, valid_context_name, @@ -35,11 +37,6 @@ ) -from mathics.core.attributes import attribute_string_to_number, A_LOCKED, A_PROTECTED - -from functools import reduce - - class AssignmentException(Exception): def __init__(self, lhs, rhs) -> None: super().__init__(" %s cannot be assigned to %s" % (rhs, lhs)) diff --git a/mathics/core/convert/expression.py b/mathics/core/convert/expression.py index 7358f8f8a..ec93b08ab 100644 --- a/mathics/core/convert/expression.py +++ b/mathics/core/convert/expression.py @@ -6,7 +6,6 @@ from mathics.core.expression import Expression, convert_expression_elements from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolList - from mathics.eval.numerify import numerify diff --git a/mathics/core/convert/function.py b/mathics/core/convert/function.py index aed748577..cd7e9591a 100644 --- a/mathics/core/convert/function.py +++ b/mathics/core/convert/function.py @@ -1,22 +1,16 @@ # -*- coding: utf-8 -*- +from typing import Callable, Optional, Tuple + from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression, from_python -from mathics.eval.nevaluator import eval_N from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue -from mathics.core.systemsymbols import ( - SymbolBlank, - SymbolInteger, - SymbolReal, -) - - -from typing import Optional, Callable, Tuple - +from mathics.core.systemsymbols import SymbolBlank, SymbolInteger, SymbolReal +from mathics.eval.nevaluator import eval_N try: - from mathics.compile.types import int_type, real_type, bool_type - from mathics.compile import _compile, CompileArg, CompileError + from mathics.compile import CompileArg, CompileError, _compile + from mathics.compile.types import bool_type, int_type, real_type use_llvm = True # _Complex not implemented diff --git a/mathics/core/convert/mpmath.py b/mathics/core/convert/mpmath.py index 1e0a27a16..655189cf4 100644 --- a/mathics/core/convert/mpmath.py +++ b/mathics/core/convert/mpmath.py @@ -1,16 +1,11 @@ # -*- coding: utf-8 -*- -import mpmath -import sympy from functools import lru_cache +import mpmath +import sympy -from mathics.core.atoms import ( - Complex, - MachineReal, - MachineReal0, - PrecisionReal, -) +from mathics.core.atoms import Complex, MachineReal, MachineReal0, PrecisionReal @lru_cache(maxsize=1024) diff --git a/mathics/core/convert/op.py b/mathics/core/convert/op.py index ab7b7d5f7..3a77e4cba 100644 --- a/mathics/core/convert/op.py +++ b/mathics/core/convert/op.py @@ -3,10 +3,10 @@ """ import os.path as osp -import pkg_resources - from functools import lru_cache +import pkg_resources + try: import ujson except ImportError: diff --git a/mathics/core/convert/python.py b/mathics/core/convert/python.py index 2e94c0685..d8ed24dc6 100644 --- a/mathics/core/convert/python.py +++ b/mathics/core/convert/python.py @@ -4,15 +4,9 @@ """ from typing import Any -from mathics.core.number import get_type -from mathics.core.atoms import ( - Complex, - Integer, - Real, - Rational, - String, -) +from mathics.core.atoms import Complex, Integer, Rational, Real, String +from mathics.core.number import get_type from mathics.core.symbols import ( BaseElement, Symbol, @@ -20,7 +14,7 @@ SymbolNull, SymbolTrue, ) -from mathics.core.systemsymbols import SymbolRule, SymbolByteArray +from mathics.core.systemsymbols import SymbolByteArray, SymbolRule def from_bool(arg: bool) -> Symbol: diff --git a/mathics/core/convert/sympy.py b/mathics/core/convert/sympy.py index b10f01333..84c29c1b4 100644 --- a/mathics/core/convert/sympy.py +++ b/mathics/core/convert/sympy.py @@ -5,13 +5,14 @@ Conversion to SymPy is handled directly in BaseElement descendants. """ -import sympy - from typing import Optional +import sympy + BasicSympy = sympy.Expr +from mathics.core.convert.matrix import matrix_data from mathics.core.symbols import ( Symbol, SymbolFalse, @@ -19,8 +20,8 @@ SymbolPower, SymbolTimes, SymbolTrue, - sympy_symbol_prefix, sympy_slot_prefix, + sympy_symbol_prefix, ) from mathics.core.systemsymbols import ( SymbolC, @@ -39,8 +40,6 @@ SymbolUnequal, ) -from mathics.core.convert.matrix import matrix_data - SymbolPrime = Symbol("Prime") SymbolRoot = Symbol("Root") SymbolRootSum = Symbol("RootSum") @@ -157,23 +156,20 @@ def eval(cls, n): def from_sympy(expr): from mathics.builtin import sympy_to_mathics from mathics.core.atoms import ( + Complex, Integer, Integer0, Integer1, + MachineReal, Rational, Real, - Complex, String, - MachineReal, ) from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.expression import Expression from mathics.core.list import ListExpression - from mathics.core.symbols import ( - Symbol, - SymbolNull, - ) from mathics.core.number import machine_precision + from mathics.core.symbols import Symbol, SymbolNull if isinstance(expr, (tuple, list)): return to_mathics_list(*expr, elements_conversion_fn=from_sympy) diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 731222ea3..984a05294 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -1,30 +1,23 @@ # -*- coding: utf-8 -*- -import pickle - -import os import base64 -import re import bisect - +import os +import pickle +import re from collections import defaultdict - from typing import List, Optional +from mathics_scanner.tokeniser import full_names_pattern + from mathics.core.atoms import String from mathics.core.attributes import A_NO_ATTRIBUTES from mathics.core.convert.expression import to_mathics_list from mathics.core.element import fully_qualified_symbol_name from mathics.core.expression import Expression -from mathics.core.symbols import ( - Atom, - Symbol, - strip_context, -) +from mathics.core.symbols import Atom, Symbol, strip_context from mathics.core.systemsymbols import SymbolGet -from mathics_scanner.tokeniser import full_names_pattern - type_compiled_pattern = type(re.compile("a.a")) # The contents of $OutputForms. FormMeta in mathics.base.forms adds to this. @@ -108,11 +101,6 @@ def __init__( "Global`", ) - from mathics.core.pymathics import ( - PyMathicsLoadException, - load_pymathics_module, - ) - # Importing "mathics.format" populates the Symbol of the # PrintForms and OutputForms sets. # @@ -121,8 +109,8 @@ def __init__( # 2 were given # Rocky: this smells of something not quite right in terms of # modularity. - import mathics.format # noqa + from mathics.core.pymathics import PyMathicsLoadException, load_pymathics_module self.printforms = list(PrintForms) self.outputforms = list(OutputForms) @@ -130,7 +118,7 @@ def __init__( self.timing_trace_evaluation = False if add_builtin: - from mathics.builtin import modules, contribute + from mathics.builtin import contribute, modules from mathics.settings import ROOT_DIR loaded = False diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py index 14592d7ce..1f9c3c836 100644 --- a/mathics/core/evaluation.py +++ b/mathics/core/evaluation.py @@ -1,19 +1,15 @@ # -*- coding: utf-8 -*- -from queue import Queue -import time - - import os import sys +import time +from queue import Queue from threading import Thread, stack_size as set_thread_stack_size - from typing import Tuple from mathics_scanner import TranslateError from mathics import settings - from mathics.core.atoms import Integer, String from mathics.core.convert.python import from_python from mathics.core.element import KeyComparable, ensure_context @@ -25,12 +21,7 @@ TimeoutInterrupt, WLThrowInterrupt, ) - -from mathics.core.symbols import ( - Symbol, - SymbolNull, -) - +from mathics.core.symbols import Symbol, SymbolNull from mathics.core.systemsymbols import ( SymbolAborted, SymbolBreak, @@ -460,7 +451,7 @@ def format_output(self, expr, format=None): if isinstance(format, dict): return dict((k, self.format_output(expr, f)) for k, f in format.items()) - from mathics.core.expression import Expression, BoxError + from mathics.core.expression import BoxError, Expression if format == "text": result = format_element(expr, self, SymbolOutputForm) diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 4019acda1..782cc0c24 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -1,13 +1,13 @@ # cython: language_level=3 # -*- coding: utf-8 -*- -import sympy import math import time - -from typing import Any, Callable, Iterable, List, Optional, Tuple, Type -from itertools import chain from bisect import bisect_left +from itertools import chain +from typing import Any, Callable, Iterable, List, Optional, Tuple, Type + +import sympy from mathics.core.atoms import Integer, String @@ -25,8 +25,8 @@ A_SEQUENCE_HOLD, attribute_string_to_number, ) -from mathics.core.convert.sympy import sympy_symbol_prefix, SympyExpression from mathics.core.convert.python import from_python +from mathics.core.convert.sympy import SympyExpression, sympy_symbol_prefix from mathics.core.element import ElementsProperties, EvalMixin, ensure_context from mathics.core.evaluation import Evaluation from mathics.core.interrupt import ReturnInterrupt diff --git a/mathics/core/formatter.py b/mathics/core/formatter.py index 6cca10fef..19176fbb8 100644 --- a/mathics/core/formatter.py +++ b/mathics/core/formatter.py @@ -1,10 +1,9 @@ import inspect -from typing import Callable import re +from typing import Callable from mathics.core.element import BoxElementMixin - # key is str: (to_xxx name, value) is formatter function to call format2fn: dict = {} diff --git a/mathics/core/number.py b/mathics/core/number.py index b528f49bb..ba166ed61 100644 --- a/mathics/core/number.py +++ b/mathics/core/number.py @@ -1,18 +1,17 @@ # -*- coding: utf-8 -*- # cython: language_level=3 -import sympy -import mpmath - -from math import log, log2, ceil import string - +from math import ceil, log, log2 from typing import List, Optional +import mpmath +import sympy + from mathics.core.symbols import ( - SymbolMinPrecision, - SymbolMaxPrecision, SymbolMachinePrecision, + SymbolMaxPrecision, + SymbolMinPrecision, ) C = log2(10) # ~ 3.3219280948873626 diff --git a/mathics/core/parser/__init__.py b/mathics/core/parser/__init__.py index ecfdaa3d1..39e5238c1 100644 --- a/mathics/core/parser/__init__.py +++ b/mathics/core/parser/__init__.py @@ -18,8 +18,8 @@ MathicsMultiLineFeeder, MathicsSingleLineFeeder, ) -from mathics.core.parser.util import parse, parse_builtin_rule from mathics.core.parser.operators import all_operator_names +from mathics.core.parser.util import parse, parse_builtin_rule __all__ = [ "MathicsFileLineFeeder", diff --git a/mathics/core/parser/convert.py b/mathics/core/parser/convert.py index ce2f8d767..6fae9d1c5 100644 --- a/mathics/core/parser/convert.py +++ b/mathics/core/parser/convert.py @@ -3,26 +3,19 @@ Conversion from AST node to Mathic BaseElement objects """ +from math import log10 from typing import Tuple -from math import log10 import sympy -from mathics.core.atoms import ( - Integer, - MachineReal, - PrecisionReal, - Rational, - String, -) - +from mathics.core.atoms import Integer, MachineReal, PrecisionReal, Rational, String from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.number import machine_precision, reconstruct_digits from mathics.core.parser.ast import ( - Symbol as AST_Symbol, - String as AST_String, - Number as AST_Number, Filename as AST_Filename, + Number as AST_Number, + String as AST_String, + Symbol as AST_Symbol, ) from mathics.core.symbols import Symbol, SymbolList diff --git a/mathics/core/parser/operators.py b/mathics/core/parser/operators.py index 61e5e26a0..2489abfd8 100644 --- a/mathics/core/parser/operators.py +++ b/mathics/core/parser/operators.py @@ -4,7 +4,6 @@ from collections import defaultdict - prefix_ops = { "Get": 720, "PreIncrement": 660, diff --git a/mathics/core/parser/parser.py b/mathics/core/parser/parser.py index 7e681b63c..d9d249ef4 100644 --- a/mathics/core/parser/parser.py +++ b/mathics/core/parser/parser.py @@ -10,22 +10,21 @@ is_symbol_name, ) -from mathics.core.parser.ast import Node, Number, Symbol, String, Filename +from mathics.core.parser.ast import Filename, Node, Number, String, Symbol from mathics.core.parser.operators import ( - prefix_ops, - postfix_ops, - left_binary_ops, - right_binary_ops, - nonassoc_binary_ops, - flat_binary_ops, - ternary_ops, - binary_ops, all_ops, + binary_ops, + flat_binary_ops, inequality_ops, + left_binary_ops, misc_ops, + nonassoc_binary_ops, + postfix_ops, + prefix_ops, + right_binary_ops, + ternary_ops, ) - special_symbols = { "\u03C0": "Pi", # Pi "\uF74D": "E", # ExponentialE diff --git a/mathics/core/parser/util.py b/mathics/core/parser/util.py index c8739d3bf..505162862 100644 --- a/mathics/core/parser/util.py +++ b/mathics/core/parser/util.py @@ -3,9 +3,9 @@ from typing import Any, FrozenSet, Tuple -from mathics.core.parser.parser import Parser -from mathics.core.parser.feed import MathicsSingleLineFeeder from mathics.core.parser.convert import convert +from mathics.core.parser.feed import MathicsSingleLineFeeder +from mathics.core.parser.parser import Parser from mathics.core.symbols import ensure_context parser = Parser() diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py index b0a398d58..660d7d275 100644 --- a/mathics/core/pattern.py +++ b/mathics/core/pattern.py @@ -2,9 +2,11 @@ # cython: profile=False # -*- coding: utf-8 -*- +from itertools import chain from typing import Optional from mathics.core.atoms import Integer +from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_ORDERLESS from mathics.core.element import BaseElement, ensure_context from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression, SymbolDefault @@ -23,10 +25,7 @@ SymbolRepeatedNull, SymbolSequence, ) -from mathics.core.util import subsets, subranges, permutations -from itertools import chain - -from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_ORDERLESS +from mathics.core.util import permutations, subranges, subsets # FIXME: create definitions in systemsymbols for missing items below. SYSTEM_SYMBOLS_PATTERNS = symbol_set( diff --git a/mathics/core/pymathics.py b/mathics/core/pymathics.py index 354404b10..213d47d4a 100644 --- a/mathics/core/pymathics.py +++ b/mathics/core/pymathics.py @@ -6,7 +6,6 @@ import importlib import sys - from mathics.core.evaluation import Evaluation # This dict probably does not belong here. @@ -59,11 +58,7 @@ def load_pymathics_module(definitions, module): Loads Mathics builtin objects and their definitions from an external Python module in the pymathics module namespace. """ - from mathics.builtin import ( - builtins_by_module, - name_is_builtin_symbol, - Builtin, - ) + from mathics.builtin import Builtin, builtins_by_module, name_is_builtin_symbol if module in sys.modules: loaded_module = importlib.reload(sys.modules[module]) diff --git a/mathics/core/read.py b/mathics/core/read.py index d464b9db1..5531cf6d4 100644 --- a/mathics/core/read.py +++ b/mathics/core/read.py @@ -6,12 +6,12 @@ import os.path as osp from mathics.builtin.atomic.strings import to_python_encoding -from mathics.core.expression import Expression from mathics.core.atoms import Integer, String from mathics.core.exceptions import MessageException +from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import Symbol from mathics.core.streams import Stream, path_search, stream_manager +from mathics.core.symbols import Symbol # FIXME: don't use a module-level path INPUTFILE_VAR = "" diff --git a/mathics/core/rules.py b/mathics/core/rules.py index aa1f66e0b..47e33a349 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -2,13 +2,12 @@ # -*- coding: utf-8 -*- from inspect import signature +from itertools import chain from mathics.core.element import KeyComparable from mathics.core.expression import Expression -from mathics.core.symbols import strip_context from mathics.core.pattern import Pattern, StopGenerator - -from itertools import chain +from mathics.core.symbols import strip_context def _python_function_arguments(f): diff --git a/mathics/core/streams.py b/mathics/core/streams.py index 69a5122d1..2cf5aa988 100644 --- a/mathics/core/streams.py +++ b/mathics/core/streams.py @@ -3,15 +3,14 @@ """ File Stream Operations """ -from io import open as io_open import os -import requests +import os.path as osp import sys import tempfile - +from io import open as io_open from typing import Optional, Tuple -import os.path as osp +import requests from mathics.settings import ROOT_DIR diff --git a/mathics/core/structure.py b/mathics/core/structure.py index 9917ae32e..d10f9be43 100644 --- a/mathics/core/structure.py +++ b/mathics/core/structure.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- -from mathics.core.symbols import ( - SymbolList, -) +from mathics.core.symbols import SymbolList class Structure: diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py index 442c61320..907b32e1e 100644 --- a/mathics/doc/common_doc.py +++ b/mathics/doc/common_doc.py @@ -29,13 +29,11 @@ import os.path as osp import pkgutil import re - from os import getenv, listdir from types import ModuleType from typing import Callable -from mathics import builtin -from mathics import settings +from mathics import builtin, settings from mathics.builtin.base import check_requires_list from mathics.core.evaluation import Message, Print from mathics.core.util import IS_PYPY diff --git a/mathics/doc/latex/doc2latex.py b/mathics/doc/latex/doc2latex.py index 71755b186..715571c2c 100755 --- a/mathics/doc/latex/doc2latex.py +++ b/mathics/doc/latex/doc2latex.py @@ -9,16 +9,14 @@ import pickle import subprocess import sys - from argparse import ArgumentParser + from mpmath import __version__ as mpmathVersion from numpy import __version__ as NumPyVersion from sympy import __version__ as SymPyVersion import mathics - -from mathics import version_string, __version__ -from mathics import settings +from mathics import __version__, settings, version_string from mathics.doc.common_doc import MathicsMainDocumentation # Global variables diff --git a/mathics/docpipeline.py b/mathics/docpipeline.py index 01fa2667c..4f2df1d82 100644 --- a/mathics/docpipeline.py +++ b/mathics/docpipeline.py @@ -14,20 +14,16 @@ import pickle import re import sys - from argparse import ArgumentParser from datetime import datetime import mathics import mathics.settings - +from mathics import settings, version_string +from mathics.builtin import builtins_dict from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation, Output from mathics.core.parser import MathicsSingleLineFeeder -from mathics.builtin import builtins_dict - -from mathics import version_string -from mathics import settings from mathics.doc.common_doc import MathicsMainDocumentation from mathics.timing import show_lru_cache_statistics diff --git a/mathics/eval/makeboxes.py b/mathics/eval/makeboxes.py index 299efb19d..7c51be55f 100644 --- a/mathics/eval/makeboxes.py +++ b/mathics/eval/makeboxes.py @@ -9,27 +9,26 @@ import typing from typing import Any - -from mathics.core.atoms import SymbolI, String, Integer, Rational, Complex -from mathics.core.element import BaseElement, BoxElementMixin, EvalMixin +from mathics.core.atoms import Complex, Integer, Rational, String, SymbolI from mathics.core.convert.expression import to_expression_with_specialization from mathics.core.definitions import OutputForms +from mathics.core.element import BaseElement, BoxElementMixin, EvalMixin from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import ( - Symbol, - SymbolMakeBoxes, Atom, + Symbol, SymbolDivide, SymbolFullForm, SymbolGraphics, SymbolGraphics3D, SymbolHoldForm, SymbolList, + SymbolMakeBoxes, SymbolNumberForm, - SymbolPostfix, SymbolPlus, + SymbolPostfix, SymbolRepeated, SymbolRepeatedNull, SymbolTimes, @@ -42,7 +41,6 @@ SymbolStandardForm, ) - element_formatters = {} diff --git a/mathics/eval/nevaluator.py b/mathics/eval/nevaluator.py index dc21be0eb..845d42029 100644 --- a/mathics/eval/nevaluator.py +++ b/mathics/eval/nevaluator.py @@ -14,11 +14,7 @@ import sympy from mathics.core.atoms import Number -from mathics.core.attributes import ( - A_N_HOLD_ALL, - A_N_HOLD_FIRST, - A_N_HOLD_REST, -) +from mathics.core.attributes import A_N_HOLD_ALL, A_N_HOLD_FIRST, A_N_HOLD_REST from mathics.core.convert.sympy import from_sympy from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation diff --git a/mathics/eval/numerify.py b/mathics/eval/numerify.py index 26e8c3828..131404f06 100644 --- a/mathics/eval/numerify.py +++ b/mathics/eval/numerify.py @@ -13,10 +13,9 @@ from mathics.core.atoms import Integer, Number from mathics.core.element import BaseElement, EvalMixin -from mathics.core.expression import Expression from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression from mathics.core.number import dps - from mathics.eval.nevaluator import eval_N diff --git a/mathics/eval/plot.py b/mathics/eval/plot.py index db0000574..32467626f 100644 --- a/mathics/eval/plot.py +++ b/mathics/eval/plot.py @@ -7,7 +7,7 @@ """ from math import cos, isinf, isnan, pi, sqrt -from typing import Callable, Iterable, List, Optional, Union, Type +from typing import Callable, Iterable, List, Optional, Type, Union from mathics.builtin.numeric import chop from mathics.builtin.options import options_to_rules diff --git a/mathics/format/__init__.py b/mathics/format/__init__.py index 925bed3ac..3c35e0041 100644 --- a/mathics/format/__init__.py +++ b/mathics/format/__init__.py @@ -24,10 +24,9 @@ """ -import os.path as osp import glob import importlib - +import os.path as osp __py_files__ = [ osp.basename(f[0:-3]) diff --git a/mathics/format/asy.py b/mathics/format/asy.py index e628691a0..5223cbba8 100644 --- a/mathics/format/asy.py +++ b/mathics/format/asy.py @@ -6,7 +6,6 @@ import re from mathics.builtin.box.graphics import ( - _ArcBox, ArrowBox, BezierCurveBox, FilledCurveBox, @@ -15,22 +14,22 @@ PointBox, PolygonBox, RectangleBox, + _ArcBox, _RoundBox, ) - from mathics.builtin.box.graphics3d import ( - Graphics3DElements, Arrow3DBox, Cone3DBox, Cuboid3DBox, Cylinder3DBox, + Graphics3DElements, Line3DBox, Point3DBox, Polygon3DBox, Sphere3DBox, Tube3DBox, ) - +from mathics.builtin.box.uniform_polyhedra import UniformPolyhedron3DBox from mathics.builtin.graphics import ( DEFAULT_POINT_FACTOR, GraphicsElements, @@ -38,12 +37,10 @@ RGBColor, ) -from mathics.builtin.box.uniform_polyhedra import UniformPolyhedron3DBox - INVERSE_POINT_FACTOR = 1 / DEFAULT_POINT_FACTOR -from mathics.core.formatter import lookup_method, add_conversion_fn +from mathics.core.formatter import add_conversion_fn, lookup_method from mathics.format.asy_fns import ( asy_add_bezier_fn, asy_add_graph_import, diff --git a/mathics/format/json.py b/mathics/format/json.py index 292ae0f52..28632e8b0 100644 --- a/mathics/format/json.py +++ b/mathics/format/json.py @@ -5,12 +5,6 @@ Right now this happens mostly for graphics primitives. """ -from mathics.builtin.graphics import PointSize - -from mathics.builtin.drawing.graphics3d import ( - Graphics3DElements, -) - from mathics.builtin.box.graphics3d import ( Arrow3DBox, Cone3DBox, @@ -22,16 +16,15 @@ Sphere3DBox, Tube3DBox, ) - from mathics.builtin.box.uniform_polyhedra import UniformPolyhedron3DBox +from mathics.builtin.drawing.graphics3d import Graphics3DElements +from mathics.builtin.graphics import PointSize +from mathics.core.formatter import add_conversion_fn, lookup_method # FIXME # Add 2D elements like DensityPlot -from mathics.core.formatter import lookup_method, add_conversion_fn - - def convert_coord_collection( collection: list, object_type: str, color, default_values: dict = {} ) -> list: diff --git a/mathics/format/latex.py b/mathics/format/latex.py index 2c472c0ae..accb49d8c 100644 --- a/mathics/format/latex.py +++ b/mathics/format/latex.py @@ -17,28 +17,25 @@ from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( + FractionBox, GridBox, RowBox, + SqrtBox, StyleBox, SubscriptBox, - SuperscriptBox, SubsuperscriptBox, - SqrtBox, - FractionBox, + SuperscriptBox, ) from mathics.builtin.colors.color_directives import RGBColor - from mathics.core.atoms import String from mathics.core.exceptions import BoxConstructError from mathics.core.formatter import ( - lookup_method as lookup_conversion_method, add_conversion_fn, + lookup_method as lookup_conversion_method, ) from mathics.core.symbols import SymbolTrue - from mathics.format.asy_fns import asy_color, asy_create_pens, asy_number - # mathics_scanner does not generates this table in a way that we can load it here. # When it get fixed, we can use that table instead of this one: diff --git a/mathics/format/mathml.py b/mathics/format/mathml.py index e0281e8ec..d26421018 100644 --- a/mathics/format/mathml.py +++ b/mathics/format/mathml.py @@ -8,27 +8,24 @@ import base64 import html - +from mathics.builtin.box.graphics import GraphicsBox +from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( + FractionBox, GridBox, RowBox, + SqrtBox, + StyleBox, SubscriptBox, - SuperscriptBox, SubsuperscriptBox, - StyleBox, - SqrtBox, - FractionBox, + SuperscriptBox, ) -from mathics.builtin.box.graphics import GraphicsBox -from mathics.builtin.box.graphics3d import Graphics3DBox - - from mathics.core.atoms import String from mathics.core.element import BoxElementMixin from mathics.core.exceptions import BoxConstructError from mathics.core.formatter import ( - lookup_method as lookup_conversion_method, add_conversion_fn, + lookup_method as lookup_conversion_method, ) from mathics.core.parser import is_symbol_name from mathics.core.symbols import SymbolTrue diff --git a/mathics/format/svg.py b/mathics/format/svg.py index 1cc46c900..0dc4fa702 100644 --- a/mathics/format/svg.py +++ b/mathics/format/svg.py @@ -4,7 +4,6 @@ """ from mathics.builtin.box.graphics import ( - _ArcBox, ArrowBox, BezierCurveBox, FilledCurveBox, @@ -14,9 +13,9 @@ PointBox, PolygonBox, RectangleBox, + _ArcBox, _RoundBox, ) - from mathics.builtin.drawing.graphics3d import Graphics3DElements from mathics.builtin.graphics import ( DEFAULT_POINT_FACTOR, @@ -24,8 +23,7 @@ PointSize, _svg_bezier, ) - -from mathics.core.formatter import lookup_method, add_conversion_fn +from mathics.core.formatter import add_conversion_fn, lookup_method class _SVGTransform: diff --git a/mathics/format/text.py b/mathics/format/text.py index 94ba78ab2..7a60c9202 100644 --- a/mathics/format/text.py +++ b/mathics/format/text.py @@ -7,22 +7,18 @@ from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( + FractionBox, GridBox, RowBox, + SqrtBox, StyleBox, SubscriptBox, - SuperscriptBox, SubsuperscriptBox, - SqrtBox, - FractionBox, + SuperscriptBox, ) - from mathics.core.atoms import String from mathics.core.exceptions import BoxConstructError -from mathics.core.formatter import ( - add_conversion_fn, - lookup_method, -) +from mathics.core.formatter import add_conversion_fn, lookup_method from mathics.core.symbols import Atom, SymbolTrue diff --git a/mathics/main.py b/mathics/main.py index 3b90a24f6..a55e49d83 100755 --- a/mathics/main.py +++ b/mathics/main.py @@ -5,24 +5,22 @@ import atexit import locale import os +import os.path as osp import re import subprocess import sys -import os.path as osp - -from mathics import settings -from mathics import version_string, license_string, __version__ +from mathics import __version__, license_string, settings, version_string from mathics.builtin.trace import TraceBuiltins, traced_do_replace from mathics.core.atoms import String -from mathics.core.definitions import autoload_files, Definitions, Symbol +from mathics.core.definitions import Definitions, Symbol, autoload_files from mathics.core.evaluation import Evaluation, Output from mathics.core.expression import Expression from mathics.core.parser import MathicsFileLineFeeder, MathicsLineFeeder from mathics.core.read import channel_to_stream from mathics.core.rules import BuiltinRule -from mathics.core.symbols import strip_context, SymbolNull from mathics.core.streams import stream_manager +from mathics.core.symbols import SymbolNull, strip_context from mathics.timing import show_lru_cache_statistics diff --git a/mathics/session.py b/mathics/session.py index 88a4957b6..6d3054cf8 100644 --- a/mathics/session.py +++ b/mathics/session.py @@ -12,11 +12,10 @@ import os.path as osp from typing import Optional -from mathics.core.definitions import autoload_files -from mathics.core.parser import parse, MathicsSingleLineFeeder -from mathics.core.definitions import Definitions -from mathics.core.evaluation import Evaluation import mathics.settings +from mathics.core.definitions import Definitions, autoload_files +from mathics.core.evaluation import Evaluation +from mathics.core.parser import MathicsSingleLineFeeder, parse def load_default_settings_files( diff --git a/mathics/settings.py b/mathics/settings.py index 88e7d99d1..554535f4d 100644 --- a/mathics/settings.py +++ b/mathics/settings.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- -import pkg_resources -import sys import os import os.path as osp +import sys from pathlib import Path +import pkg_resources + def get_srcdir(): filename = osp.normcase(osp.dirname(osp.abspath(__file__))) diff --git a/mathics/timing.py b/mathics/timing.py index 693b7bdca..07db752cb 100644 --- a/mathics/timing.py +++ b/mathics/timing.py @@ -65,10 +65,10 @@ def show_lru_cache_statistics(): """ Print statistics from LRU caches (@lru_cache of functools) """ - from mathics.core.atoms import Integer, Rational - from mathics.builtin.arithmetic import call_mpmath, _MPMathFunction + from mathics.builtin.arithmetic import _MPMathFunction, call_mpmath from mathics.builtin.atomic.numbers import log_n_b from mathics.builtin.base import run_sympy + from mathics.core.atoms import Integer, Rational from mathics.core.convert.mpmath import from_mpmath print(f"Integer {Integer.__init__.cache_info()}") diff --git a/setup.py b/setup.py index c0162a8ae..56bc0b2e8 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ import re import sys -from setuptools import setup, Extension +from setuptools import Extension, setup is_PyPy = platform.python_implementation() == "PyPy" diff --git a/test/builtin/arithmetic/test_assumptions.py b/test/builtin/arithmetic/test_assumptions.py index 561c8f5ec..0f8a4b372 100644 --- a/test/builtin/arithmetic/test_assumptions.py +++ b/test/builtin/arithmetic/test_assumptions.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -from ...helper import check_evaluation import pytest +from ...helper import check_evaluation + list_test_assumptions_integrate = [ ( "Integrate[x^n, {x, 0, 1}]", diff --git a/test/builtin/atomic/test_strings.py b/test/builtin/atomic/test_strings.py index 6bdd27b71..45104e059 100644 --- a/test/builtin/atomic/test_strings.py +++ b/test/builtin/atomic/test_strings.py @@ -5,6 +5,7 @@ In particular, Alphabet """ from test.helper import check_evaluation + import pytest diff --git a/test/builtin/colors/test_colors.py b/test/builtin/colors/test_colors.py index a3d22e1af..1bde91131 100755 --- a/test/builtin/colors/test_colors.py +++ b/test/builtin/colors/test_colors.py @@ -7,7 +7,6 @@ import unittest from random import random - import mathics.builtin.colors.color_internals as colors from mathics.builtin.numpy_utils import array, stacked, vectorize from mathics.core.atoms import String @@ -17,7 +16,6 @@ from mathics.core.expression import Expression from mathics.core.systemsymbols import SymbolColorConvert - _color_tests = [ [ [ diff --git a/test/builtin/files_io/test_files.py b/test/builtin/files_io/test_files.py index 82926f3de..3e0a2bf6c 100644 --- a/test/builtin/files_io/test_files.py +++ b/test/builtin/files_io/test_files.py @@ -3,10 +3,11 @@ Unit tests from builtins/files_io/files.py """ import os.path as osp -import pytest import sys from test.helper import check_evaluation, evaluate +import pytest + def test_compress(): for text in ("", "abc", " "): diff --git a/test/builtin/files_io/test_importexport.py b/test/builtin/files_io/test_importexport.py index 2b28c9a61..c85d8f59c 100644 --- a/test/builtin/files_io/test_importexport.py +++ b/test/builtin/files_io/test_importexport.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- -import tempfile import os import os.path as osp -import pytest import sys -from ...helper import session +import tempfile + +import pytest from mathics.builtin.atomic.strings import to_python_encoding +from ...helper import session + # def test_import(): # eaccent = "\xe9" # for str_expr, str_expected, message in ( diff --git a/test/builtin/image/test_image.py b/test/builtin/image/test_image.py index 7989fde86..bb6e7a523 100644 --- a/test/builtin/image/test_image.py +++ b/test/builtin/image/test_image.py @@ -5,9 +5,10 @@ Image[] and image related functions. """ import os +from test.helper import evaluate + import pytest -from test.helper import evaluate from mathics.builtin.base import check_requires_list from mathics.core.symbols import SymbolNull diff --git a/test/builtin/numbers/test_calculus.py b/test/builtin/numbers/test_calculus.py index 044d86398..e928a6619 100644 --- a/test/builtin/numbers/test_calculus.py +++ b/test/builtin/numbers/test_calculus.py @@ -8,11 +8,12 @@ """ -import pytest +from test.helper import check_evaluation, evaluate from typing import Optional -from test.helper import evaluate, check_evaluation -from mathics.builtin.base import check_requires_list +import pytest + +from mathics.builtin.base import check_requires_list if check_requires_list(["scipy", "scipy.integrate"]): methods_findminimum = ["Automatic", "Newton", "brent", "golden"] diff --git a/test/builtin/numbers/test_nintegrate.py b/test/builtin/numbers/test_nintegrate.py index e4943fa5a..e88f8ae29 100644 --- a/test/builtin/numbers/test_nintegrate.py +++ b/test/builtin/numbers/test_nintegrate.py @@ -12,7 +12,6 @@ from mathics.builtin.base import check_requires_list - if check_requires_list(["scipy", "scipy.integrate"]): methods = ["Automatic", "Romberg", "Internal", "NQuadrature"] diff --git a/test/builtin/numbers/test_randomnumbers.py b/test/builtin/numbers/test_randomnumbers.py index 31fdaaf00..d2bf37277 100644 --- a/test/builtin/numbers/test_randomnumbers.py +++ b/test/builtin/numbers/test_randomnumbers.py @@ -3,9 +3,10 @@ Unit tests for mathics.builtins.numbers.randomnumbers """ -import pytest from test.helper import check_evaluation +import pytest + @pytest.mark.parametrize( ("str_expr", "str_expected"), diff --git a/test/builtin/test_assignment.py b/test/builtin/test_assignment.py index cb4e42d61..d8d34bd2a 100644 --- a/test/builtin/test_assignment.py +++ b/test/builtin/test_assignment.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- import os +from test.helper import check_evaluation, session import pytest -from test.helper import check_evaluation, session from mathics_scanner.errors import IncompleteSyntaxError DEBUGASSIGN = int(os.environ.get("DEBUGSET", "0")) == 1 diff --git a/test/builtin/test_attributes.py b/test/builtin/test_attributes.py index 41f76df66..65ee4020c 100644 --- a/test/builtin/test_attributes.py +++ b/test/builtin/test_attributes.py @@ -4,10 +4,10 @@ """ import os -import pytest - from test.helper import check_evaluation +import pytest + DEBUGRULESPAT = int(os.environ.get("DEBUGRULESPAT", "0")) == 1 if DEBUGRULESPAT: diff --git a/test/builtin/test_comparison.py b/test/builtin/test_comparison.py index 9efc55cbf..432dd62b2 100644 --- a/test/builtin/test_comparison.py +++ b/test/builtin/test_comparison.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -import pytest from test.helper import check_evaluation, session +import pytest + @pytest.mark.parametrize( ("str_expr", "str_expected", "assert_fail_message"), diff --git a/test/builtin/test_datentime.py b/test/builtin/test_datentime.py index 9430a923f..ac9053f74 100644 --- a/test/builtin/test_datentime.py +++ b/test/builtin/test_datentime.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- +import sys +import time from test.helper import check_evaluation, evaluate import pytest -import sys - -import time @pytest.mark.skipif( diff --git a/test/builtin/test_makeboxes.py b/test/builtin/test_makeboxes.py index 95e1a553d..3650f9681 100644 --- a/test/builtin/test_makeboxes.py +++ b/test/builtin/test_makeboxes.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- import os -import pytest from test.helper import check_evaluation, session + +import pytest from mathics_scanner.errors import IncompleteSyntaxError # To check the progress in the improvement of formatting routines, set this variable to 1. diff --git a/test/builtin/test_procedural.py b/test/builtin/test_procedural.py index 7620003de..779ec683c 100644 --- a/test/builtin/test_procedural.py +++ b/test/builtin/test_procedural.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- -import pytest from test.helper import check_evaluation, session +import pytest + + # NestWhile tests @pytest.mark.parametrize( ("str_expr", "str_expected"), diff --git a/test/consistency-and-style/test_duplicate_builtins.py b/test/consistency-and-style/test_duplicate_builtins.py index 56a597c7c..b052c15d3 100644 --- a/test/consistency-and-style/test_duplicate_builtins.py +++ b/test/consistency-and-style/test_duplicate_builtins.py @@ -4,9 +4,11 @@ In the past when reorganizing builtin functions we sometimes had missing or duplicate build-in functions definitions. """ -import pytest import os -from mathics.builtin import name_is_builtin_symbol, modules + +import pytest + +from mathics.builtin import modules, name_is_builtin_symbol from mathics.builtin.base import Builtin diff --git a/test/consistency-and-style/test_summary_text.py b/test/consistency-and-style/test_summary_text.py index 680663c4e..5a9d99617 100644 --- a/test/consistency-and-style/test_summary_text.py +++ b/test/consistency-and-style/test_summary_text.py @@ -1,15 +1,15 @@ -import pytest - import glob import importlib -import pkgutil import os import os.path as osp -from mathics.version import __version__ # noqa used in loading to check consistency. +import pkgutil +import pytest + +from mathics import __file__ as mathics_initfile_path from mathics.builtin import name_is_builtin_symbol from mathics.builtin.base import Builtin -from mathics import __file__ as mathics_initfile_path +from mathics.version import __version__ # noqa used in loading to check consistency. # Get file system path name for mathics.builtin mathics_path = osp.dirname(mathics_initfile_path) diff --git a/test/core/parser/test_convert.py b/test/core/parser/test_convert.py index d0928eee2..ea99bf362 100644 --- a/test/core/parser/test_convert.py +++ b/test/core/parser/test_convert.py @@ -1,6 +1,6 @@ -import unittest import random import sys +import unittest from mathics_scanner import ( IncompleteSyntaxError, @@ -9,21 +9,13 @@ SingleLineFeeder, ) -from mathics.core.atoms import ( - Integer, - Integer0, - Integer1, - Real, - Rational, - String, -) +from mathics.core.atoms import Integer, Integer0, Integer1, Rational, Real, String from mathics.core.definitions import Definitions from mathics.core.expression import Expression from mathics.core.parser import parse from mathics.core.symbols import Symbol from mathics.core.systemsymbols import SymbolDerivative - definitions = Definitions(add_builtin=True) diff --git a/test/core/parser/test_parser.py b/test/core/parser/test_parser.py index 629f2a1ce..e697400f8 100644 --- a/test/core/parser/test_parser.py +++ b/test/core/parser/test_parser.py @@ -2,8 +2,8 @@ # -*- coding: utf-8 -*- -import sys import random +import sys import unittest from mathics_scanner import ( @@ -13,7 +13,7 @@ SingleLineFeeder, ) -from mathics.core.parser.ast import Node, Symbol, Number, String, Filename +from mathics.core.parser.ast import Filename, Node, Number, String, Symbol from mathics.core.parser.parser import Parser diff --git a/test/core/parser/test_util.py b/test/core/parser/test_util.py index 7d8ae7b62..65e28dc80 100644 --- a/test/core/parser/test_util.py +++ b/test/core/parser/test_util.py @@ -10,7 +10,6 @@ from mathics.core.definitions import Definitions from mathics.core.parser import parse - definitions = Definitions(add_builtin=True) diff --git a/test/core/test_arithmetic.py b/test/core/test_arithmetic.py index e8ceeee23..64e78e078 100644 --- a/test/core/test_arithmetic.py +++ b/test/core/test_arithmetic.py @@ -3,6 +3,7 @@ import unittest + from mathics.core.atoms import ( Integer, Integer1, @@ -11,11 +12,10 @@ IntegerM1, Rational, ) +from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.definitions import Definitions -from mathics.core.convert.expression import to_expression from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression -from mathics.core.convert.expression import to_mathics_list from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolAbs, SymbolPlus, SymbolTimes diff --git a/test/core/test_atoms.py b/test/core/test_atoms.py index 03b24ebac..7dbd155d0 100644 --- a/test/core/test_atoms.py +++ b/test/core/test_atoms.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- -from mathics.core.evaluation import Evaluation -from mathics.core.expression import Expression +import sys + +import mathics.core.atoms as atoms +import mathics.core.systemsymbols as system_symbols from mathics.core.atoms import ( Complex, Integer, @@ -15,14 +17,12 @@ Real, String, ) +from mathics.core.definitions import Definitions +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue from mathics.core.systemsymbols import SymbolSameQ -from mathics.core.definitions import Definitions -import sys -import mathics.core.systemsymbols as system_symbols -import mathics.core.atoms as atoms - definitions = Definitions(add_builtin=True) diff --git a/test/core/test_expression.py b/test/core/test_expression.py index e9322606e..cb568e173 100644 --- a/test/core/test_expression.py +++ b/test/core/test_expression.py @@ -1,9 +1,12 @@ # -*- coding: utf-8 -*- -import pytest from test.helper import check_evaluation + +import pytest + from mathics.builtin.base import check_requires_list + # FIXME: come up with an example that doesn't require skimage. @pytest.mark.skipif( not check_requires_list(["skimage"]), diff --git a/test/core/test_expression_constructor.py b/test/core/test_expression_constructor.py index 8008ad1ae..01381067a 100644 --- a/test/core/test_expression_constructor.py +++ b/test/core/test_expression_constructor.py @@ -1,7 +1,7 @@ +from mathics.core.atoms import Integer, Integer1 from mathics.core.convert.expression import to_expression -from mathics.core.expression import Expression, ElementsProperties +from mathics.core.expression import ElementsProperties, Expression from mathics.core.symbols import SymbolPlus -from mathics.core.atoms import Integer, Integer1 def test_expression_constructor(): diff --git a/test/core/test_flatten_head.py b/test/core/test_flatten_head.py index f7043bcf6..f51ada3a2 100644 --- a/test/core/test_flatten_head.py +++ b/test/core/test_flatten_head.py @@ -1,17 +1,8 @@ # -*- coding: utf-8 -*- -from mathics.core.atoms import ( - Integer, - Integer1, - Integer2, - Integer3, -) - +from mathics.core.atoms import Integer, Integer1, Integer2, Integer3 from mathics.core.expression import Expression -from mathics.core.symbols import ( - Symbol, - SymbolPlus, -) +from mathics.core.symbols import Symbol, SymbolPlus def test_flatten_with_respect_to_head(): diff --git a/test/core/test_sympy_python_convert.py b/test/core/test_sympy_python_convert.py index 48289c41f..1cfa65d6f 100644 --- a/test/core/test_sympy_python_convert.py +++ b/test/core/test_sympy_python_convert.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- import random -import sympy import sys import unittest -from mathics.core.symbols import Symbol +import sympy + from mathics.core.atoms import ( Complex, Integer, @@ -20,7 +20,7 @@ from mathics.core.convert.sympy import from_sympy from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import SymbolPlus +from mathics.core.symbols import Symbol, SymbolPlus from mathics.core.systemsymbols import ( SymbolD, SymbolDerivative, diff --git a/test/format/test_asy.py b/test/format/test_asy.py index 30e43a186..2251231d1 100644 --- a/test/format/test_asy.py +++ b/test/format/test_asy.py @@ -1,12 +1,13 @@ import re -from mathics.core.expression import Expression -from mathics.core.symbols import Symbol + +from mathics.builtin.makeboxes import MakeBoxes from mathics.core.atoms import Integer0, Integer1 from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression from mathics.core.list import ListExpression +from mathics.core.symbols import Symbol from mathics.core.systemsymbols import SymbolGraphics, SymbolPoint from mathics.session import MathicsSession -from mathics.builtin.makeboxes import MakeBoxes session = MathicsSession(add_builtin=True, catch_interrupt=False) evaluation = Evaluation(session.definitions) diff --git a/test/format/test_format.py b/test/format/test_format.py index e8b3a7057..5d3539399 100644 --- a/test/format/test_format.py +++ b/test/format/test_format.py @@ -1,8 +1,9 @@ # from .helper import session -from mathics.session import MathicsSession -from mathics.core.symbols import Symbol import os +from mathics.core.symbols import Symbol +from mathics.session import MathicsSession + session = MathicsSession() # from mathics.builtin.base import BoxConstruct, Predefined diff --git a/test/format/test_svg.py b/test/format/test_svg.py index b7e3c2f49..4443d6020 100644 --- a/test/format/test_svg.py +++ b/test/format/test_svg.py @@ -1,13 +1,14 @@ import re -from mathics.core.symbols import Symbol + +from mathics.builtin.makeboxes import MakeBoxes from mathics.core.atoms import Integer0, Integer1 -from mathics.core.expression import Expression from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.formatter import lookup_method from mathics.core.list import ListExpression +from mathics.core.symbols import Symbol from mathics.core.systemsymbols import SymbolPoint from mathics.session import MathicsSession -from mathics.builtin.makeboxes import MakeBoxes -from mathics.core.formatter import lookup_method session = MathicsSession(add_builtin=True, catch_interrupt=False) evaluation = Evaluation(session.definitions) diff --git a/test/helper.py b/test/helper.py index f0371e82a..c7f7b2344 100644 --- a/test/helper.py +++ b/test/helper.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- import time -from mathics.session import MathicsSession from typing import Optional +from mathics.session import MathicsSession + # Set up a Mathics session with definitions. # For consistency set the character encoding ASCII which is # the lowest common denominator available on all systems. diff --git a/test/package/test_combinatorica.py b/test/package/test_combinatorica.py index cbdde2494..2c104050f 100644 --- a/test/package/test_combinatorica.py +++ b/test/package/test_combinatorica.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -from test.helper import evaluate, check_evaluation, reset_session +from test.helper import check_evaluation, evaluate, reset_session + import pytest # This variable is set to initialize the module just once, diff --git a/test/test_constrmatrix.py b/test/test_constrmatrix.py index 4d0a678f2..13a713087 100644 --- a/test/test_constrmatrix.py +++ b/test/test_constrmatrix.py @@ -1,6 +1,7 @@ -from .helper import session import pytest +from .helper import session + @pytest.mark.parametrize( ("str_expr", "str_expected", "fail_msg"), diff --git a/test/test_context.py b/test/test_context.py index 480121de0..0ecc659dc 100644 --- a/test/test_context.py +++ b/test/test_context.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -from .helper import check_evaluation, reset_session import pytest - from mathics_scanner.errors import IncompleteSyntaxError +from .helper import check_evaluation, reset_session str_test_context_1 = """ BeginPackage["FeynCalc`"]; diff --git a/test/test_custom_boxexpression.py b/test/test_custom_boxexpression.py index 6cd8edb0a..e32cba955 100644 --- a/test/test_custom_boxexpression.py +++ b/test/test_custom_boxexpression.py @@ -1,11 +1,11 @@ -from .helper import evaluate, session - from mathics.builtin.base import BoxExpression, Predefined from mathics.builtin.graphics import GRAPHICS_OPTIONS from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED, A_READ_PROTECTED from mathics.core.expression import Expression from mathics.core.symbols import Symbol +from .helper import evaluate, session + SymbolCustomGraphicsBox = Symbol("CustomGraphicsBox") diff --git a/test/test_deletecases.py b/test/test_deletecases.py index 0c028fd59..7275a575f 100644 --- a/test/test_deletecases.py +++ b/test/test_deletecases.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -from .helper import evaluate - import pytest +from .helper import evaluate + @pytest.mark.parametrize( "str_expr,str_expected", diff --git a/test/test_evaluation.py b/test/test_evaluation.py index 81899a436..96ffdae43 100644 --- a/test/test_evaluation.py +++ b/test/test_evaluation.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- -from .helper import evaluate, check_evaluation - import sys + import pytest +from .helper import check_evaluation, evaluate + @pytest.mark.parametrize( "str_expr,str_expected", diff --git a/test/test_evaluators.py b/test/test_evaluators.py index 06e4cf292..69fed3a7e 100644 --- a/test/test_evaluators.py +++ b/test/test_evaluators.py @@ -2,9 +2,9 @@ import pytest -from mathics.session import MathicsSession from mathics.eval.nevaluator import eval_N, eval_nvalues from mathics.eval.numerify import numerify as eval_numerify +from mathics.session import MathicsSession session = MathicsSession() evaluation = session.evaluation diff --git a/test/test_help.py b/test/test_help.py index 83b2cbfae..4d8fe28d1 100755 --- a/test/test_help.py +++ b/test/test_help.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -from .helper import check_evaluation from mathics.builtin.base import Builtin +from .helper import check_evaluation + class Builtin1(Builtin): summary_text = "short description" diff --git a/test/test_inout.py b/test/test_inout.py index 74df2d398..21b1fe45b 100755 --- a/test/test_inout.py +++ b/test/test_inout.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- -from .helper import check_evaluation import os import sys +from .helper import check_evaluation + # FIXME: see if we can refine this better such as # by running some Python code and looking for a failure. limited_characterset = sys.platform not in { diff --git a/test/test_main.py b/test/test_main.py index 4cfc8c6ca..2971b5a06 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- -import subprocess - import os.path as osp import re -import pytest +import subprocess import sys +import pytest + def get_testdir(): filename = osp.normcase(osp.dirname(osp.abspath(__file__))) diff --git a/test/test_numericq.py b/test/test_numericq.py index 91b6f101b..526f2f176 100644 --- a/test/test_numericq.py +++ b/test/test_numericq.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -from .helper import check_evaluation, session import pytest +from .helper import check_evaluation, session + @pytest.mark.parametrize( ( diff --git a/test/test_numpy_utils.py b/test/test_numpy_utils.py index 0fdae9950..d540d4894 100644 --- a/test/test_numpy_utils.py +++ b/test/test_numpy_utils.py @@ -5,23 +5,21 @@ import unittest from mathics.builtin.numpy_utils import ( - stack, - unstack, - concat, - vectorize, - conditional, - clip, + allclose, array, choose, -) -from mathics.builtin.numpy_utils import ( - minimum, - maximum, + clip, + concat, + conditional, dot_t, - mod, floor, + maximum, + minimum, + mod, sqrt, - allclose, + stack, + unstack, + vectorize, ) diff --git a/test/test_returncode.py b/test/test_returncode.py index 3620193d7..a1eb67dc8 100644 --- a/test/test_returncode.py +++ b/test/test_returncode.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -import subprocess - import os.path as osp +import subprocess def get_testdir(): diff --git a/test/test_series.py b/test/test_series.py index 1d1cf22c2..af5f67488 100644 --- a/test/test_series.py +++ b/test/test_series.py @@ -3,9 +3,10 @@ Unit tests from builtin ... calculus.py specific for Series """ -from .helper import check_evaluation import pytest +from .helper import check_evaluation + def test_seriesdata_product(): for str_expr, str_expected, message in ( diff --git a/test/test_settings.py b/test/test_settings.py index f42d97492..1621a4cd4 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- +from mathics.session import get_settings_value, load_default_settings_files + from .helper import session -from mathics.session import load_default_settings_files, get_settings_value def test_settings(): diff --git a/test/test_structure.py b/test/test_structure.py index 633b2bafe..17f752b74 100644 --- a/test/test_structure.py +++ b/test/test_structure.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -from .helper import check_evaluation, reset_session import pytest +from .helper import check_evaluation, reset_session + _initialized = False diff --git a/test/test_system_info.py b/test/test_system_info.py index 00b143603..8975626ac 100644 --- a/test/test_system_info.py +++ b/test/test_system_info.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -from .helper import session - from mathics.system_info import mathics_system_info +from .helper import session + def test_system_info(): info = mathics_system_info(session.definitions) From 7d48b2531d1591b44fd8168e5e4091c01685b032 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 11:22:25 -0500 Subject: [PATCH 079/121] add descriptive (wiki) name for Arg --- mathics/builtin/arithmetic.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 82bb512c2..71f7186a9 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -230,8 +230,12 @@ def create_infix(items, operator, prec, grouping): class Abs(_MPMathFunction): """ - - <url>:Absolute value: https://en.wikipedia.org/wiki/Absolute_value</url> (<url>:SymPy: https://docs.sympy.org/latest/modules/functions/elementary.html#sympy.functions.elementary.complexes.Abs</url>, <url>:WMA: https://reference.wolfram.com/language/ref/Abs</url>) + <url> + :Absolute value: + https://en.wikipedia.org/wiki/Absolute_value</url> (<url> + :SymPy: + https://docs.sympy.org/latest/modules/functions/elementary.html#sympy.functions.elementary.complexes.Abs</url>, <url> + :WMA: https://reference.wolfram.com/language/ref/Abs</url>) <dl> <dt>'Abs[$x$]' @@ -266,8 +270,10 @@ class Abs(_MPMathFunction): class Arg(_MPMathFunction): """ - <url>:Arg:https://en.wikipedia.org/wiki/Argument_(complex_analysis)</url> \ - (<url>:WMA link:https://reference.wolfram.com/language/ref/Arg.html</url>) + <url>:Argument (complex analysis): + https://en.wikipedia.org/wiki/Argument_(complex_analysis)</url> (<url> + :WMA link:https://reference.wolfram.com/language/ref/Arg.html</url>) + <dl> <dt>'Arg'[$z$, $method_option$] <dd>returns the argument of a complex value $z$. From 60a6cde729e3e6d6d68ff99d22f3ac1ba30748dd Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 12:56:45 -0500 Subject: [PATCH 080/121] Remove irrelevant isort config line --- .isort.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/.isort.cfg b/.isort.cfg index b865cda23..d64618e1a 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -4,7 +4,6 @@ include_trailing_comma = True force_grid_wrap = 0 use_parentheses = True line_length = 88 -known_crunch = cr, zz9d, zz9lib, pycrunch, silhouette sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,CRUNCH,LOCALFOLDER default_section = THIRDPARTY combine_as_imports = 1 From 1b9e53af7c84e688f2249ba5bb5df9c1b8e8c294 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 12:33:17 -0500 Subject: [PATCH 081/121] Add $PythonImplementation... shows whether we are running Pyston, PyPy, or CPython and what version of that we have running. --- CHANGES.rst | 1 + SYMBOLS_MANIFEST.txt | 1 + mathics/builtin/system.py | 53 ++++++++++++++++++++++++++++----------- mathics/system_info.py | 17 +++++++++++++ test/test_system_info.py | 1 + 5 files changed, 59 insertions(+), 14 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 8633b6887..25569cd2b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,7 @@ New Builtins #. ``$BoxForms`` #. ``$OutputForms`` #. ``$PrintForms`` +#. ``$PythonImplementation`` #. ``Accuracy`` #. ``ClebschGordan`` #. ``Curl`` (2-D and 3-D vector forms only) diff --git a/SYMBOLS_MANIFEST.txt b/SYMBOLS_MANIFEST.txt index 1e2e49d9d..0d25a0258 100644 --- a/SYMBOLS_MANIFEST.txt +++ b/SYMBOLS_MANIFEST.txt @@ -56,6 +56,7 @@ System`$PreRead System`$PrintForms System`$ProcessID System`$ProcessorType +System`$PythonImplementation System`$RandomState System`$RecursionLimit System`$RootDirectory diff --git a/mathics/builtin/system.py b/mathics/builtin/system.py index f58ff9099..236342b20 100644 --- a/mathics/builtin/system.py +++ b/mathics/builtin/system.py @@ -75,7 +75,7 @@ class Environment(Builtin): summary_text = "list the system environment variables" - def apply(self, var, evaluation): + def eval(self, var, evaluation): "Environment[var_String]" env_var = var.get_string_value() if env_var not in os.environ: @@ -116,7 +116,7 @@ class GetEnvironment(Builtin): summary_text = "retrieve the value of a system environment variable" - def apply(self, var, evaluation): + def eval(self, var, evaluation): "GetEnvironment[var___]" if isinstance(var, String): env_var = var.get_string_value() @@ -262,24 +262,49 @@ def evaluate(self, evaluation) -> Integer: class ProcessorType(Predefined): r""" - <url>:WMA link:https://reference.wolfram.com/language/ref/ProcessorType.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ProcessorType.html</url> <dl> - <dt>'$ProcessorType' - <dd>gives a string giving the architecture of the processor on which the \Mathics is being run. + <dt>'$ProcessorType' + <dd>gives a string giving the architecture of the processor on which the \Mathics is being run. </dl> - X> $ProcessorType - = x86_64 + + >> $ProcessorType + = ... """ + name = "$ProcessorType" + summary_text = ( "name of the architecture of the processor over which Mathics is running" ) - name = "$ProcessorType" def evaluate(self, evaluation): return String(platform.machine()) +class PythonImplementation(Predefined): + r""" + ## <url>:PythonImplementation native symbol:</url> + + <dl> + <dt>'$PythonImplementation' + <dd>gives a string indication the Python implementation used to run \Mathics. + </dl> + >> $PythonImplementation + = ... + """ + name = "$PythonImplementation" + + summary_text = "name of the Python implementation running Mathics" + + def evaluate(self, evaluation): + from mathics.system_info import python_implementation + + return String(python_implementation()) + + class ScriptCommandLine(Predefined): """ <url>:WMA link:https://reference.wolfram.com/language/ref/ScriptCommandLine.html</url> @@ -320,7 +345,7 @@ class Run(Builtin): summary_text = "run a system command" - def apply(self, command, evaluation): + def eval(self, command, evaluation): "Run[command_String]" command_str = command.to_python() return Integer(subprocess.call(command_str, shell=True)) @@ -480,7 +505,7 @@ class MemoryAvailable(Builtin): summary_text = "the available amount of physical memory in the system" - def apply(self, evaluation) -> Integer: + def eval(self, evaluation) -> Integer: """MemoryAvailable[]""" totalmem = psutil.virtual_memory().available return Integer(totalmem) @@ -523,7 +548,7 @@ class MemoryAvailable(Builtin): summary_text = "the available amount of physical memory in the system" - def apply(self, evaluation) -> Integer: + def eval(self, evaluation) -> Integer: """MemoryAvailable[]""" return Integer(-1) @@ -543,7 +568,7 @@ class MemoryInUse(Builtin): summary_text = "number of bytes of memory currently being used by Mathics" - def apply_0(self, evaluation) -> Integer: + def eval_0(self, evaluation) -> Integer: """MemoryInUse[]""" # Partially borrowed from https://code.activestate.com/recipes/577504/ from itertools import chain @@ -596,7 +621,7 @@ class Share(Builtin): summary_text = "force Python garbage collection" - def apply(self, evaluation) -> Integer: + def eval(self, evaluation) -> Integer: """Share[]""" # TODO: implement a routine that swap all the definitions, # collecting repeated symbols and expressions, and then @@ -610,7 +635,7 @@ def apply(self, evaluation) -> Integer: gc.collect() return Integer0 - def apply_with_symbol(self, symbol, evaluation) -> Integer: + def eval_with_symbol(self, symbol, evaluation) -> Integer: """Share[symbol_Symbol]""" # TODO: implement a routine that swap all the definitions, # collecting repeated symbols and expressions, and then diff --git a/mathics/system_info.py b/mathics/system_info.py index b2f145365..008718dd0 100644 --- a/mathics/system_info.py +++ b/mathics/system_info.py @@ -11,6 +11,22 @@ from mathics.core.evaluation import Evaluation +def python_implementation() -> str: + """ + Returns the Python implemnetation, e.g Pyston, PyPy, CPython... + """ + if hasattr(sys, "pyston_version_info"): + custom_version_info = sys.pyston_version_info + python_implementation = "Pyston" + elif hasattr(sys, "pypy_version_info"): + custom_version_info = sys.pypy_version_info + python_implementation = "PyPy" + else: + custom_version_info = sys.version_info + python_implementation = platform.python_implementation() + return f"{python_implementation} {'.'.join((str(i) for i in custom_version_info))}" + + def mathics_system_info(defs): def eval(name, needs_head=True): evaled = name().evaluate(evaluation) @@ -28,6 +44,7 @@ def eval(name, needs_head=True): "$MachineName": platform.uname().node, "$ProcessID": os.getppid(), "$ProcessorType": platform.machine(), + "$PythonImplementation": python_implementation(), "$RootDirectory": eval(filesystem.RootDirectory), "$SystemID": sys.platform, "$SystemMemory": eval(msystem.SystemMemory), diff --git a/test/test_system_info.py b/test/test_system_info.py index 8975626ac..a838cc142 100644 --- a/test/test_system_info.py +++ b/test/test_system_info.py @@ -20,6 +20,7 @@ def test_system_info(): "$MachineName", "$ProcessID", "$ProcessorType", + "$PythonImplementation", "$RootDirectory", "$SystemID", "$SystemMemory", From ee782cb9692a85c24118865708eb44740d82d350 Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Sat, 24 Dec 2022 14:12:59 -0300 Subject: [PATCH 082/121] e1 rebase --- SYMBOLS_MANIFEST.txt | 3 + mathics/builtin/arithfns/basic.py | 3 +- mathics/builtin/arithmetic.py | 13 +- .../builtin/assignments/assign_binaryop.py | 7 +- mathics/builtin/assignments/assignment.py | 160 ++++++++++++++---- mathics/builtin/assignments/types.py | 10 +- mathics/builtin/assignments/upvalues.py | 90 ---------- mathics/builtin/atomic/numbers.py | 3 +- mathics/builtin/binary/io.py | 1 + mathics/builtin/binary/system.py | 1 - mathics/builtin/datentime.py | 2 +- mathics/builtin/list/eol.py | 9 +- mathics/builtin/numbers/algebra.py | 6 +- mathics/builtin/numbers/calculus.py | 1 - mathics/system_info.py | 7 + 15 files changed, 169 insertions(+), 147 deletions(-) diff --git a/SYMBOLS_MANIFEST.txt b/SYMBOLS_MANIFEST.txt index 1e2e49d9d..36583f178 100644 --- a/SYMBOLS_MANIFEST.txt +++ b/SYMBOLS_MANIFEST.txt @@ -311,6 +311,7 @@ System`DirectoryName System`DirectoryQ System`DirectoryStack System`DiscreteLimit +System`DiscretePlot System`DisjointQ System`Disk System`DiskBox @@ -592,6 +593,7 @@ System`LinearModelFit System`LinearSolve System`List System`ListLinePlot +System`ListLogPlot System`ListPlot System`ListQ System`Listable @@ -601,6 +603,7 @@ System`Log System`Log10 System`Log2 System`LogGamma +System`LogPlot System`LogisticSigmoid System`Longest System`Lookup diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index df11da85b..79bfb4907 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -675,7 +675,8 @@ class Sqrt(SympyFunction): class Subtract(BinaryOperator): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Subtract.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/Subtract.html</url> <dl> <dt>'Subtract[$a$, $b$]' diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index ecde784e2..91d114855 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -579,8 +579,9 @@ def to_sympy(self, expr, **kwargs): class Conjugate(_MPMathFunction): """ - <url>:Complex Conjugate:https://en.wikipedia.org/wiki/Complex_conjugate</url> \ - (<url>:WMA link:https://reference.wolfram.com/language/ref/Conjugate.html</url>) + <url>:Complex Conjugate: + https://en.wikipedia.org/wiki/Complex_conjugate</url> \ + (<url>:WMA:https://reference.wolfram.com/language/ref/Conjugate.html</url>) <dl> <dt>'Conjugate[$z$]' @@ -616,7 +617,8 @@ class Conjugate(_MPMathFunction): class DirectedInfinity(SympyFunction): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/DirectedInfinity.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/DirectedInfinity.html</url> <dl> <dt>'DirectedInfinity[$z$]' @@ -710,7 +712,7 @@ def to_sympy(self, expr, **kwargs): class I(Predefined): """ <url>:Imaginary unit:https://en.wikipedia.org/wiki/Imaginary_unit</url> \ - (<url>:WMA link:https://reference.wolfram.com/language/ref/I.html</url>) + (<url>:WMA:https://reference.wolfram.com/language/ref/I.html</url>) <dl> <dt>'I' @@ -1211,7 +1213,8 @@ class Real_(Builtin): class RealNumberQ(Test): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/RealNumberQ.html</url> + ## Not found in WMA + ## <url>:WMA link:https://reference.wolfram.com/language/ref/RealNumberQ.html</url> <dl> <dt>'RealNumberQ[$expr$]' diff --git a/mathics/builtin/assignments/assign_binaryop.py b/mathics/builtin/assignments/assign_binaryop.py index 32192bd4c..347c8bd3b 100644 --- a/mathics/builtin/assignments/assign_binaryop.py +++ b/mathics/builtin/assignments/assign_binaryop.py @@ -50,7 +50,8 @@ class AddTo(BinaryOperator): class Decrement(PostfixOperator): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Decrement.html</url> + <url>:WMA link + :https://reference.wolfram.com/language/ref/Decrement.html</url> <dl> <dt>'Decrement[$x$]' @@ -109,7 +110,8 @@ class DivideBy(BinaryOperator): class Increment(PostfixOperator): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Increment.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/Increment.html</url> <dl> <dt>'Increment[$x$]' @@ -148,6 +150,7 @@ class Increment(PostfixOperator): class PreIncrement(PrefixOperator): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/PreIncrement.html</url> diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py index 88149fcaf..384d96c59 100644 --- a/mathics/builtin/assignments/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -56,6 +56,43 @@ def assign(self, lhs, rhs, evaluation, tags=None, upset=False): return False +# Placing this here is a bit weird, but it is not clear where else is better suited for this right now. +class LoadModule(Builtin): + """ + ## <url>:mathics native for pymathics:</url> + + <dl> + <dt>'LoadModule[$module$]' + <dd>'Load Mathics definitions from the python module $module$ + </dl> + >> LoadModule["nomodule"] + : Python module nomodule does not exist. + = $Failed + >> LoadModule["sys"] + : Python module sys is not a pymathics module. + = $Failed + """ + + name = "LoadModule" + messages = { + "notfound": "Python module `1` does not exist.", + "notmathicslib": "Python module `1` is not a pymathics module.", + } + summary_text = "load a pymathics module" + + def eval(self, module, evaluation): + "LoadModule[module_String]" + try: + eval_load_module(module.value, evaluation) + except PyMathicsLoadException: + evaluation.message(self.name, "notmathicslib", module) + return SymbolFailed + except ImportError: + evaluation.message(self.get_name(), "notfound", module) + return SymbolFailed + return module + + class Set(BinaryOperator, _SetOperator): """ <url>:WMA link:https://reference.wolfram.com/language/ref/Set.html</url> @@ -137,7 +174,7 @@ class Set(BinaryOperator, _SetOperator): summary_text = "assign a value" - def apply(self, lhs, rhs, evaluation): + def eval(self, lhs, rhs, evaluation): "lhs_ = rhs_" self.assign(lhs, rhs, evaluation) @@ -146,7 +183,8 @@ def apply(self, lhs, rhs, evaluation): class SetDelayed(Set): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/SetDelayed.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/SetDelayed.html</url> <dl> <dt>'SetDelayed[$expr$, $value$]' @@ -215,7 +253,7 @@ class SetDelayed(Set): summary_text = "test a delayed value; used in defining functions" - def apply(self, lhs, rhs, evaluation): + def eval(self, lhs, rhs, evaluation): "lhs_ := rhs_" if self.assign(lhs, rhs, evaluation): @@ -232,7 +270,8 @@ class TagSet(Builtin, _SetOperator): <dt>'TagSet[$f$, $expr$, $value$]' <dt>'$f$ /: $expr$ = $value$' - <dd>assigns $value$ to $expr$, associating the corresponding assignment with the symbol $f$. + <dd>assigns $value$ to $expr$, associating the corresponding assignment \ + with the symbol $f$. </dl> Create an upvalue without using 'UpSet': @@ -260,7 +299,7 @@ class TagSet(Builtin, _SetOperator): } summary_text = "assign a value to an expression, associating the corresponding assignment with the a symbol" - def apply(self, f, lhs, rhs, evaluation): + def eval(self, f, lhs, rhs, evaluation): "f_ /: lhs_ = rhs_" name = f.get_name() @@ -275,7 +314,8 @@ def apply(self, f, lhs, rhs, evaluation): class TagSetDelayed(TagSet): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/TagSetDelayed.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/TagSetDelayed.html</url> <dl> <dt>'TagSetDelayed[$f$, $expr$, $value$]' @@ -288,7 +328,7 @@ class TagSetDelayed(TagSet): attributes = A_HOLD_ALL | A_PROTECTED | A_SEQUENCE_HOLD summary_text = "assign a delayed value to an expression, associating the corresponding assignment with the a symbol" - def apply(self, f, lhs, rhs, evaluation): + def eval(self, f, lhs, rhs, evaluation): "f_ /: lhs_ := rhs_" name = f.get_name() @@ -302,38 +342,92 @@ def apply(self, f, lhs, rhs, evaluation): return SymbolFailed -# Placing this here is a bit weird, but it is not clear where else is better suited for this right now. -class LoadModule(Builtin): +class UpSet(BinaryOperator, _SetOperator): """ - ## <url>:mathics native for pymathics:</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/UpSet.html</url> <dl> - <dt>'LoadModule[$module$]' - <dd>'Load Mathics definitions from the python module $module$ + <dt>$f$[$x$] ^= $expression$ + <dd>evaluates $expression$ and assigns it to the value of $f$[$x$], associating the value with $x$. </dl> - >> LoadModule["nomodule"] - : Python module nomodule does not exist. - = $Failed - >> LoadModule["sys"] - : Python module sys is not a pymathics module. + + '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 = A_HOLD_FIRST | A_PROTECTED | A_SEQUENCE_HOLD + grouping = "Right" + operator = "^=" + precedence = 40 + + summary_text = ( + "set value and associate the assignment with symbols that occur at level one" + ) + + def eval(self, lhs, rhs, evaluation): + "lhs_ ^= rhs_" + + self.assign(lhs, rhs, evaluation, upset=True) + return rhs + + +class UpSetDelayed(UpSet): + """ + <url>:WMA link: + https://reference.wolfram.com/language/ref/UpSetDelayed.html</url> + + <dl> + <dt>'UpSetDelayed[$expression$, $value$]' + + <dt>'$expression$ ^:= $value$' + <dd>assigns $expression$ to the value of $f$[$x$] (without evaluating $expression$), associating the value with $x$. + </dl> + + >> 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 """ - name = "LoadModule" - messages = { - "notfound": "Python module `1` does not exist.", - "notmathicslib": "Python module `1` is not a pymathics module.", - } - summary_text = "load a pymathics module" + attributes = A_HOLD_ALL | A_PROTECTED | A_SEQUENCE_HOLD + operator = "^:=" + summary_text = "set a delayed value and associate the assignment with symbols that occur at level one" - def apply(self, module, evaluation): - "LoadModule[module_String]" - try: - eval_load_module(module.value, evaluation) - except PyMathicsLoadException: - evaluation.message(self.name, "notmathicslib", module) - return SymbolFailed - except ImportError: - evaluation.message(self.get_name(), "notfound", module) + def eval(self, lhs, rhs, evaluation): + "lhs_ ^:= rhs_" + + if self.assign(lhs, rhs, evaluation, upset=True): + return SymbolNull + else: return SymbolFailed - return module diff --git a/mathics/builtin/assignments/types.py b/mathics/builtin/assignments/types.py index 17c946a69..e99a42217 100644 --- a/mathics/builtin/assignments/types.py +++ b/mathics/builtin/assignments/types.py @@ -50,7 +50,7 @@ class DefaultValues(Builtin): "give default values for the arguments associated with a function symbol" ) - def apply(self, symbol, evaluation): + def eval(self, symbol, evaluation): "DefaultValues[symbol_]" return get_symbol_values(symbol, "System`DefaultValues", "default", evaluation) @@ -80,7 +80,7 @@ class Messages(Builtin): attributes = A_HOLD_ALL | A_PROTECTED summary_text = "give the list the messages associated with a particular symbol" - def apply(self, symbol, evaluation): + def eval(self, symbol, evaluation): "Messages[symbol_]" return get_symbol_values(symbol, "Messages", "messages", evaluation) @@ -88,6 +88,7 @@ def apply(self, symbol, evaluation): class NValues(Builtin): """ + ## No longer in WMA ## <url>:WMA link:https://reference.wolfram.com/language/ref/NValues.html</url> <dl> @@ -129,7 +130,7 @@ class NValues(Builtin): attributes = A_HOLD_ALL | A_PROTECTED summary_text = "give the list of numerical values associated with a symbol" - def apply(self, symbol, evaluation): + def eval(self, symbol, evaluation): "NValues[symbol_]" return get_symbol_values(symbol, "NValues", "n", evaluation) @@ -137,6 +138,7 @@ def apply(self, symbol, evaluation): class SubValues(Builtin): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/SubValues.html</url> @@ -160,7 +162,7 @@ class SubValues(Builtin): attributes = A_HOLD_ALL | A_PROTECTED summary_text = "give the list of subvalues associated with a symbol" - def apply(self, symbol, evaluation): + def eval(self, symbol, evaluation): "SubValues[symbol_]" return get_symbol_values(symbol, "SubValues", "sub", evaluation) diff --git a/mathics/builtin/assignments/upvalues.py b/mathics/builtin/assignments/upvalues.py index 596947a5e..ebbed3903 100644 --- a/mathics/builtin/assignments/upvalues.py +++ b/mathics/builtin/assignments/upvalues.py @@ -22,96 +22,6 @@ from mathics.core.systemsymbols import SymbolFailed -class UpSet(BinaryOperator, _SetOperator): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/UpSet.html</url> - - <dl> - <dt>$f$[$x$] ^= $expression$ - <dd>evaluates $expression$ and assigns it to the value of $f$[$x$], \ - associating the value with $x$. - </dl> - - '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 = A_HOLD_FIRST | A_PROTECTED | A_SEQUENCE_HOLD - 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(lhs, rhs, evaluation, upset=True) - return rhs - - -class UpSetDelayed(UpSet): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/UpSetDelayed.html</url> - - <dl> - <dt>'UpSetDelayed[$expression$, $value$]' - - <dt>'$expression$ ^:= $value$' - <dd>assigns $expression$ to the value of $f$[$x$] (without evaluating $expression$), associating the value with $x$. - </dl> - - >> 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 = A_HOLD_ALL | A_PROTECTED | A_SEQUENCE_HOLD - operator = "^:=" - summary_text = "set a delayed value and associate the assignment with symbols that occur at level one" - - def eval(self, lhs, rhs, evaluation): - "lhs_ ^:= rhs_" - - if self.assign(lhs, rhs, evaluation, upset=True): - return SymbolNull - else: - return SymbolFailed - - # In Mathematica 5, this appears under "Types of Values". class UpValues(Builtin): """ diff --git a/mathics/builtin/atomic/numbers.py b/mathics/builtin/atomic/numbers.py index 619c1e00b..99d8a0b69 100644 --- a/mathics/builtin/atomic/numbers.py +++ b/mathics/builtin/atomic/numbers.py @@ -823,6 +823,7 @@ class MaxPrecision(Predefined): class MachineEpsilon_(Predefined): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/$MachineEpsilon.html</url> @@ -960,6 +961,7 @@ class MinPrecision(Builtin): class NumericQ(Builtin): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/NumericQ.html</url> @@ -1011,7 +1013,6 @@ def apply(self, expr, evaluation): class Precision(Builtin): """ - <url> :Precision: https://en.wikipedia.org/wiki/Accuracy_and_precision</url> (<url> diff --git a/mathics/builtin/binary/io.py b/mathics/builtin/binary/io.py index 9dcd5fb41..077ad3ccc 100644 --- a/mathics/builtin/binary/io.py +++ b/mathics/builtin/binary/io.py @@ -31,6 +31,7 @@ class _BinaryFormat: """ + Container for BinaryRead readers and BinaryWrite writers """ diff --git a/mathics/builtin/binary/system.py b/mathics/builtin/binary/system.py index 4160bd7d8..62f8f1308 100644 --- a/mathics/builtin/binary/system.py +++ b/mathics/builtin/binary/system.py @@ -14,7 +14,6 @@ class ByteOrdering(Predefined): <url> :WMA link: https://reference.wolfram.com/language/ref/ByteOrdering.html</url> - <dl> <dt>'ByteOrdering' <dd> is an option for BinaryRead, BinaryWrite, and related functions \ diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index b21303307..8f550d4a2 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -1195,7 +1195,7 @@ def eval_3(self, expr, t, failexpr, evaluation): class TimeZone(Predefined): """ <url>:Time Zone:https://en.wikipedia.org/wiki/Time_zone</url> (<url> - :WMA link: + :WMA: https://reference.wolfram.com/language/ref/$TimeZone.html</url>) <dl> diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index 6407dd4de..77f7fc635 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -473,7 +473,8 @@ class Extract(Builtin): class First(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/First.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/First.html</url> <dl> <dt>'First[$expr$]' @@ -515,7 +516,8 @@ def eval(self, expr, evaluation): class FirstCase(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/FirstCase.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/FirstCase.html</url> <dl> <dt> FirstCase[{$e1$, $e2$, ...}, $pattern$] @@ -547,7 +549,8 @@ class FirstCase(Builtin): class FirstPosition(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/FirstPosition.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/FirstPosition.html</url> <dl> <dt>'FirstPosition[$expr$, $pattern$]' diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index a539f7d8c..593a4a14f 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -1089,7 +1089,6 @@ def eval_var_filter(self, expr, varlist, filt, evaluation): class Denominator(Builtin): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/Denominator.html</url> @@ -1313,6 +1312,7 @@ def eval(self, expr, evaluation, options): class ExpandDenominator(_Expand): """ + <url>:WMA link: https://reference.wolfram.com/language/ref/ExpandDenominator.html</url> @@ -1579,7 +1579,6 @@ def eval_list(self, expr, vars, evaluation): # FullSimplify class Simplify(Builtin): r""" - <url>:WMA link: https://reference.wolfram.com/language/ref/Simplify.html</url> @@ -1778,7 +1777,6 @@ class FullSimplify(Simplify): class MinimalPolynomial(Builtin): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/MinimalPolynomial.html</url> @@ -1870,7 +1868,6 @@ def eval(self, expr, evaluation): class PolynomialQ(Builtin): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/PolynomialQ.html</url> @@ -1995,7 +1992,6 @@ class PowerExpand(Builtin): class Together(Builtin): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/Together.html</url> diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index f46a71489..3f537d1c3 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -368,7 +368,6 @@ def apply_wrong(self, expr, x, other, evaluation): class Derivative(PostfixOperator, SympyFunction): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/Derivative.html</url> diff --git a/mathics/system_info.py b/mathics/system_info.py index b2f145365..31b3e136d 100644 --- a/mathics/system_info.py +++ b/mathics/system_info.py @@ -28,6 +28,13 @@ def eval(name, needs_head=True): "$MachineName": platform.uname().node, "$ProcessID": os.getppid(), "$ProcessorType": platform.machine(), + "$SystemID": sys.platform, + "$UserName": eval(msystem.UserName), + "$SystemMemory": eval(msystem.SystemMemory), + "MemoryAvailable[]": eval(msystem.MemoryAvailable, needs_head=False), + "$SystemTimeZone": eval(datentime.SystemTimeZone), + "MachinePrecision": eval(numeric.MachinePrecision_), + "$BaseDirectory": eval(filesystem.BaseDirectory_), "$RootDirectory": eval(filesystem.RootDirectory), "$SystemID": sys.platform, "$SystemMemory": eval(msystem.SystemMemory), From 7e09275969b39fd082d26a7ceaf6f0b1a8cc670c Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Sat, 24 Dec 2022 14:21:12 -0300 Subject: [PATCH 083/121] revert system_info.py --- mathics/system_info.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/mathics/system_info.py b/mathics/system_info.py index 31b3e136d..b2f145365 100644 --- a/mathics/system_info.py +++ b/mathics/system_info.py @@ -28,13 +28,6 @@ def eval(name, needs_head=True): "$MachineName": platform.uname().node, "$ProcessID": os.getppid(), "$ProcessorType": platform.machine(), - "$SystemID": sys.platform, - "$UserName": eval(msystem.UserName), - "$SystemMemory": eval(msystem.SystemMemory), - "MemoryAvailable[]": eval(msystem.MemoryAvailable, needs_head=False), - "$SystemTimeZone": eval(datentime.SystemTimeZone), - "MachinePrecision": eval(numeric.MachinePrecision_), - "$BaseDirectory": eval(filesystem.BaseDirectory_), "$RootDirectory": eval(filesystem.RootDirectory), "$SystemID": sys.platform, "$SystemMemory": eval(msystem.SystemMemory), From ac35a957be891791bc1a35c3b902ff04bc42eea6 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 13:06:32 -0500 Subject: [PATCH 084/121] Remove blank lines at the top of docstrings --- mathics/builtin/assignments/assign_binaryop.py | 1 - mathics/builtin/assignments/types.py | 1 - mathics/builtin/atomic/numbers.py | 2 -- mathics/builtin/binary/io.py | 1 - mathics/builtin/numbers/algebra.py | 1 - 5 files changed, 6 deletions(-) diff --git a/mathics/builtin/assignments/assign_binaryop.py b/mathics/builtin/assignments/assign_binaryop.py index 347c8bd3b..d204bb456 100644 --- a/mathics/builtin/assignments/assign_binaryop.py +++ b/mathics/builtin/assignments/assign_binaryop.py @@ -150,7 +150,6 @@ class Increment(PostfixOperator): class PreIncrement(PrefixOperator): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/PreIncrement.html</url> diff --git a/mathics/builtin/assignments/types.py b/mathics/builtin/assignments/types.py index e99a42217..b0188efa7 100644 --- a/mathics/builtin/assignments/types.py +++ b/mathics/builtin/assignments/types.py @@ -138,7 +138,6 @@ def eval(self, symbol, evaluation): class SubValues(Builtin): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/SubValues.html</url> diff --git a/mathics/builtin/atomic/numbers.py b/mathics/builtin/atomic/numbers.py index 99d8a0b69..efd8ed553 100644 --- a/mathics/builtin/atomic/numbers.py +++ b/mathics/builtin/atomic/numbers.py @@ -823,7 +823,6 @@ class MaxPrecision(Predefined): class MachineEpsilon_(Predefined): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/$MachineEpsilon.html</url> @@ -961,7 +960,6 @@ class MinPrecision(Builtin): class NumericQ(Builtin): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/NumericQ.html</url> diff --git a/mathics/builtin/binary/io.py b/mathics/builtin/binary/io.py index 077ad3ccc..9dcd5fb41 100644 --- a/mathics/builtin/binary/io.py +++ b/mathics/builtin/binary/io.py @@ -31,7 +31,6 @@ class _BinaryFormat: """ - Container for BinaryRead readers and BinaryWrite writers """ diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py index 593a4a14f..c7067396b 100644 --- a/mathics/builtin/numbers/algebra.py +++ b/mathics/builtin/numbers/algebra.py @@ -1312,7 +1312,6 @@ def eval(self, expr, evaluation, options): class ExpandDenominator(_Expand): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/ExpandDenominator.html</url> From 4b356111d64c6fe99f2bbb4d9bd9cca758f7481a Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 23:55:15 -0500 Subject: [PATCH 085/121] Correct isort config --- .isort.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.isort.cfg b/.isort.cfg index d64618e1a..bd69b54cd 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -4,7 +4,7 @@ include_trailing_comma = True force_grid_wrap = 0 use_parentheses = True line_length = 88 -sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,CRUNCH,LOCALFOLDER +sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER default_section = THIRDPARTY combine_as_imports = 1 profile = black From c031eb7e487f789dddccb8c41ae45a2805d6f525 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sun, 25 Dec 2022 09:05:43 -0500 Subject: [PATCH 086/121] Update Packaging versions accepted ... and also report versons on optional packages --- mathics/__init__.py | 38 ++++++++++++++++++++++++++++++-------- mathics/doc/common_doc.py | 5 +++++ mathics/main.py | 2 +- setup.py | 4 ++-- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/mathics/__init__.py b/mathics/__init__.py index d7f66d8bc..bdacad16d 100644 --- a/mathics/__init__.py +++ b/mathics/__init__.py @@ -2,6 +2,8 @@ import platform import sys +from importlib import import_module +from typing import Dict import mpmath import numpy @@ -9,21 +11,41 @@ from mathics.version import __version__ -version_info = { +# version_info contains a list of Python packages +# and the versions infsalled or "Not installed" +# if the package is not installed and "No version information" +# if we can't get version infomation. +version_info: Dict[str, str] = { "mathics": __version__, - "sympy": sympy.__version__, "mpmath": mpmath.__version__, "numpy": numpy.__version__, "python": platform.python_implementation() + " " + sys.version.split("\n")[0], + "sympy": sympy.__version__, + "sympy": sympy.__version__, } -try: - import cython -except ImportError: - pass -else: - version_info["cython"] = cython.__version__ +# optional_software contains a list of Python packages +# that add functionality but are optional +optional_software: Dict[str, str] = ( + "cython", + "lxml", + "networkx", + "nltk", + "psutil", + "scikit-image", + "scipy", + "wordcloud", +) + +for package in optional_software: + try: + mod = import_module(package) + package_version = mod.__dict__.get("__version__", "No version information") + except ImportError: + package_version = "Not installed" + + version_info[package] = package_version version_string = """Mathics {mathics} on {python} diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py index 907b32e1e..233060263 100644 --- a/mathics/doc/common_doc.py +++ b/mathics/doc/common_doc.py @@ -632,6 +632,11 @@ def post_sub(text, post_substitutions): return text +def skip_doc(cls): + """Returns True if we should skip cls in docstring extraction.""" + return cls.__name__.endswith("Box") or (hasattr(cls, "no_doc") and cls.no_doc) + + class Tests: # FIXME: add optional guide section def __init__(self, part: str, chapter: str, section: str, doctests): diff --git a/mathics/main.py b/mathics/main.py index a55e49d83..b0d1c6df1 100755 --- a/mathics/main.py +++ b/mathics/main.py @@ -345,7 +345,7 @@ def main() -> int: ) args, script_args = argparser.parse_known_args() - quit_command = "CTRL-BREAK" if sys.platform == "win32" else "CONTROL-D" + quit_command = "CTRL-BREAK" if sys.platform in ("win32", "nt") else "CONTROL-D" extension_modules = [] if args.pyextensions: diff --git a/setup.py b/setup.py index 56bc0b2e8..379e845b3 100644 --- a/setup.py +++ b/setup.py @@ -49,13 +49,13 @@ "recordclass", "numpy", "llvmlite<0.37", - "sympy>=1.8,<1.9", + "sympy>=1.8,<1.12", ] if is_PyPy: print("Mathics does not support PyPy Python 3.6" % sys.version_info[:2]) sys.exit(-1) else: - INSTALL_REQUIRES += ["numpy<=1.24", "llvmlite", "sympy>=1.8, < 1.11"] + INSTALL_REQUIRES += ["numpy<=1.24", "llvmlite", "sympy>=1.8, < 1.12"] if not is_PyPy: INSTALL_REQUIRES += ["recordclass"] From a12c21c24ad9f5ab986a777e0e1c1ec644065cb4 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sun, 25 Dec 2022 09:22:05 -0500 Subject: [PATCH 087/121] Update CHANGES .. * Allow Sympy 1.12 * Provide list of optional software for each list its version. * tweak Windows and PyPy detection --- CHANGES.rst | 27 ++++++++++++++++++++++----- mathics/__init__.py | 1 - mathics/system_info.py | 2 +- setup.py | 4 +++- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 25569cd2b..293c74eae 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,20 @@ CHANGES 5.0.3dev0 --------- +API ++++ + +#. New function ``mathics.system_info.python_implementation()`` shows the Python Implementation, e.g. CPython, PyPy, Pyston that is running Python. This is included in the information ``mathics.system_info.mathics_system__system_info()`` returns and is used in ``$PythonImplementation`` +#. A list of optional software can be found in ``mathics.optional_software``. Versions of that software are included in ``mathics.version_info``. + + +Package update +.............. + +#. SymPy 1.11.1 accepted +#. Numpy 1.24.0 accepted + + New Builtins +++++++++++ @@ -35,18 +49,21 @@ Documentation #. "Exponential Functional" split out from "Trigonometry Functions" #. A new section on "Accuracy and Precision" was included in the manual. #. "Forms of Input and Output" is its own section +#. All Builtins have links to WMA pages. +#. More url links to Wiki pages added; more internal cross links added. Internals +++++++++ #. ``boxes_to_`` methods are now optional for ``BoxElement`` subclasses. Most of the code is now moved to the ``mathics.format`` submodule, and implemented in a more scalable way. #. ``mathics.builtin.inout`` was splitted in several modules (``inout``, ``messages``, ``layout``, ``makeboxes``) in order to improve the documentation. -#. `from_mpmath` conversion supports a new parameter ``acc`` to set the accuracy of the number. +#. ``from_mpmath`` conversion supports a new parameter ``acc`` to set the accuracy of the number. #. Operator name to unicode or ASCII comes from Mathics scanner character tables. -#. ``eval*`` methods in `Builtin` classes are considerer as synonyms of ``apply*`` methods. -#. Modularize and improve the way in which `Builtin` classes are selected to have an associated `Definition`. -#. `_SetOperator.assign_elementary` was renamed as `_SetOperator.assign`. All the special cases are not handled by the `_SetOperator.special_cases` dict. - +#. Builtin instance methods that start ``apply`` are considered rule matching and function application; the use of the name ``apply``is deprecated, when ``eval`` is intended. +#. Modularize and improve the way in which ``Builtin`` classes are selected to have an associated ``Definition``. +#. ``_SetOperator.assign_elementary`` was renamed as ``_SetOperator.assign``. All the special cases are not handled by the ``_SetOperator.special_cases`` dict. +#. ``isort`` run over all Python files. More type annotations and docstrings on functions added. +#. caching on immutable atoms like, ``String``, ``Integer``, ``Real``, etc. was improved; the ``__hash__()`` function was sped up. There is a small speedup overall from this at the expense of increased memory. Bugs diff --git a/mathics/__init__.py b/mathics/__init__.py index bdacad16d..ace3cf6ef 100644 --- a/mathics/__init__.py +++ b/mathics/__init__.py @@ -21,7 +21,6 @@ "numpy": numpy.__version__, "python": platform.python_implementation() + " " + sys.version.split("\n")[0], "sympy": sympy.__version__, - "sympy": sympy.__version__, } diff --git a/mathics/system_info.py b/mathics/system_info.py index 008718dd0..2a621435d 100644 --- a/mathics/system_info.py +++ b/mathics/system_info.py @@ -13,7 +13,7 @@ def python_implementation() -> str: """ - Returns the Python implemnetation, e.g Pyston, PyPy, CPython... + Returns the Python implementation, e.g Pyston, PyPy, CPython... """ if hasattr(sys, "pyston_version_info"): custom_version_info = sys.pyston_version_info diff --git a/setup.py b/setup.py index 379e845b3..5e963c25c 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,9 @@ from setuptools import Extension, setup -is_PyPy = platform.python_implementation() == "PyPy" +is_PyPy = platform.python_implementation() == "PyPy" or hasattr( + sys, "pypy_version_info" +) INSTALL_REQUIRES = ["Mathics-Scanner >= 1.3.0.dev0"] From b141e26332c3fc7047a579b8abf0254e1353c4b3 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 18:47:34 -0500 Subject: [PATCH 088/121] Start factoring image module --- mathics/builtin/drawing/image.py | 103 +++++++++++++---------- mathics/builtin/files_io/importexport.py | 37 ++++---- 2 files changed, 80 insertions(+), 60 deletions(-) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 43be18a5e..b2efe7f59 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -16,6 +16,7 @@ import math import os.path as osp from collections import defaultdict +from operator import itemgetter from typing import Optional, Tuple from mathics.builtin.base import AtomBuiltin, Builtin, String, Test @@ -102,50 +103,62 @@ class _SkimageBuiltin(_ImageBuiltin): # Code related to Mathics Functions that import and export. -class _Exif: - _names = { # names overriding the ones given by Pillow - 37385: "FlashInfo", - 40960: "FlashpixVersion", - 40962: "PixelXDimension", - 40963: "PixelYDimension", - } +# Exif: Exchangeable image file format for digital still cameras. +# See http://www.exiv2.org/tags.html - @staticmethod - def extract(im, evaluation): - if hasattr(im, "_getexif"): - exif = im._getexif() - if not exif: - return +# names overriding the ones given by Pillow +Exif_names = { + 37385: "FlashInfo", + 40960: "FlashpixVersion", + 40962: "PixelXDimension", + 40963: "PixelYDimension", +} + + +def extract_exif(image, evaluation) -> Optional[Expression]: + """ + Convert Exif information from image into options + that can be passed to Image[]. + Return None if there is no Exif information. + """ + if hasattr(image, "getexif"): + exif = image.getexif() + # If exif is None or an empty list, we have no information. + if not exif: + return None + + exif_options: List[Expression] = [] + for k, v in sorted(exif.items(), key=itemgetter(0)): + name = ExifTags.get(k) + if not name: + continue - for k, v in sorted(exif.items(), key=lambda x: x[0]): - name = ExifTags.get(k) - if not name: - continue - - # EXIF has the following types: Short, Long, Rational, Ascii, Byte - # (see http://www.exiv2.org/tags.html). we detect the type from the - # Python type Pillow gives us and do the appropiate MMA handling. - - if isinstance(v, tuple) and len(v) == 2: # Rational - value = Rational(v[0], v[1]) - if name == "FocalLength": - value = value.round(2) - else: - value = Expression(SymbolSimplify, value).evaluate(evaluation) - elif isinstance(v, bytes): # Byte - value = String(" ".join(["%d" % x for x in v])) - elif isinstance(v, (int, str)): # Short, Long, Ascii - value = v + # EXIF has the following types: Short, Long, Rational, Ascii, Byte + # (see http://www.exiv2.org/tags.html). we detect the type from the + # Python type Pillow gives us and do the appropiate MMA handling. + + if isinstance(v, tuple) and len(v) == 2: # Rational + value = Rational(v[0], v[1]) + if name == "FocalLength": + value = from_python(value.round(2)) else: - continue + value = Expression(SymbolSimplify, value).evaluate(evaluation) + elif isinstance(v, bytes): # Byte + value = String(" ".join([str(x) for x in v])) + elif isinstance(v, (int, str)): # Short, Long, ASCII + value = from_python(v) + else: + continue - yield Expression(SymbolRule, String(_Exif._names.get(k, name)), value) + exif_options.append( + Expression(SymbolRule, String(Exif_names.get(k, name)), value) + ) + + return Expression(SymbolRule, String("RawExif"), ListExpression(*exif_options)) class ImageImport(_ImageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageImport.html</url> - <dl> <dt> 'ImageImport["path"]' <dd> import an image from the file "path". @@ -171,23 +184,23 @@ def apply(self, path, evaluation): pillow = PIL.Image.open(path.get_string_value()) pixels = numpy.asarray(pillow) is_rgb = len(pixels.shape) >= 3 and pixels.shape[2] >= 3 - exif = to_mathics_list(*list(_Exif.extract(pillow, evaluation))) + # exif = to_mathics_list(*list(_Exif.extract(pillow, evaluation))) + options_from_exif = extract_exif(pillow, evaluation) image = Image(pixels, "RGB" if is_rgb else "Grayscale") - return ListExpression( + image_list_expression = [ Expression(SymbolRule, String("Image"), image), Expression(SymbolRule, String("ColorSpace"), String(image.color_space)), - Expression( - SymbolRule, String("ImageSize"), from_python(image.dimensions()) - ), - Expression(SymbolRule, String("RawExif"), exif), - ) + ] + + if options_from_exif is not None: + image_list_expression.append(options_from_exif) + + return ListExpression(*image_list_expression) class ImageExport(_ImageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageExport.html</url> - <dl> <dt> 'ImageExport["path", $image$]' <dd> export $image$ as file in "path". diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index ecc127e08..e9815aa0d 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -1295,11 +1295,13 @@ class Import(Builtin): <url>:WMA link:https://reference.wolfram.com/language/ref/Import.html</url> <dl> - <dt>'Import["$file$"]' + <dt>'Import["$file$"]' <dd>imports data from a file. - <dt>'Import["$file$", $elements$]' + + <dt>'Import["$file$", $elements$]' <dd>imports the specified elements from a file. - <dt>'Import["http://$url$", ...]' and 'Import["ftp://$url$", ...]' + + <dt>'Import["http://$url$", ...]' and 'Import["ftp://$url$", ...]' <dd>imports from a URL. </dl> @@ -1337,7 +1339,6 @@ class Import(Builtin): = {accidental, alter, arpeggiate, ..., words} """ - summary_text = "import elements from a file" messages = { "nffil": "File not found during Import.", "chtype": ( @@ -1349,13 +1350,15 @@ class Import(Builtin): "emptyfch": "Function Channel not defined.", } + options = { + "$OptionSyntax": "System`Ignore", + } + rules = { "Import[filename_]": "Import[filename, {}]", } - options = { - "$OptionSyntax": "System`Ignore", - } + summary_text = "import elements from a file" def apply(self, filename, evaluation, options={}): "Import[filename_, OptionsPattern[]]" @@ -1578,11 +1581,13 @@ class ImportString(Import): <url>:WMA link:https://reference.wolfram.com/language/ref/ImportString.html</url> <dl> - <dt>'ImportString["$data$", "$format$"]' + <dt>'ImportString["$data$", "$format$"]' <dd>imports data in the specified format from a string. - <dt>'ImportString["$file$", $elements$]' + + <dt>'ImportString["$file$", $elements$]' <dd>imports the specified elements from a string. - <dt>'ImportString["$data$"]' + + <dt>'ImportString["$data$"]' <dd>attempts to determine the format of the string from its content. </dl> @@ -1641,7 +1646,6 @@ def apply_elements(self, data, elements, evaluation, options={}): if not (isinstance(data, String)): evaluation.message("ImportString", "string", data) return SymbolFailed - path = data.get_string_value() def determine_filetype(): if not FileFormat.detector: @@ -1683,11 +1687,13 @@ class Export(Builtin): <url>:WMA link:https://reference.wolfram.com/language/ref/Export.html</url> <dl> - <dt>'Export["$file$.$ext$", $expr$]' + <dt>'Export["$file$.$ext$", $expr$]' <dd>exports $expr$ to a file, using the extension $ext$ to determine the format. - <dt>'Export["$file$", $expr$, "$format$"]' + + <dt>'Export["$file$", $expr$, "$format$"]' <dd>exports $expr$ to a file in the specified format. - <dt>'Export["$file$", $exprs$, $elems$]' + + <dt>'Export["$file$", $exprs$, $elems$]' <dd>exports $exprs$ to a file as elements specified by $elems$. </dl> @@ -1735,7 +1741,6 @@ class Export(Builtin): """ - summary_text = "export elements to a file" messages = { "chtype": "First argument `1` is not a valid file specification.", "infer": "Cannot infer format of file `1`.", @@ -1774,6 +1779,8 @@ class Export(Builtin): "$OptionSyntax": "System`Ignore", } + summary_text = "export elements to a file" + def apply(self, filename, expr, evaluation, options={}): "Export[filename_, expr_, OptionsPattern[Export]]" From 1ba9cae871747bab23a2191a3f9ca5f6546a400c Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 21:03:50 -0500 Subject: [PATCH 089/121] Store pillow object.. This may make it simpler and more robust when all we need to do is write it out. Also, annotate more types --- mathics/builtin/drawing/image.py | 145 ++++++++++++++++++------------- 1 file changed, 83 insertions(+), 62 deletions(-) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index b2efe7f59..ebc37fb48 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -36,6 +36,7 @@ ) from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_python +from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull, SymbolTrue @@ -115,19 +116,29 @@ class _SkimageBuiltin(_ImageBuiltin): } -def extract_exif(image, evaluation) -> Optional[Expression]: +def extract_exif(image, evaluation: Evaluation) -> Optional[Expression]: """ Convert Exif information from image into options that can be passed to Image[]. Return None if there is no Exif information. """ if hasattr(image, "getexif"): - exif = image.getexif() + + # PIL seems to have a bug in getting v2_tags, + # specifically tag offsets because + # it expects image.fp to exist and for us it + # doesn't. + try: + exif = image.getexif() + except Exception: + return None + # If exif is None or an empty list, we have no information. if not exif: return None exif_options: List[Expression] = [] + for k, v in sorted(exif.items(), key=itemgetter(0)): name = ExifTags.get(k) if not name: @@ -167,27 +178,26 @@ class ImageImport(_ImageBuiltin): ## Image >> Import["ExampleData/Einstein.jpg"] = -Image- - #> Import["ExampleData/sunflowers.jpg"] + >> Import["ExampleData/sunflowers.jpg"] = -Image- >> Import["ExampleData/MadTeaParty.gif"] = -Image- >> Import["ExampleData/moon.tif"] = -Image- - #> Import["ExampleData/lena.tif"] + >> Import["ExampleData/lena.tif"] = -Image- """ summary_text = "import an image from a file" - def apply(self, path, evaluation): + def apply(self, path: String, evaluation: Evaluation): """ImageImport[path_String]""" - pillow = PIL.Image.open(path.get_string_value()) + pillow = PIL.Image.open(path.value) pixels = numpy.asarray(pillow) is_rgb = len(pixels.shape) >= 3 and pixels.shape[2] >= 3 - # exif = to_mathics_list(*list(_Exif.extract(pillow, evaluation))) options_from_exif = extract_exif(pillow, evaluation) - image = Image(pixels, "RGB" if is_rgb else "Grayscale") + image = Image(pixels, "RGB" if is_rgb else "Grayscale", pillow=pillow) image_list_expression = [ Expression(SymbolRule, String("Image"), image), Expression(SymbolRule, String("ColorSpace"), String(image.color_space)), @@ -210,7 +220,7 @@ class ImageExport(_ImageBuiltin): messages = {"noimage": "only an Image[] can be exported into an image file"} summary_text = "export an image to a file" - def apply(self, path, expr, opts, evaluation): + def apply(self, path: String, expr, opts, evaluation: Evaluation): """ImageExport[path_String, expr_, opts___]""" if isinstance(expr, Image): expr.pil().save(path.get_string_value()) @@ -254,7 +264,7 @@ def _reduce(iterable, ufunc): ufunc(result, i, result) return result - def apply(self, image, args, evaluation): + def apply(self, image, args, evaluation: Evaluation): "%(name)s[image_Image, args__]" images, arg = self.convert_args(image, *args.get_sequence()) if images is None: @@ -676,7 +686,7 @@ class ImageReflect(_ImageBuiltin): messages = {"bdrfl2": "`1` is not a valid 2D reflection specification."} - def apply(self, image, orig, dest, evaluation): + def apply(self, image, orig, dest, evaluation: Evaluation): "ImageReflect[image_Image, Rule[orig_, dest_]]" if isinstance(orig, Symbol) and isinstance(dest, Symbol): specs = [orig.get_name(), dest.get_name()] @@ -744,7 +754,7 @@ class ImageRotate(_ImageBuiltin): "imgang": "Angle `1` should be a real number, one of Top, Bottom, Left, Right, or a rule from one to another." } - def apply(self, image, angle, evaluation): + def apply(self, image, angle, evaluation: Evaluation): "ImageRotate[image_Image, angle_]" # FIXME: this test I suppose is okay in that it checks more or less what is needed. @@ -806,7 +816,7 @@ class ImagePartition(_ImageBuiltin): messages = {"arg2": "`1` is not a valid size specification for image partitions."} - def apply(self, image, w: Integer, h: Integer, evaluation): + def apply(self, image, w: Integer, h: Integer, evaluation: Evaluation): "ImagePartition[image_Image, {w_Integer, h_Integer}]" py_w = w.value py_h = h.value @@ -857,7 +867,7 @@ class ImageAdjust(_ImageBuiltin): "ImageAdjust[image_Image, {c_?RealNumberQ, b_?RealNumberQ}]": "ImageAdjust[image, {c, b, 1}]", } - def apply_auto(self, image, evaluation): + def apply_auto(self, image, evaluation: Evaluation): "ImageAdjust[image_Image]" pixels = pixels_as_float(image.pixels) @@ -874,7 +884,7 @@ def apply_auto(self, image, evaluation): pixels /= scales return Image(pixels, image.color_space) - def apply_contrast_brightness_gamma(self, image, c, b, g, evaluation): + def apply_contrast_brightness_gamma(self, image, c, b, g, evaluation: Evaluation): "ImageAdjust[image_Image, {c_?RealNumberQ, b_?RealNumberQ, g_?RealNumberQ}]" im = image.pil() @@ -945,7 +955,7 @@ class Sharpen(_ImageBuiltin): summary_text = "sharpen version of an image" rules = {"Sharpen[i_Image]": "Sharpen[i, 2]"} - def apply(self, image, r, evaluation): + def apply(self, image, r, evaluation: Evaluation): "Sharpen[image_Image, r_?RealNumberQ]" f = PIL.ImageFilter.UnsharpMask(r.round_to_float()) return image.filter(lambda im: im.filter(f)) @@ -968,7 +978,7 @@ class GaussianFilter(_ImageBuiltin): summary_text = "apply a gaussian filter to an image" messages = {"only3": "GaussianFilter only supports up to three channels."} - def apply_radius(self, image, radius, evaluation): + def apply_radius(self, image, radius, evaluation: Evaluation): "GaussianFilter[image_Image, radius_?RealNumberQ]" if len(image.pixels.shape) > 2 and image.pixels.shape[2] > 3: return evaluation.message("GaussianFilter", "only3") @@ -1015,7 +1025,7 @@ class MinFilter(PillowImageFilter): summary_text = "replace every pixel value by the minimum in a neighbourhood" - def apply(self, image, r: Integer, evaluation): + def apply(self, image, r: Integer, evaluation: Evaluation): "MinFilter[image_Image, r_Integer]" return self.compute(image, PIL.ImageFilter.MinFilter(1 + 2 * r.value)) @@ -1038,7 +1048,7 @@ class MaxFilter(PillowImageFilter): summary_text = "replace every pixel value by the maximum in a neighbourhood" - def apply(self, image, r: Integer, evaluation): + def apply(self, image, r: Integer, evaluation: Evaluation): "MaxFilter[image_Image, r_Integer]" return self.compute(image, PIL.ImageFilter.MaxFilter(1 + 2 * r.value)) @@ -1060,7 +1070,7 @@ class MedianFilter(PillowImageFilter): summary_text = "replace every pixel value by the median in a neighbourhood" - def apply(self, image, r: Integer, evaluation): + def apply(self, image, r: Integer, evaluation: Evaluation): "MedianFilter[image_Image, r_Integer]" return self.compute(image, PIL.ImageFilter.MedianFilter(1 + 2 * r.value)) @@ -1090,7 +1100,7 @@ class EdgeDetect(_SkimageBuiltin): "EdgeDetect[i_Image, r_?RealNumberQ]": "EdgeDetect[i, r, 0.2]", } - def apply(self, image, r, t, evaluation): + def apply(self, image, r, t, evaluation: Evaluation): "EdgeDetect[image_Image, r_?RealNumberQ, t_?RealNumberQ]" import skimage.feature @@ -1126,7 +1136,7 @@ class BoxMatrix(_ImageBuiltin): summary_text = "a matrix with all its entries set to 1" - def apply(self, r, evaluation): + def apply(self, r, evaluation: Evaluation): "BoxMatrix[r_?RealNumberQ]" py_r = abs(r.round_to_float()) s = int(math.floor(1 + 2 * py_r)) @@ -1148,7 +1158,7 @@ class DiskMatrix(_ImageBuiltin): summary_text = "a matrix with 1 in a disk-shaped region, and 0 outside" - def apply(self, r, evaluation): + def apply(self, r, evaluation: Evaluation): "DiskMatrix[r_?RealNumberQ]" py_r = abs(r.round_to_float()) s = int(math.floor(0.5 + py_r)) @@ -1179,7 +1189,7 @@ class DiamondMatrix(_ImageBuiltin): summary_text = "a matrix with 1 in a diamond-shaped region, and 0 outside" - def apply(self, r, evaluation): + def apply(self, r, evaluation: Evaluation): "DiamondMatrix[r_?RealNumberQ]" py_r = abs(r.round_to_float()) t = int(math.floor(0.5 + py_r)) @@ -1221,7 +1231,7 @@ class ImageConvolve(_ImageBuiltin): summary_text = "give the convolution of image with kernel" - def apply(self, image, kernel, evaluation): + def apply(self, image, kernel, evaluation: Evaluation): "%(name)s[image_Image, kernel_?MatrixQ]" numpy_kernel = matrix_to_numpy(kernel) pixels = pixels_as_float(image.pixels) @@ -1240,7 +1250,7 @@ class _MorphologyFilter(_SkimageBuiltin): rules = {"%(name)s[i_Image, r_?RealNumberQ]": "%(name)s[i, BoxMatrix[r]]"} - def apply(self, image, k, evaluation): + def apply(self, image, k, evaluation: Evaluation): "%(name)s[image_Image, k_?MatrixQ]" if image.color_space != "Grayscale": image = image.grayscale() @@ -1339,7 +1349,7 @@ class MorphologicalComponents(_SkimageBuiltin): rules = {"MorphologicalComponents[i_Image]": "MorphologicalComponents[i, 0]"} - def apply(self, image, t, evaluation): + def apply(self, image, t, evaluation: Evaluation): "MorphologicalComponents[image_Image, t_?RealNumberQ]" pixels = pixels_as_ubyte( pixels_as_float(image.grayscale().pixels) > t.round_to_float() @@ -1356,11 +1366,13 @@ def apply(self, image, t, evaluation): class ImageColorSpace(_ImageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageColorSpace.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ImageColorSpace.html</url> <dl> - <dt>'ImageColorSpace[$image$]' - <dd>gives $image$'s color space, e.g. "RGB" or "CMYK". + <dt>'ImageColorSpace[$image$]' + <dd>gives $image$'s color space, e.g. "RGB" or "CMYK". </dl> >> img = Import["ExampleData/lena.tif"]; @@ -1370,17 +1382,19 @@ class ImageColorSpace(_ImageBuiltin): summary_text = "colorspace used in the image" - def apply(self, image, evaluation): + def apply(self, image, evaluation: Evaluation): "ImageColorSpace[image_Image]" return String(image.color_space) class ColorQuantize(_ImageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ColorQuantize.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ColorQuantize.html</url> <dl> - <dt>'ColorQuantize[$image$, $n$]' + <dt>'ColorQuantize[$image$, $n$]' <dd>gives a version of $image$ using only $n$ colors. </dl> @@ -1399,7 +1413,7 @@ class ColorQuantize(_ImageBuiltin): summary_text = "give an approximation to image that uses only n distinct colors" messages = {"intp": "Positive integer expected at position `2` in `1`."} - def apply(self, image, n: Integer, evaluation): + def apply(self, image, n: Integer, evaluation: Evaluation): "ColorQuantize[image_Image, n_Integer]" py_value = n.value if py_value <= 0: @@ -1446,7 +1460,7 @@ class Threshold(_SkimageBuiltin): "skimage": "Please install scikit-image to use Method -> Cluster.", } - def apply(self, image, evaluation, options): + def apply(self, image, evaluation: Evaluation, options): "Threshold[image_Image, OptionsPattern[Threshold]]" pixels = image.grayscale().pixels @@ -1496,7 +1510,7 @@ class Binarize(_SkimageBuiltin): summary_text = "create a binarized image" - def apply(self, image, evaluation): + def apply(self, image, evaluation: Evaluation): "Binarize[image_Image]" image = image.grayscale() thresh = ( @@ -1505,12 +1519,12 @@ def apply(self, image, evaluation): if thresh is not None: return Image(image.pixels > thresh, "Grayscale") - def apply_t(self, image, t, evaluation): + def apply_t(self, image, t, evaluation: Evaluation): "Binarize[image_Image, t_?RealNumberQ]" pixels = image.grayscale().pixels return Image(pixels > t.round_to_float(), "Grayscale") - def apply_t1_t2(self, image, t1, t2, evaluation): + def apply_t1_t2(self, image, t1, t2, evaluation: Evaluation): "Binarize[image_Image, {t1_?RealNumberQ, t2_?RealNumberQ}]" pixels = image.grayscale().pixels mask1 = pixels > t1.round_to_float() @@ -1521,17 +1535,19 @@ def apply_t1_t2(self, image, t1, t2, evaluation): class ColorSeparate(_ImageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ColorSeparate.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ColorSeparate.html</url> <dl> - <dt>'ColorSeparate[$image$]' + <dt>'ColorSeparate[$image$]' <dd>Gives each channel of $image$ as a separate grayscale image. </dl> """ summary_text = "separate color channels" - def apply(self, image, evaluation): + def apply(self, image, evaluation: Evaluation): "ColorSeparate[image_Image]" images = [] pixels = image.pixels @@ -1558,7 +1574,7 @@ class ColorCombine(_ImageBuiltin): summary_text = "combine color channels" - def apply(self, channels, colorspace, evaluation): + def apply(self, channels, colorspace, evaluation: Evaluation): "ColorCombine[channels_List, colorspace_String]" py_colorspace = colorspace.get_string_value() @@ -1614,15 +1630,16 @@ def _linearize(a): class Colorize(_ImageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Colorize.html</url> <dl> - <dt>'Colorize[$values$]' - <dd>returns an image where each number in the rectangular matrix $values$ is a pixel and each - occurence of the same number is displayed in the same unique color, which is different from the - colors of all non-identical numbers. - <dt>'Colorize[$image$]' + <dt>'Colorize[$values$]' + <dd>returns an image where each number in the rectangular matrix \ + $values$ is a pixel and each occurence of the same number is \ + displayed in the same unique color, which is different from the \ + colors of all non-identical numbers. + + <dt>'Colorize[$image$]' <dd>gives a colorized version of $image$. </dl> @@ -1713,7 +1730,7 @@ class ImageData(_ImageBuiltin): messages = {"pixelfmt": 'Unsupported pixel format "``".'} - def apply(self, image, stype, evaluation): + def apply(self, image, stype, evaluation: Evaluation): "ImageData[image_Image, stype_String]" pixels = image.pixels stype = stype.get_string_value() @@ -1751,7 +1768,7 @@ class ImageTake(_ImageBuiltin): summary_text = "create an image from a range of lines of another image" - def apply(self, image, n: Integer, evaluation): + def apply(self, image, n: Integer, evaluation: Evaluation): "ImageTake[image_Image, n_Integer]" py_n = n.value if py_n >= 0: @@ -1773,7 +1790,7 @@ def flip(pixels): return slice(min(py_i1, py_i2), 1 + max(py_i1, py_i2)), flip - def apply_rows(self, image, r1: Integer, r2: Integer, evaluation): + def apply_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): "ImageTake[image_Image, {r1_Integer, r2_Integer}]" s, f = self._slice(image, r1, r2, 0) return Image(f(image.pixels[s]), image.color_space) @@ -1820,7 +1837,7 @@ class PixelValue(_ImageBuiltin): summary_text = "pixel value of image at a given position" messages = {"nopad": "Padding not implemented for PixelValue."} - def apply(self, image, x, y, evaluation): + def apply(self, image, x, y, evaluation: Evaluation): "PixelValue[image_Image, {x_?RealNumberQ, y_?RealNumberQ}]" x = int(x.round_to_float()) y = int(y.round_to_float()) @@ -1862,7 +1879,7 @@ class PixelValuePositions(_ImageBuiltin): "PixelValuePositions[image_Image, val_?RealNumberQ]": "PixelValuePositions[image, val, 0]" } - def apply(self, image, val, d, evaluation): + def apply(self, image, val, d, evaluation: Evaluation): "PixelValuePositions[image_Image, val_?RealNumberQ, d_?RealNumberQ]" val = val.round_to_float() d = d.round_to_float() @@ -1913,7 +1930,7 @@ class ImageDimensions(_ImageBuiltin): summary_text = "pixel dimensions of the raster associated with an image" - def apply(self, image, evaluation): + def apply(self, image, evaluation: Evaluation): "ImageDimensions[image_Image]" return to_mathics_list(*image.dimensions(), elements_conversion_fn=Integer) @@ -1938,7 +1955,7 @@ class ImageAspectRatio(_ImageBuiltin): summary_text = "give the ratio of height to width of an image" - def apply(self, image, evaluation): + def apply(self, image, evaluation: Evaluation): "ImageAspectRatio[image_Image]" dim = image.dimensions() return Expression(SymbolDivide, Integer(dim[1]), Integer(dim[0])) @@ -1965,7 +1982,7 @@ class ImageChannels(_ImageBuiltin): summary_text = "number of channels present in the data for an image" - def apply(self, image, evaluation): + def apply(self, image, evaluation: Evaluation): "ImageChannels[image_Image]" return Integer(image.channels()) @@ -1993,7 +2010,7 @@ class ImageType(_ImageBuiltin): summary_text = "type of values used for each pixel element in an image" - def apply(self, image, evaluation): + def apply(self, image, evaluation: Evaluation): "ImageType[image_Image]" return String(image.storage_type()) @@ -2075,6 +2092,8 @@ class Image(Atom): class_head_name = "System`Image" def __init__(self, pixels, color_space, metadata={}, **kwargs): + if "pillow" in kwargs: + self.pillow = kwargs.pop("pillow") super(Image, self).__init__(**kwargs) if len(pixels.shape) == 2: pixels = pixels.reshape(list(pixels.shape) + [1]) @@ -2091,7 +2110,7 @@ def filter(self, f): # apply PIL filters component-wise return Image(numpy.dstack(channels), self.color_space) def pil(self): - # see http://pillow.readthedocs.io/en/3.1.x/handbook/concepts.html#concept-modes + # see https://pillow.readthedocs.io/en/stable/handbook/concepts.html n = self.channels() if n == 1: @@ -2155,7 +2174,7 @@ def color_convert(self, to_color_space, preserve_alpha=True): def grayscale(self): return self.color_convert("Grayscale") - def atom_to_boxes(self, f, evaluation): + def atom_to_boxes(self, f, evaluation: Evaluation): pixels = pixels_as_ubyte(self.color_convert("RGB", True).pixels) shape = pixels.shape @@ -2289,7 +2308,7 @@ class ImageAtom(AtomBuiltin): summary_text = "internal representation of an image" requires = _image_requires - def apply_create(self, array, evaluation): + def apply_create(self, array, evaluation: Evaluation): "Image[array_]" pixels = _image_pixels(array.to_python()) if pixels is not None: @@ -2306,10 +2325,12 @@ def apply_create(self, array, evaluation): class TextRecognize(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/TextRecognize.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/TextRecognize.html</url> <dl> - <dt>'TextRecognize[{$image$}]' + <dt>'TextRecognize[{$image$}]' <dd>Recognizes text in $image$ and returns it as string. </dl> """ From 1fc96f5289e3750c5feda045bf3a0c722e438146 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sat, 24 Dec 2022 23:52:38 -0500 Subject: [PATCH 090/121] Add no-doc to test_summary_text Go over ImageFilters --- mathics/builtin/drawing/image.py | 281 ++++++++---------- mathics/eval/image.py | 87 +++++- .../test_summary_text.py | 9 +- 3 files changed, 206 insertions(+), 171 deletions(-) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index ebc37fb48..10d9e95af 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -16,7 +16,7 @@ import math import os.path as osp from collections import defaultdict -from operator import itemgetter +from copy import deepcopy from typing import Optional, Tuple from mathics.builtin.base import AtomBuiltin, Builtin, String, Test @@ -40,9 +40,10 @@ from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull, SymbolTrue -from mathics.core.systemsymbols import SymbolRule, SymbolSimplify +from mathics.core.systemsymbols import SymbolRule from mathics.eval.image import ( convolve, + extract_exif, matrix_to_numpy, numpy_flip, numpy_to_matrix, @@ -68,11 +69,9 @@ import PIL.ImageEnhance import PIL.ImageFilter import PIL.ImageOps - from PIL.ExifTags import TAGS as ExifTags - _enabled = True except ImportError: - _enabled = False + pass from io import BytesIO @@ -104,70 +103,6 @@ class _SkimageBuiltin(_ImageBuiltin): # Code related to Mathics Functions that import and export. -# Exif: Exchangeable image file format for digital still cameras. -# See http://www.exiv2.org/tags.html - -# names overriding the ones given by Pillow -Exif_names = { - 37385: "FlashInfo", - 40960: "FlashpixVersion", - 40962: "PixelXDimension", - 40963: "PixelYDimension", -} - - -def extract_exif(image, evaluation: Evaluation) -> Optional[Expression]: - """ - Convert Exif information from image into options - that can be passed to Image[]. - Return None if there is no Exif information. - """ - if hasattr(image, "getexif"): - - # PIL seems to have a bug in getting v2_tags, - # specifically tag offsets because - # it expects image.fp to exist and for us it - # doesn't. - try: - exif = image.getexif() - except Exception: - return None - - # If exif is None or an empty list, we have no information. - if not exif: - return None - - exif_options: List[Expression] = [] - - for k, v in sorted(exif.items(), key=itemgetter(0)): - name = ExifTags.get(k) - if not name: - continue - - # EXIF has the following types: Short, Long, Rational, Ascii, Byte - # (see http://www.exiv2.org/tags.html). we detect the type from the - # Python type Pillow gives us and do the appropiate MMA handling. - - if isinstance(v, tuple) and len(v) == 2: # Rational - value = Rational(v[0], v[1]) - if name == "FocalLength": - value = from_python(value.round(2)) - else: - value = Expression(SymbolSimplify, value).evaluate(evaluation) - elif isinstance(v, bytes): # Byte - value = String(" ".join([str(x) for x in v])) - elif isinstance(v, (int, str)): # Short, Long, ASCII - value = from_python(v) - else: - continue - - exif_options.append( - Expression(SymbolRule, String(Exif_names.get(k, name)), value) - ) - - return Expression(SymbolRule, String("RawExif"), ListExpression(*exif_options)) - - class ImageImport(_ImageBuiltin): """ <dl> @@ -188,9 +123,9 @@ class ImageImport(_ImageBuiltin): = -Image- """ - summary_text = "import an image from a file" + no_doc = True - def apply(self, path: String, evaluation: Evaluation): + def eval(self, path: String, evaluation: Evaluation): """ImageImport[path_String]""" pillow = PIL.Image.open(path.value) pixels = numpy.asarray(pillow) @@ -217,10 +152,11 @@ class ImageExport(_ImageBuiltin): </dl> """ + no_doc = True + messages = {"noimage": "only an Image[] can be exported into an image file"} - summary_text = "export an image to a file" - def apply(self, path: String, expr, opts, evaluation: Evaluation): + def eval(self, path: String, expr, opts, evaluation: Evaluation): """ImageExport[path_String, expr_, opts___]""" if isinstance(expr, Image): expr.pil().save(path.get_string_value()) @@ -264,7 +200,7 @@ def _reduce(iterable, ufunc): ufunc(result, i, result) return result - def apply(self, image, args, evaluation: Evaluation): + def eval(self, image, args, evaluation: Evaluation): "%(name)s[image_Image, args__]" images, arg = self.convert_args(image, *args.get_sequence()) if images is None: @@ -420,7 +356,7 @@ class RandomImage(_ImageBuiltin): } summary_text = "build an image with random pixels" - def apply(self, minval, maxval, w, h, evaluation, options): + def eval(self, minval, maxval, w, h, evaluation, options): "RandomImage[{minval_?RealNumberQ, maxval_?RealNumberQ}, {w_Integer, h_Integer}, OptionsPattern[RandomImage]]" color_space = self.get_option(options, "ColorSpace", evaluation) if ( @@ -536,7 +472,7 @@ def _get_image_size_spec(self, old_size, new_size) -> Optional[float]: return max(1, old_size * s) # handle negative s values silently return None - def apply_resize_width(self, image, s, evaluation, options): + def eval_resize_width(self, image, s, evaluation, options): "ImageResize[image_Image, s_, OptionsPattern[ImageResize]]" old_w = image.pixels.shape[1] if s.has_form("List", 1): @@ -550,9 +486,9 @@ def apply_resize_width(self, image, s, evaluation, options): height = width else: height = Symbol("Automatic") - return self.apply_resize_width_height(image, width, height, evaluation, options) + return self.eval_resize_width_height(image, width, height, evaluation, options) - def apply_resize_width_height(self, image, width, height, evaluation, options): + def eval_resize_width_height(self, image, width, height, evaluation, options): "ImageResize[image_Image, {width_, height_}, OptionsPattern[ImageResize]]" # resampling method resampling = self.get_option(options, "Resampling", evaluation) @@ -686,7 +622,7 @@ class ImageReflect(_ImageBuiltin): messages = {"bdrfl2": "`1` is not a valid 2D reflection specification."} - def apply(self, image, orig, dest, evaluation: Evaluation): + def eval(self, image, orig, dest, evaluation: Evaluation): "ImageReflect[image_Image, Rule[orig_, dest_]]" if isinstance(orig, Symbol) and isinstance(dest, Symbol): specs = [orig.get_name(), dest.get_name()] @@ -754,7 +690,7 @@ class ImageRotate(_ImageBuiltin): "imgang": "Angle `1` should be a real number, one of Top, Bottom, Left, Right, or a rule from one to another." } - def apply(self, image, angle, evaluation: Evaluation): + def eval(self, image, angle, evaluation: Evaluation): "ImageRotate[image_Image, angle_]" # FIXME: this test I suppose is okay in that it checks more or less what is needed. @@ -816,7 +752,7 @@ class ImagePartition(_ImageBuiltin): messages = {"arg2": "`1` is not a valid size specification for image partitions."} - def apply(self, image, w: Integer, h: Integer, evaluation: Evaluation): + def eval(self, image, w: Integer, h: Integer, evaluation: Evaluation): "ImagePartition[image_Image, {w_Integer, h_Integer}]" py_w = w.value py_h = h.value @@ -867,7 +803,7 @@ class ImageAdjust(_ImageBuiltin): "ImageAdjust[image_Image, {c_?RealNumberQ, b_?RealNumberQ}]": "ImageAdjust[image, {c, b, 1}]", } - def apply_auto(self, image, evaluation: Evaluation): + def eval_auto(self, image, evaluation: Evaluation): "ImageAdjust[image_Image]" pixels = pixels_as_float(image.pixels) @@ -884,7 +820,7 @@ def apply_auto(self, image, evaluation: Evaluation): pixels /= scales return Image(pixels, image.color_space) - def apply_contrast_brightness_gamma(self, image, c, b, g, evaluation: Evaluation): + def eval_contrast_brightness_gamma(self, image, c, b, g, evaluation: Evaluation): "ImageAdjust[image_Image, {c_?RealNumberQ, b_?RealNumberQ, g_?RealNumberQ}]" im = image.pil() @@ -912,9 +848,10 @@ class Blur(_ImageBuiltin): <url>:WMA link:https://reference.wolfram.com/language/ref/Blur.html</url> <dl> - <dt>'Blur[$image$]' + <dt>'Blur[$image$]' <dd>gives a blurred version of $image$. - <dt>'Blur[$image$, $r$]' + + <dt>'Blur[$image$, $r$]' <dd>blurs $image$ with a kernel of size $r$. </dl> @@ -955,7 +892,7 @@ class Sharpen(_ImageBuiltin): summary_text = "sharpen version of an image" rules = {"Sharpen[i_Image]": "Sharpen[i, 2]"} - def apply(self, image, r, evaluation: Evaluation): + def eval(self, image, r, evaluation: Evaluation): "Sharpen[image_Image, r_?RealNumberQ]" f = PIL.ImageFilter.UnsharpMask(r.round_to_float()) return image.filter(lambda im: im.filter(f)) @@ -978,7 +915,7 @@ class GaussianFilter(_ImageBuiltin): summary_text = "apply a gaussian filter to an image" messages = {"only3": "GaussianFilter only supports up to three channels."} - def apply_radius(self, image, radius, evaluation: Evaluation): + def eval_radius(self, image, radius, evaluation: Evaluation): "GaussianFilter[image_Image, radius_?RealNumberQ]" if len(image.pixels.shape) > 2 and image.pixels.shape[2] > 3: return evaluation.message("GaussianFilter", "only3") @@ -993,11 +930,11 @@ def apply_radius(self, image, radius, evaluation: Evaluation): class PillowImageFilter(_ImageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/PillowImageFilter.html</url> + ## <url>:PillowImageFilter:</url> <dl> - <dt>'PillowImageFilter[$image$, "filtername"]' - <dd> applies an image filter "filtername" from the pillow library. + <dt>'PillowImageFilter[$image$, "filtername"]' + <dd> applies an image filter "filtername" from the pillow library. </dl> TODO: test cases? """ @@ -1025,7 +962,7 @@ class MinFilter(PillowImageFilter): summary_text = "replace every pixel value by the minimum in a neighbourhood" - def apply(self, image, r: Integer, evaluation: Evaluation): + def eval(self, image, r: Integer, evaluation: Evaluation): "MinFilter[image_Image, r_Integer]" return self.compute(image, PIL.ImageFilter.MinFilter(1 + 2 * r.value)) @@ -1036,9 +973,9 @@ class MaxFilter(PillowImageFilter): <url>:WMA link:https://reference.wolfram.com/language/ref/MaxFilter.html</url> <dl> - <dt>'MaxFilter[$image$, $r$]' - <dd>gives $image$ with a maximum filter of radius $r$ applied on it. This always - picks the largest value in the filter's area. + <dt>'MaxFilter[$image$, $r$]' + <dd>gives $image$ with a maximum filter of radius $r$ applied on it. This always \ + picks the largest value in the filter's area. </dl> >> lena = Import["ExampleData/lena.tif"]; @@ -1048,19 +985,21 @@ class MaxFilter(PillowImageFilter): summary_text = "replace every pixel value by the maximum in a neighbourhood" - def apply(self, image, r: Integer, evaluation: Evaluation): + def eval(self, image, r: Integer, evaluation: Evaluation): "MaxFilter[image_Image, r_Integer]" return self.compute(image, PIL.ImageFilter.MaxFilter(1 + 2 * r.value)) class MedianFilter(PillowImageFilter): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/MedianFilter.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/MedianFilter.html</url> <dl> - <dt>'MedianFilter[$image$, $r$]' - <dd>gives $image$ with a median filter of radius $r$ applied on it. This always - picks the median value in the filter's area. + <dt>'MedianFilter[$image$, $r$]' + <dd>gives $image$ with a median filter of radius $r$ applied on it. This always \ + picks the median value in the filter's area. </dl> >> lena = Import["ExampleData/lena.tif"]; @@ -1070,7 +1009,7 @@ class MedianFilter(PillowImageFilter): summary_text = "replace every pixel value by the median in a neighbourhood" - def apply(self, image, r: Integer, evaluation: Evaluation): + def eval(self, image, r: Integer, evaluation: Evaluation): "MedianFilter[image_Image, r_Integer]" return self.compute(image, PIL.ImageFilter.MedianFilter(1 + 2 * r.value)) @@ -1081,7 +1020,7 @@ class EdgeDetect(_SkimageBuiltin): <url>:WMA link:https://reference.wolfram.com/language/ref/EdgeDetect.html</url> <dl> - <dt>'EdgeDetect[$image$]' + <dt>'EdgeDetect[$image$]' <dd>returns an image showing the edges in $image$. </dl> @@ -1100,7 +1039,7 @@ class EdgeDetect(_SkimageBuiltin): "EdgeDetect[i_Image, r_?RealNumberQ]": "EdgeDetect[i, r, 0.2]", } - def apply(self, image, r, t, evaluation: Evaluation): + def eval(self, image, r, t, evaluation: Evaluation): "EdgeDetect[image_Image, r_?RealNumberQ, t_?RealNumberQ]" import skimage.feature @@ -1136,7 +1075,7 @@ class BoxMatrix(_ImageBuiltin): summary_text = "a matrix with all its entries set to 1" - def apply(self, r, evaluation: Evaluation): + def eval(self, r, evaluation: Evaluation): "BoxMatrix[r_?RealNumberQ]" py_r = abs(r.round_to_float()) s = int(math.floor(1 + 2 * py_r)) @@ -1148,7 +1087,7 @@ class DiskMatrix(_ImageBuiltin): <url>:WMA link:https://reference.wolfram.com/language/ref/DiskMatrix.html</url> <dl> - <dt>'DiskMatrix[$s]' + <dt>'DiskMatrix[$s]' <dd>Gives a disk shaped kernel of size 2 $s$ + 1. </dl> @@ -1158,7 +1097,7 @@ class DiskMatrix(_ImageBuiltin): summary_text = "a matrix with 1 in a disk-shaped region, and 0 outside" - def apply(self, r, evaluation: Evaluation): + def eval(self, r, evaluation: Evaluation): "DiskMatrix[r_?RealNumberQ]" py_r = abs(r.round_to_float()) s = int(math.floor(0.5 + py_r)) @@ -1189,7 +1128,7 @@ class DiamondMatrix(_ImageBuiltin): summary_text = "a matrix with 1 in a diamond-shaped region, and 0 outside" - def apply(self, r, evaluation: Evaluation): + def eval(self, r, evaluation: Evaluation): "DiamondMatrix[r_?RealNumberQ]" py_r = abs(r.round_to_float()) t = int(math.floor(0.5 + py_r)) @@ -1216,7 +1155,7 @@ class ImageConvolve(_ImageBuiltin): <url>:WMA link:https://reference.wolfram.com/language/ref/ImageConvolve.html</url> <dl> - <dt>'ImageConvolve[$image$, $kernel$]' + <dt>'ImageConvolve[$image$, $kernel$]' <dd>Computes the convolution of $image$ using $kernel$. </dl> @@ -1231,7 +1170,7 @@ class ImageConvolve(_ImageBuiltin): summary_text = "give the convolution of image with kernel" - def apply(self, image, kernel, evaluation: Evaluation): + def eval(self, image, kernel, evaluation: Evaluation): "%(name)s[image_Image, kernel_?MatrixQ]" numpy_kernel = matrix_to_numpy(kernel) pixels = pixels_as_float(image.pixels) @@ -1250,7 +1189,7 @@ class _MorphologyFilter(_SkimageBuiltin): rules = {"%(name)s[i_Image, r_?RealNumberQ]": "%(name)s[i, BoxMatrix[r]]"} - def apply(self, image, k, evaluation: Evaluation): + def eval(self, image, k, evaluation: Evaluation): "%(name)s[image_Image, k_?MatrixQ]" if image.color_space != "Grayscale": image = image.grayscale() @@ -1268,7 +1207,7 @@ class Dilation(_MorphologyFilter): <url>:WMA link:https://reference.wolfram.com/language/ref/Dilation.html</url> <dl> - <dt>'Dilation[$image$, $ker$]' + <dt>'Dilation[$image$, $ker$]' <dd>Gives the morphological dilation of $image$ with respect to structuring element $ker$. </dl> @@ -1285,7 +1224,7 @@ class Erosion(_MorphologyFilter): <url>:WMA link:https://reference.wolfram.com/language/ref/Erosion.html</url> <dl> - <dt>'Erosion[$image$, $ker$]' + <dt>'Erosion[$image$, $ker$]' <dd>Gives the morphological erosion of $image$ with respect to structuring element $ker$. </dl> @@ -1302,7 +1241,7 @@ class Opening(_MorphologyFilter): <url>:WMA link:https://reference.wolfram.com/language/ref/Opening.html</url> <dl> - <dt>'Opening[$image$, $ker$]' + <dt>'Opening[$image$, $ker$]' <dd>Gives the morphological opening of $image$ with respect to structuring element $ker$. </dl> @@ -1319,7 +1258,7 @@ class Closing(_MorphologyFilter): <url>:WMA link:https://reference.wolfram.com/language/ref/Closing.html</url> <dl> - <dt>'Closing[$image$, $ker$]' + <dt>'Closing[$image$, $ker$]' <dd>Gives the morphological closing of $image$ with respect to structuring element $ker$. </dl> @@ -1336,12 +1275,13 @@ class MorphologicalComponents(_SkimageBuiltin): <url>:WMA link:https://reference.wolfram.com/language/ref/MorphologicalComponents.html</url> <dl> - <dt>'MorphologicalComponents[$image$]' - <dd> Builds a 2-D array in which each pixel of $image$ is replaced - by an integer index representing the connected foreground image - component in which the pixel lies. - <dt>'MorphologicalComponents[$image$, $threshold$]' - <dd> consider any pixel with a value above $threshold$ as the foreground. + <dt>'MorphologicalComponents[$image$]' + <dd> Builds a 2-D array in which each pixel of $image$ is replaced \ + by an integer index representing the connected foreground image \ + component in which the pixel lies. + + <dt>'MorphologicalComponents[$image$, $threshold$]' + <dd> consider any pixel with a value above $threshold$ as the foreground. </dl> """ @@ -1349,7 +1289,7 @@ class MorphologicalComponents(_SkimageBuiltin): rules = {"MorphologicalComponents[i_Image]": "MorphologicalComponents[i, 0]"} - def apply(self, image, t, evaluation: Evaluation): + def eval(self, image, t, evaluation: Evaluation): "MorphologicalComponents[image_Image, t_?RealNumberQ]" pixels = pixels_as_ubyte( pixels_as_float(image.grayscale().pixels) > t.round_to_float() @@ -1382,7 +1322,7 @@ class ImageColorSpace(_ImageBuiltin): summary_text = "colorspace used in the image" - def apply(self, image, evaluation: Evaluation): + def eval(self, image, evaluation: Evaluation): "ImageColorSpace[image_Image]" return String(image.color_space) @@ -1413,7 +1353,7 @@ class ColorQuantize(_ImageBuiltin): summary_text = "give an approximation to image that uses only n distinct colors" messages = {"intp": "Positive integer expected at position `2` in `1`."} - def apply(self, image, n: Integer, evaluation: Evaluation): + def eval(self, image, n: Integer, evaluation: Evaluation): "ColorQuantize[image_Image, n_Integer]" py_value = n.value if py_value <= 0: @@ -1460,7 +1400,7 @@ class Threshold(_SkimageBuiltin): "skimage": "Please install scikit-image to use Method -> Cluster.", } - def apply(self, image, evaluation: Evaluation, options): + def eval(self, image, evaluation: Evaluation, options): "Threshold[image_Image, OptionsPattern[Threshold]]" pixels = image.grayscale().pixels @@ -1510,7 +1450,7 @@ class Binarize(_SkimageBuiltin): summary_text = "create a binarized image" - def apply(self, image, evaluation: Evaluation): + def eval(self, image, evaluation: Evaluation): "Binarize[image_Image]" image = image.grayscale() thresh = ( @@ -1519,12 +1459,12 @@ def apply(self, image, evaluation: Evaluation): if thresh is not None: return Image(image.pixels > thresh, "Grayscale") - def apply_t(self, image, t, evaluation: Evaluation): + def eval_t(self, image, t, evaluation: Evaluation): "Binarize[image_Image, t_?RealNumberQ]" pixels = image.grayscale().pixels return Image(pixels > t.round_to_float(), "Grayscale") - def apply_t1_t2(self, image, t1, t2, evaluation: Evaluation): + def eval_t1_t2(self, image, t1, t2, evaluation: Evaluation): "Binarize[image_Image, {t1_?RealNumberQ, t2_?RealNumberQ}]" pixels = image.grayscale().pixels mask1 = pixels > t1.round_to_float() @@ -1547,7 +1487,7 @@ class ColorSeparate(_ImageBuiltin): summary_text = "separate color channels" - def apply(self, image, evaluation: Evaluation): + def eval(self, image, evaluation: Evaluation): "ColorSeparate[image_Image]" images = [] pixels = image.pixels @@ -1564,7 +1504,7 @@ class ColorCombine(_ImageBuiltin): <url>:WMA link:https://reference.wolfram.com/language/ref/ColorCombine.html</url> <dl> - <dt>'ColorCombine[$channels$, $colorspace$]' + <dt>'ColorCombine[$channels$, $colorspace$]' <dd>Gives an image with $colorspace$ and the respective components described by the given channels. </dl> @@ -1574,7 +1514,7 @@ class ColorCombine(_ImageBuiltin): summary_text = "combine color channels" - def apply(self, channels, colorspace, evaluation: Evaluation): + def eval(self, channels, colorspace, evaluation: Evaluation): "ColorCombine[channels_List, colorspace_String]" py_colorspace = colorspace.get_string_value() @@ -1657,7 +1597,7 @@ class Colorize(_ImageBuiltin): "cfun": "`1` is neither a gradient ColorData nor a pure function suitable as ColorFunction." } - def apply(self, values, evaluation, options): + def eval(self, values, evaluation, options): "Colorize[values_, OptionsPattern[%(name)s]]" if isinstance(values, Image): @@ -1725,12 +1665,12 @@ class ImageData(_ImageBuiltin): = ImageData[-Image-, Bytf] """ - summary_text = "the array of pixel values from an image" - rules = {"ImageData[image_Image]": 'ImageData[image, "Real"]'} - messages = {"pixelfmt": 'Unsupported pixel format "``".'} - def apply(self, image, stype, evaluation: Evaluation): + rules = {"ImageData[image_Image]": 'ImageData[image, "Real"]'} + summary_text = "the array of pixel values from an image" + + def eval(self, image, stype, evaluation: Evaluation): "ImageData[image_Image, stype_String]" pixels = image.pixels stype = stype.get_string_value() @@ -1768,7 +1708,7 @@ class ImageTake(_ImageBuiltin): summary_text = "create an image from a range of lines of another image" - def apply(self, image, n: Integer, evaluation: Evaluation): + def eval(self, image, n: Integer, evaluation: Evaluation): "ImageTake[image_Image, n_Integer]" py_n = n.value if py_n >= 0: @@ -1790,12 +1730,12 @@ def flip(pixels): return slice(min(py_i1, py_i2), 1 + max(py_i1, py_i2)), flip - def apply_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): + def eval_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): "ImageTake[image_Image, {r1_Integer, r2_Integer}]" s, f = self._slice(image, r1, r2, 0) return Image(f(image.pixels[s]), image.color_space) - def apply_rows_cols( + def eval_rows_cols( self, image, r1: Integer, r2: Integer, c1: Integer, c2: Integer, evaluation ): "ImageTake[image_Image, {r1_Integer, r2_Integer}, {c1_Integer, c2_Integer}]" @@ -1834,10 +1774,11 @@ class PixelValue(_ImageBuiltin): : Padding not implemented for PixelValue. """ - summary_text = "pixel value of image at a given position" messages = {"nopad": "Padding not implemented for PixelValue."} - def apply(self, image, x, y, evaluation: Evaluation): + summary_text = "pixel value of image at a given position" + + def eval(self, image, x, y, evaluation: Evaluation): "PixelValue[image_Image, {x_?RealNumberQ, y_?RealNumberQ}]" x = int(x.round_to_float()) y = int(y.round_to_float()) @@ -1879,7 +1820,7 @@ class PixelValuePositions(_ImageBuiltin): "PixelValuePositions[image_Image, val_?RealNumberQ]": "PixelValuePositions[image, val, 0]" } - def apply(self, image, val, d, evaluation: Evaluation): + def eval(self, image, val, d, evaluation: Evaluation): "PixelValuePositions[image_Image, val_?RealNumberQ, d_?RealNumberQ]" val = val.round_to_float() d = d.round_to_float() @@ -1930,7 +1871,7 @@ class ImageDimensions(_ImageBuiltin): summary_text = "pixel dimensions of the raster associated with an image" - def apply(self, image, evaluation: Evaluation): + def eval(self, image, evaluation: Evaluation): "ImageDimensions[image_Image]" return to_mathics_list(*image.dimensions(), elements_conversion_fn=Integer) @@ -1955,7 +1896,7 @@ class ImageAspectRatio(_ImageBuiltin): summary_text = "give the ratio of height to width of an image" - def apply(self, image, evaluation: Evaluation): + def eval(self, image, evaluation: Evaluation): "ImageAspectRatio[image_Image]" dim = image.dimensions() return Expression(SymbolDivide, Integer(dim[1]), Integer(dim[0])) @@ -1982,7 +1923,7 @@ class ImageChannels(_ImageBuiltin): summary_text = "number of channels present in the data for an image" - def apply(self, image, evaluation: Evaluation): + def eval(self, image, evaluation: Evaluation): "ImageChannels[image_Image]" return Integer(image.channels()) @@ -1991,6 +1932,7 @@ class ImageType(_ImageBuiltin): """ <url> :WMA link:https://reference.wolfram.com/language/ref/ImageType.html</url> + <dl> <dt>'ImageType[$image$]' <dd>gives the interval storage type of $image$, e.g. "Real", "Bit32", or "Bit". @@ -2010,7 +1952,7 @@ class ImageType(_ImageBuiltin): summary_text = "type of values used for each pixel element in an image" - def apply(self, image, evaluation: Evaluation): + def eval(self, image, evaluation: Evaluation): "ImageType[image_Image]" return String(image.storage_type()) @@ -2174,7 +2116,11 @@ def color_convert(self, to_color_space, preserve_alpha=True): def grayscale(self): return self.color_convert("Grayscale") - def atom_to_boxes(self, f, evaluation: Evaluation): + def atom_to_boxes(self, f, evaluation: Evaluation) -> ImageBox: + """ + Converts our internal Image object into a base64-encode + image PNG. + """ pixels = pixels_as_ubyte(self.color_convert("RGB", True).pixels) shape = pixels.shape @@ -2183,12 +2129,15 @@ def atom_to_boxes(self, f, evaluation: Evaluation): scaled_width = width scaled_height = height - if len(shape) >= 3 and shape[2] == 4: - pixels_format = "RGBA" + # If the image was created from PIL, use that rather than + # reconstruct it from pixels which we can get wrong. + # In particular getting color-mapping info right can be + # tricky. + if hasattr(self, "pillow"): + pillow = deepcopy(self.pillow) else: - pixels_format = "RGB" - - pillow = PIL.Image.fromarray(pixels, pixels_format) + pixels_format = "RGBA" if len(shape) >= 3 and shape[2] == 4 else "RGB" + pillow = PIL.Image.fromarray(pixels, pixels_format) # if the image is very small, scale it up using nearest neighbour. min_size = 128 @@ -2294,10 +2243,11 @@ class ImageAtom(AtomBuiltin): <url>:WMA link:https://reference.wolfram.com/language/ref/ImageAtom.html</url> <dl> - <dt>'Image[...]' - <dd> produces the internal representation of an image from an array - of values for the pixels. + <dt>'Image[...]' + <dd> produces the internal representation of an image from an array \ + of values for the pixels. </dl> + #> Image[{{{1,1,0},{0,1,1}}, {{1,0,1},{1,1,0}}}] = -Image- @@ -2308,7 +2258,7 @@ class ImageAtom(AtomBuiltin): summary_text = "internal representation of an image" requires = _image_requires - def apply_create(self, array, evaluation: Evaluation): + def eval_create(self, array, evaluation: Evaluation): "Image[array_]" pixels = _image_pixels(array.to_python()) if pixels is not None: @@ -2346,7 +2296,7 @@ class TextRecognize(Builtin): options = {"Language": '"English"'} - def apply(self, image, evaluation, options): + def eval(self, image, evaluation, options): "TextRecognize[image_Image, OptionsPattern[%(name)s]]" import pyocr @@ -2398,17 +2348,21 @@ def apply(self, image, evaluation, options): class WordCloud(Builtin): """ - - <url>:WMA link:https://reference.wolfram.com/language/ref/WordCloud.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/WordCloud.html</url> <dl> - <dt>'WordCloud[{$word1$, $word2$, ...}]' + <dt>'WordCloud[{$word1$, $word2$, ...}]' <dd>Gives a word cloud with the given list of words. - <dt>'WordCloud[{$weight1$ -> $word1$, $weight2$ -> $word2$, ...}]' + + <dt>'WordCloud[{$weight1$ -> $word1$, $weight2$ -> $word2$, ...}]' <dd>Gives a word cloud with the words weighted using the given weights. - <dt>'WordCloud[{$weight1$, $weight2$, ...} -> {$word1$, $word2$, ...}]' + + <dt>'WordCloud[{$weight1$, $weight2$, ...} -> {$word1$, $word2$, ...}]' <dd>Also gives a word cloud with the words weighted using the given weights. - <dt>'WordCloud[{{$word1$, $weight1$}, {$word2$, $weight2$}, ...}]' + + <dt>'WordCloud[{{$word1$, $weight1$}, {$word2$, $weight2$}, ...}]' <dd>Gives a word cloud with the words weighted using the given weights. </dl> @@ -2440,7 +2394,7 @@ class WordCloud(Builtin): (102, 102, 102), ) - def apply_words_weights(self, weights, words, evaluation, options): + def eval_words_weights(self, weights, words, evaluation, options): "WordCloud[weights_List -> words_List, OptionsPattern[%(name)s]]" if len(weights.elements) != len(words.elements): return @@ -2451,7 +2405,7 @@ def weights_and_words(): return self._word_cloud(weights_and_words(), evaluation, options) - def apply_words(self, words, evaluation, options): + def eval_words(self, words, evaluation, options): "WordCloud[words_List, OptionsPattern[%(name)s]]" if not words: @@ -2521,7 +2475,6 @@ def _word_cloud(self, words, evaluation, options): return # inspired by http://minimaxir.com/2016/05/wordclouds/ - import os import random def color_func( diff --git a/mathics/eval/image.py b/mathics/eval/image.py index c5aecb418..40aeac77a 100644 --- a/mathics/eval/image.py +++ b/mathics/eval/image.py @@ -4,13 +4,42 @@ helper functions for images """ +from operator import itemgetter +from typing import List, Optional + import numpy +from mathics.builtin.base import String +from mathics.core.atoms import Rational +from mathics.core.convert.python import from_python +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.systemsymbols import SymbolRule, SymbolSimplify + +try: + from PIL.ExifTags import TAGS as ExifTags +except ImportError: + ExifTags = {} + +# Exif: Exchangeable image file format for digital still cameras. +# See http://www.exiv2.org/tags.html + +# names overriding the ones given by Pillow +Exif_names = { + 37385: "FlashInfo", + 40960: "FlashpixVersion", + 40962: "PixelXDimension", + 40963: "PixelYDimension", +} + 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. + """ + 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) @@ -31,6 +60,58 @@ def convolve(in1, in2, fixed=True): return ret[tuple(slice(p, -p) for p in excess)] +def extract_exif(image, evaluation: Evaluation) -> Optional[Expression]: + """ + Convert Exif information from image into options + that can be passed to Image[]. + Return None if there is no Exif information. + """ + if hasattr(image, "getexif"): + + # PIL seems to have a bug in getting v2_tags, + # specifically tag offsets because + # it expects image.fp to exist and for us it + # doesn't. + try: + exif = image.getexif() + except Exception: + return None + + # If exif is None or an empty list, we have no information. + if not exif: + return None + + exif_options: List[Expression] = [] + + for k, v in sorted(exif.items(), key=itemgetter(0)): + name = ExifTags.get(k) + if not name: + continue + + # EXIF has the following types: Short, Long, Rational, Ascii, Byte + # (see http://www.exiv2.org/tags.html). we detect the type from the + # Python type Pillow gives us and do the appropiate MMA handling. + + if isinstance(v, tuple) and len(v) == 2: # Rational + value = Rational(v[0], v[1]) + if name == "FocalLength": + value = from_python(value.round(2)) + else: + value = Expression(SymbolSimplify, value).evaluate(evaluation) + elif isinstance(v, bytes): # Byte + value = String(" ".join([str(x) for x in v])) + elif isinstance(v, (int, str)): # Short, Long, ASCII + value = from_python(v) + else: + continue + + exif_options.append( + Expression(SymbolRule, String(Exif_names.get(k, name)), value) + ) + + return Expression(SymbolRule, String("RawExif"), ListExpression(*exif_options)) + + def matrix_to_numpy(a): def matrix(): for y in a.elements: diff --git a/test/consistency-and-style/test_summary_text.py b/test/consistency-and-style/test_summary_text.py index 5a9d99617..7f36b9259 100644 --- a/test/consistency-and-style/test_summary_text.py +++ b/test/consistency-and-style/test_summary_text.py @@ -9,6 +9,7 @@ from mathics import __file__ as mathics_initfile_path from mathics.builtin import name_is_builtin_symbol from mathics.builtin.base import Builtin +from mathics.doc.common_doc import skip_doc from mathics.version import __version__ # noqa used in loading to check consistency. # Get file system path name for mathics.builtin @@ -203,14 +204,14 @@ def test_summary_text_available(module_name): var = name_is_builtin_symbol(module, name) if var is None: continue - # skip if var is not a builtin that belongs to - # this module - if len(name) > 3 and name[-3:] == "Box": - continue + instance = var(expression=False) if not isinstance(instance, Builtin): continue + if skip_doc(instance.__class__): + continue + # For private / internal symbols, # the documentation is optional. if "Internal`" in instance.context or "Private`" in instance.context: From eee73fc0e69b5f2f05b859fb828e64dcda574497 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sun, 25 Dec 2022 01:06:23 -0500 Subject: [PATCH 091/121] Misc cleanups... Order functions. --- mathics/builtin/drawing/image.py | 265 ++++++++++++++++--------------- 1 file changed, 141 insertions(+), 124 deletions(-) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 10d9e95af..a1e98be5c 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -159,7 +159,7 @@ class ImageExport(_ImageBuiltin): def eval(self, path: String, expr, opts, evaluation: Evaluation): """ImageExport[path_String, expr_, opts___]""" if isinstance(expr, Image): - expr.pil().save(path.get_string_value()) + expr.pil().save(path.value) return SymbolNull else: return evaluation.message("ImageExport", "noimage") @@ -675,7 +675,7 @@ class ImageRotate(_ImageBuiltin): >> ImageRotate[ein, 45 Degree] = -Image- - >> ImageRotate[ein, Pi / 2] + >> ImageRotate[ein, Pi / 4] = -Image- #> ImageRotate[ein, ein] @@ -683,13 +683,14 @@ class ImageRotate(_ImageBuiltin): = ImageRotate[-Image-, -Image-] """ - summary_text = "rotate an image" - rules = {"ImageRotate[i_Image]": "ImageRotate[i, 90 Degree]"} - messages = { "imgang": "Angle `1` should be a real number, one of Top, Bottom, Left, Right, or a rule from one to another." } + rules = {"ImageRotate[i_Image]": "ImageRotate[i, 90 Degree]"} + + summary_text = "rotate an image" + def eval(self, image, angle, evaluation: Evaluation): "ImageRotate[image_Image, angle_]" @@ -718,9 +719,10 @@ class ImagePartition(_ImageBuiltin): <url>:WMA link:https://reference.wolfram.com/language/ref/ImagePartition.html</url> <dl> - <dt>'ImagePartition[$image$, $s$]' + <dt>'ImagePartition[$image$, $s$]' <dd>Partitions an image into an array of $s$ x $s$ pixel subimages. - <dt>'ImagePartition[$image$, {$w$, $h$}]' + + <dt>'ImagePartition[$image$, {$w$, $h$}]' <dd>Partitions an image into an array of $w$ x $h$ pixel subimages. </dl> @@ -779,16 +781,20 @@ def eval(self, image, w: Integer, h: Integer, evaluation: Evaluation): class ImageAdjust(_ImageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageAdjust.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/ImageAdjust.html</url> <dl> - <dt>'ImageAdjust[$image$]' + <dt>'ImageAdjust[$image$]' <dd>adjusts the levels in $image$. - <dt>'ImageAdjust[$image$, $c$]' + + <dt>'ImageAdjust[$image$, $c$]' <dd>adjusts the contrast in $image$ by $c$. - <dt>'ImageAdjust[$image$, {$c$, $b$}]' + + <dt>'ImageAdjust[$image$, {$c$, $b$}]' <dd>adjusts the contrast $c$, and brightness $b$ in $image$. - <dt>'ImageAdjust[$image$, {$c$, $b$, $g$}]' + + <dt>'ImageAdjust[$image$, {$c$, $b$, $g$}]' <dd>adjusts the contrast $c$, brightness $b$, and gamma $g$ in $image$. </dl> @@ -970,7 +976,9 @@ def eval(self, image, r: Integer, evaluation: Evaluation): class MaxFilter(PillowImageFilter): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/MaxFilter.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/MaxFilter.html</url> <dl> <dt>'MaxFilter[$image$, $r$]' @@ -1670,10 +1678,10 @@ class ImageData(_ImageBuiltin): rules = {"ImageData[image_Image]": 'ImageData[image, "Real"]'} summary_text = "the array of pixel values from an image" - def eval(self, image, stype, evaluation: Evaluation): + def eval(self, image, stype: String, evaluation: Evaluation): "ImageData[image_Image, stype_String]" pixels = image.pixels - stype = stype.get_string_value() + stype = stype.value if stype == "Real": pixels = pixels_as_float(pixels) elif stype == "Byte": @@ -1815,11 +1823,12 @@ class PixelValuePositions(_ImageBuiltin): = {0.25098, 0.0117647, 0.215686} """ - summary_text = "list the position of pixels with a given value" rules = { "PixelValuePositions[image_Image, val_?RealNumberQ]": "PixelValuePositions[image, val, 0]" } + summary_text = "list the position of pixels with a given value" + def eval(self, image, val, d, evaluation: Evaluation): "PixelValuePositions[image_Image, val_?RealNumberQ, d_?RealNumberQ]" val = val.round_to_float() @@ -1849,7 +1858,9 @@ def eval(self, image, val, d, evaluation: Evaluation): class ImageDimensions(_ImageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageDimensions.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ImageDimensions.html</url> <dl> <dt>'ImageDimensions[$image$]' @@ -1904,7 +1915,6 @@ def eval(self, image, evaluation: Evaluation): class ImageChannels(_ImageBuiltin): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/ImageChannels.html</url> @@ -1959,7 +1969,6 @@ def eval(self, image, evaluation: Evaluation): class BinaryImageQ(_ImageTest): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/BinaryImageQ.html</url> @@ -2000,7 +2009,6 @@ def _image_pixels(matrix): class ImageQ(_ImageTest): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageQ.html</url> <dl> @@ -2043,83 +2051,23 @@ def __init__(self, pixels, color_space, metadata={}, **kwargs): self.color_space = color_space self.metadata = metadata - def filter(self, f): # apply PIL filters component-wise - pixels = self.pixels - n = pixels.shape[2] - channels = [ - f(PIL.Image.fromarray(c, "L")) for c in (pixels[:, :, i] for i in range(n)) - ] - return Image(numpy.dstack(channels), self.color_space) - - def pil(self): - # see https://pillow.readthedocs.io/en/stable/handbook/concepts.html - n = self.channels() - - if n == 1: - dtype = self.pixels.dtype - - if dtype in (numpy.float32, numpy.float64): - pixels = self.pixels.astype(numpy.float32) - mode = "F" - elif dtype == numpy.uint32: - pixels = self.pixels - mode = "I" - else: - pixels = pixels_as_ubyte(self.pixels) - mode = "L" - - pixels = pixels.reshape(pixels.shape[:2]) - elif n == 3: - if self.color_space == "LAB": - mode = "LAB" - pixels = self.pixels - elif self.color_space == "HSB": - mode = "HSV" - pixels = self.pixels - elif self.color_space == "RGB": - mode = "RGB" - pixels = self.pixels - else: - mode = "RGB" - pixels = self.color_convert("RGB").pixels - - pixels = pixels_as_ubyte(pixels) - elif n == 4: - if self.color_space == "CMYK": - mode = "CMYK" - pixels = self.pixels - elif self.color_space == "RGB": - mode = "RGBA" - pixels = self.pixels - else: - mode = "RGBA" - pixels = self.color_convert("RGB").pixels - - pixels = pixels_as_ubyte(pixels) - else: - raise NotImplementedError - - return PIL.Image.fromarray(pixels, mode) - - def color_convert(self, to_color_space, preserve_alpha=True): - if to_color_space == self.color_space and preserve_alpha: - return self - else: - pixels = pixels_as_float(self.pixels) - converted = convert_color( - pixels, self.color_space, to_color_space, preserve_alpha + # Set a value for self.__hash__() once so that every time + # it is used this is fast. Note that in contrast to the + # cached object key, the hash key needs to be unique across all + # Python objects, so we include the class in the + # event that different objects have the same Python value + self.hash = hash( + ( + "Image", + self.pixels.tobytes(), + self.color_space, + frozenset(self.metadata.items()), ) - if converted is None: - return None - return Image(converted, to_color_space) - - def grayscale(self): - return self.color_convert("Grayscale") + ) - def atom_to_boxes(self, f, evaluation: Evaluation) -> ImageBox: + def atom_to_boxes(self, form, evaluation: Evaluation) -> ImageBox: """ - Converts our internal Image object into a base64-encode - image PNG. + Converts our internal Image object into a PNG base64-encoded. """ pixels = pixels_as_ubyte(self.color_convert("RGB", True).pixels) shape = pixels.shape @@ -2167,15 +2115,47 @@ def atom_to_boxes(self, f, evaluation: Evaluation) -> ImageBox: Integer(scaled_height), ) + # __hash__ is defined so that we can store Number-derived objects + # in a set or dictionary. + def __hash__(self): + return self.hash + def __str__(self): return "-Image-" - def do_copy(self): - return Image(self.pixels, self.color_space, self.metadata) + def color_convert(self, to_color_space, preserve_alpha=True): + if to_color_space == self.color_space and preserve_alpha: + return self + else: + pixels = pixels_as_float(self.pixels) + converted = convert_color( + pixels, self.color_space, to_color_space, preserve_alpha + ) + if converted is None: + return None + return Image(converted, to_color_space) + + def channels(self): + return self.pixels.shape[2] def default_format(self, evaluation, form): return "-Image-" + def dimensions(self) -> Tuple[int, int]: + shape = self.pixels.shape + return shape[1], shape[0] + + def do_copy(self): + return Image(self.pixels, self.color_space, self.metadata) + + def filter(self, f): # apply PIL filters component-wise + pixels = self.pixels + n = pixels.shape[2] + channels = [ + f(PIL.Image.fromarray(c, "L")) for c in (pixels[:, :, i] for i in range(n)) + ] + return Image(numpy.dstack(channels), self.color_space) + def get_sort_key(self, pattern_sort=False) -> tuple: if pattern_sort: # If pattern_sort=True, returns the sort key that matches to an Atom. @@ -2187,6 +2167,65 @@ def get_sort_key(self, pattern_sort=False) -> tuple: # and a hash in the 6th place. return (1, 3, SymbolImage, len(self.pixels), tuple(), 2, hash(self)) + def grayscale(self): + return self.color_convert("Grayscale") + + def pil(self): + # see https://pillow.readthedocs.io/en/stable/handbook/concepts.html + n = self.channels() + + if n == 1: + dtype = self.pixels.dtype + + if dtype in (numpy.float32, numpy.float64): + pixels = self.pixels.astype(numpy.float32) + mode = "F" + elif dtype == numpy.uint32: + pixels = self.pixels + mode = "I" + else: + pixels = pixels_as_ubyte(self.pixels) + mode = "L" + + pixels = pixels.reshape(pixels.shape[:2]) + elif n == 3: + if self.color_space == "LAB": + mode = "LAB" + pixels = self.pixels + elif self.color_space == "HSB": + mode = "HSV" + pixels = self.pixels + elif self.color_space == "RGB": + mode = "RGB" + pixels = self.pixels + else: + mode = "RGB" + pixels = self.color_convert("RGB").pixels + + pixels = pixels_as_ubyte(pixels) + elif n == 4: + if self.color_space == "CMYK": + mode = "CMYK" + pixels = self.pixels + elif self.color_space == "RGB": + mode = "RGBA" + pixels = self.pixels + else: + mode = "RGBA" + pixels = self.color_convert("RGB").pixels + + pixels = pixels_as_ubyte(pixels) + else: + raise NotImplementedError + + return PIL.Image.fromarray(pixels, mode) + + def options(self): + return ListExpression( + Expression(SymbolRule, String("ColorSpace"), String(self.color_space)), + Expression(SymbolRule, String("MetaInformation"), self.metadata), + ) + def sameQ(self, other) -> bool: """Mathics SameQ""" if not isinstance(other, Image): @@ -2195,26 +2234,6 @@ def sameQ(self, other) -> bool: return False return numpy.array_equal(self.pixels, other.pixels) - def to_python(self, *args, **kwargs): - return self.pixels - - def __hash__(self): - return hash( - ( - "Image", - self.pixels.tobytes(), - self.color_space, - frozenset(self.metadata.items()), - ) - ) - - def dimensions(self) -> Tuple[int, int]: - shape = self.pixels.shape - return shape[1], shape[0] - - def channels(self): - return self.pixels.shape[2] - def storage_type(self): dtype = self.pixels.dtype if dtype in (numpy.float32, numpy.float64): @@ -2230,11 +2249,8 @@ def storage_type(self): else: return str(dtype) - def options(self): - return ListExpression( - Expression(SymbolRule, String("ColorSpace"), String(self.color_space)), - Expression(SymbolRule, String("MetaInformation"), self.metadata), - ) + def to_python(self, *args, **kwargs): + return self.pixels class ImageAtom(AtomBuiltin): @@ -2285,9 +2301,6 @@ class TextRecognize(Builtin): </dl> """ - summary_text = "recognize text in an image" - requires = _image_requires + ("pyocr",) - messages = { "tool": "No text recognition tools were found in the paths available to the Mathics kernel.", "langinv": "No language data for `1` is available.", @@ -2296,6 +2309,10 @@ class TextRecognize(Builtin): options = {"Language": '"English"'} + requires = _image_requires + ("pyocr",) + + summary_text = "recognize text in an image" + def eval(self, image, evaluation, options): "TextRecognize[image_Image, OptionsPattern[%(name)s]]" import pyocr From ef64cc67c19cee6861e8ecc649e99301907e759b Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sun, 25 Dec 2022 01:18:31 -0500 Subject: [PATCH 092/121] Make a ass over summaries --- mathics/builtin/drawing/image.py | 383 +++++++++++++++---------------- 1 file changed, 190 insertions(+), 193 deletions(-) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index a1e98be5c..eebf2499e 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -103,6 +103,27 @@ class _SkimageBuiltin(_ImageBuiltin): # Code related to Mathics Functions that import and export. +class ImageExport(_ImageBuiltin): + """ + <dl> + <dt> 'ImageExport["path", $image$]' + <dd> export $image$ as file in "path". + </dl> + """ + + no_doc = True + + messages = {"noimage": "only an Image[] can be exported into an image file"} + + def eval(self, path: String, expr, opts, evaluation: Evaluation): + """ImageExport[path_String, expr_, opts___]""" + if isinstance(expr, Image): + expr.pil().save(path.value) + return SymbolNull + else: + return evaluation.message("ImageExport", "noimage") + + class ImageImport(_ImageBuiltin): """ <dl> @@ -144,27 +165,6 @@ def eval(self, path: String, evaluation: Evaluation): return ListExpression(*image_list_expression) -class ImageExport(_ImageBuiltin): - """ - <dl> - <dt> 'ImageExport["path", $image$]' - <dd> export $image$ as file in "path". - </dl> - """ - - no_doc = True - - messages = {"noimage": "only an Image[] can be exported into an image file"} - - def eval(self, path: String, expr, opts, evaluation: Evaluation): - """ImageExport[path_String, expr_, opts___]""" - if isinstance(expr, Image): - expr.pil().save(path.value) - return SymbolNull - else: - return evaluation.message("ImageExport", "noimage") - - # image math @@ -250,67 +250,67 @@ class ImageAdd(_ImageArithmetic): summary_text = "build an image adding pixel values of another image " -class ImageSubtract(_ImageArithmetic): +class ImageMultiply(_ImageArithmetic): """ - <url>:WMA link: - https://reference.wolfram.com/language/ref/ImageSubtract.html</url> + <url>:WMA link:https://reference.wolfram.com/language/ref/ImageMultiply.html</url> <dl> - <dt>'ImageSubtract[$image$, $expr_1$, $expr_2$, ...]' - <dd>subtracts all $expr_i$ from $image$ where each $expr_i$ must be an \ - image or a real number. + <dt>'ImageMultiply[$image$, $expr_1$, $expr_2$, ...]' + <dd>multiplies all $expr_i$ with $image$ where each $expr_i$ must be an image or a real number. </dl> >> i = Image[{{0, 0.5, 0.2, 0.1, 0.9}, {1.0, 0.1, 0.3, 0.8, 0.6}}]; - >> ImageSubtract[i, 0.2] + >> ImageMultiply[i, 0.2] = -Image- - >> ImageSubtract[i, i] + >> ImageMultiply[i, i] = -Image- - #> ImageSubtract[i, 0.2, i, 0.1] + #> ImageMultiply[i, 0.2, i, 0.1] = -Image- - #> ImageSubtract[i, x] + #> ImageMultiply[i, x] : Expecting a number, image, or graphics instead of x. - = ImageSubtract[-Image-, x] + = ImageMultiply[-Image-, x] + + S> ein = Import["ExampleData/Einstein.jpg"]; + S> noise = RandomImage[{0.7, 1.3}, ImageDimensions[ein]]; + S> ImageMultiply[noise, ein] + = -Image- """ - summary_text = "build an image substracting pixel values of another image " + summary_text = "build an image multiplying the pixel values of another image " -class ImageMultiply(_ImageArithmetic): +class ImageSubtract(_ImageArithmetic): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageMultiply.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/ImageSubtract.html</url> <dl> - <dt>'ImageMultiply[$image$, $expr_1$, $expr_2$, ...]' - <dd>multiplies all $expr_i$ with $image$ where each $expr_i$ must be an image or a real number. + <dt>'ImageSubtract[$image$, $expr_1$, $expr_2$, ...]' + <dd>subtracts all $expr_i$ from $image$ where each $expr_i$ must be an \ + image or a real number. </dl> >> i = Image[{{0, 0.5, 0.2, 0.1, 0.9}, {1.0, 0.1, 0.3, 0.8, 0.6}}]; - >> ImageMultiply[i, 0.2] + >> ImageSubtract[i, 0.2] = -Image- - >> ImageMultiply[i, i] + >> ImageSubtract[i, i] = -Image- - #> ImageMultiply[i, 0.2, i, 0.1] + #> ImageSubtract[i, 0.2, i, 0.1] = -Image- - #> ImageMultiply[i, x] + #> ImageSubtract[i, x] : Expecting a number, image, or graphics instead of x. - = ImageMultiply[-Image-, x] - - S> ein = Import["ExampleData/Einstein.jpg"]; - S> noise = RandomImage[{0.7, 1.3}, ImageDimensions[ein]]; - S> ImageMultiply[noise, ein] - = -Image- + = ImageSubtract[-Image-, x] """ - summary_text = "build an image multiplying the pixel values of another image " + summary_text = "build an image substracting pixel values of another image " class RandomImage(_ImageBuiltin): @@ -868,7 +868,7 @@ class Blur(_ImageBuiltin): = -Image- """ - summary_text = "blurred version of an image" + summary_text = "blur an image" rules = { "Blur[image_Image]": "Blur[image, 2]", "Blur[image_Image, r_?RealNumberQ]": "ImageConvolve[image, BoxMatrix[r] / Total[Flatten[BoxMatrix[r]]]]", @@ -1081,7 +1081,7 @@ class BoxMatrix(_ImageBuiltin): = {{1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}} """ - summary_text = "a matrix with all its entries set to 1" + summary_text = "create a matrix with all its entries set to 1" def eval(self, r, evaluation: Evaluation): "BoxMatrix[r_?RealNumberQ]" @@ -1103,7 +1103,7 @@ class DiskMatrix(_ImageBuiltin): = {{0, 0, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 1, 1, 0}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {0, 1, 1, 1, 1, 1, 0}, {0, 0, 1, 1, 1, 0, 0}} """ - summary_text = "a matrix with 1 in a disk-shaped region, and 0 outside" + summary_text = "create a matrix with 1 in a disk-shaped region, and 0 outside" def eval(self, r, evaluation: Evaluation): "DiskMatrix[r_?RealNumberQ]" @@ -1134,7 +1134,7 @@ class DiamondMatrix(_ImageBuiltin): = {{0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 1, 1, 0}, {1, 1, 1, 1, 1, 1, 1}, {0, 1, 1, 1, 1, 1, 0}, {0, 0, 1, 1, 1, 0, 0}, {0, 0, 0, 1, 0, 0, 0}} """ - summary_text = "a matrix with 1 in a diamond-shaped region, and 0 outside" + summary_text = "create a matrix with 1 in a diamond-shaped region, and 0 outside" def eval(self, r, evaluation: Evaluation): "DiamondMatrix[r_?RealNumberQ]" @@ -1258,7 +1258,7 @@ class Opening(_MorphologyFilter): = -Image- """ - summary_text = "morphological opening regarding a kernel" + summary_text = "get morphological opening regarding a kernel" class Closing(_MorphologyFilter): @@ -1784,7 +1784,7 @@ class PixelValue(_ImageBuiltin): messages = {"nopad": "Padding not implemented for PixelValue."} - summary_text = "pixel value of image at a given position" + summary_text = "get pixel value of image at a given position" def eval(self, image, x, y, evaluation: Evaluation): "PixelValue[image_Image, {x_?RealNumberQ, y_?RealNumberQ}]" @@ -1880,7 +1880,7 @@ class ImageDimensions(_ImageBuiltin): = {2, 3} """ - summary_text = "pixel dimensions of the raster associated with an image" + summary_text = "get pixel dimensions of the raster associated with an image" def eval(self, image, evaluation: Evaluation): "ImageDimensions[image_Image]" @@ -1931,7 +1931,7 @@ class ImageChannels(_ImageBuiltin): = 3 """ - summary_text = "number of channels present in the data for an image" + summary_text = "get number of channels present in the data for an image" def eval(self, image, evaluation: Evaluation): "ImageChannels[image_Image]" @@ -2271,7 +2271,7 @@ class ImageAtom(AtomBuiltin): = -Image- """ - summary_text = "internal representation of an image" + summary_text = "get internal representation of an image" requires = _image_requires def eval_create(self, array, evaluation: Evaluation): @@ -2359,167 +2359,164 @@ def eval(self, image, evaluation, options): return String(text) -import sys +class WordCloud(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/WordCloud.html</url> -if "Pyston" not in sys.version: + <dl> + <dt>'WordCloud[{$word1$, $word2$, ...}]' + <dd>Gives a word cloud with the given list of words. - class WordCloud(Builtin): - """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/WordCloud.html</url> + <dt>'WordCloud[{$weight1$ -> $word1$, $weight2$ -> $word2$, ...}]' + <dd>Gives a word cloud with the words weighted using the given weights. - <dl> - <dt>'WordCloud[{$word1$, $word2$, ...}]' - <dd>Gives a word cloud with the given list of words. + <dt>'WordCloud[{$weight1$, $weight2$, ...} -> {$word1$, $word2$, ...}]' + <dd>Also gives a word cloud with the words weighted using the given weights. - <dt>'WordCloud[{$weight1$ -> $word1$, $weight2$ -> $word2$, ...}]' - <dd>Gives a word cloud with the words weighted using the given weights. + <dt>'WordCloud[{{$word1$, $weight1$}, {$word2$, $weight2$}, ...}]' + <dd>Gives a word cloud with the words weighted using the given weights. + </dl> - <dt>'WordCloud[{$weight1$, $weight2$, ...} -> {$word1$, $word2$, ...}]' - <dd>Also gives a word cloud with the words weighted using the given weights. + >> WordCloud[StringSplit[Import["ExampleData/EinsteinSzilLetter.txt", CharacterEncoding->"UTF8"]]] + = -Image- - <dt>'WordCloud[{{$word1$, $weight1$}, {$word2$, $weight2$}, ...}]' - <dd>Gives a word cloud with the words weighted using the given weights. - </dl> + >> WordCloud[Range[50] -> ToString /@ Range[50]] + = -Image- + """ - >> WordCloud[StringSplit[Import["ExampleData/EinsteinSzilLetter.txt", CharacterEncoding->"UTF8"]]] - = -Image- + # this is the palettable.colorbrewer.qualitative.Dark2_8 palette + default_colors = ( + (27, 158, 119), + (217, 95, 2), + (117, 112, 179), + (231, 41, 138), + (102, 166, 30), + (230, 171, 2), + (166, 118, 29), + (102, 102, 102), + ) + + options = { + "IgnoreCase": "True", + "ImageSize": "Automatic", + "MaxItems": "Automatic", + } - >> WordCloud[Range[50] -> ToString /@ Range[50]] - = -Image- - """ + requires = _image_requires + ("wordcloud",) - summary_text = "a word cloud from a list of words" - requires = _image_requires + ("wordcloud",) + summary_text = "show a word cloud from a list of words" - options = { - "IgnoreCase": "True", - "ImageSize": "Automatic", - "MaxItems": "Automatic", - } + def eval_words_weights(self, weights, words, evaluation, options): + "WordCloud[weights_List -> words_List, OptionsPattern[%(name)s]]" + if len(weights.elements) != len(words.elements): + return - # this is the palettable.colorbrewer.qualitative.Dark2_8 palette - default_colors = ( - (27, 158, 119), - (217, 95, 2), - (117, 112, 179), - (231, 41, 138), - (102, 166, 30), - (230, 171, 2), - (166, 118, 29), - (102, 102, 102), - ) + def weights_and_words(): + for weight, word in zip(weights.elements, words.elements): + yield weight.round_to_float(), word.get_string_value() - def eval_words_weights(self, weights, words, evaluation, options): - "WordCloud[weights_List -> words_List, OptionsPattern[%(name)s]]" - if len(weights.elements) != len(words.elements): - return + return self._word_cloud(weights_and_words(), evaluation, options) - def weights_and_words(): - for weight, word in zip(weights.elements, words.elements): - yield weight.round_to_float(), word.get_string_value() + def eval_words(self, words, evaluation, options): + "WordCloud[words_List, OptionsPattern[%(name)s]]" - return self._word_cloud(weights_and_words(), evaluation, options) + if not words: + return + elif isinstance(words.elements[0], String): - def eval_words(self, words, evaluation, options): - "WordCloud[words_List, OptionsPattern[%(name)s]]" + def weights_and_words(): + for word in words.elements: + yield 1, word.get_string_value() - if not words: - return - elif isinstance(words.elements[0], String): + else: - def weights_and_words(): - for word in words.elements: - yield 1, word.get_string_value() + def weights_and_words(): + for word in words.elements: + if len(word.elements) != 2: + raise ValueError - else: + head_name = word.get_head_name() + if head_name == "System`Rule": + weight, s = word.elements + elif head_name == "System`List": + s, weight = word.elements + else: + raise ValueError - def weights_and_words(): - for word in words.elements: - if len(word.elements) != 2: - raise ValueError + yield weight.round_to_float(), s.get_string_value() - head_name = word.get_head_name() - if head_name == "System`Rule": - weight, s = word.elements - elif head_name == "System`List": - s, weight = word.elements - else: - raise ValueError + try: + return self._word_cloud(weights_and_words(), evaluation, options) + except ValueError: + return - yield weight.round_to_float(), s.get_string_value() + def _word_cloud(self, words, evaluation, options): + ignore_case = self.get_option(options, "IgnoreCase", evaluation) is Symbol( + "True" + ) - try: - return self._word_cloud(weights_and_words(), evaluation, options) - except ValueError: + freq = defaultdict(int) + for py_weight, py_word in words: + if py_word is None or py_weight is None: return + key = py_word.lower() if ignore_case else py_word + freq[key] += py_weight - def _word_cloud(self, words, evaluation, options): - ignore_case = self.get_option(options, "IgnoreCase", evaluation) is Symbol( - "True" - ) - - freq = defaultdict(int) - for py_weight, py_word in words: - if py_word is None or py_weight is None: + max_items = self.get_option(options, "MaxItems", evaluation) + if isinstance(max_items, Integer): + py_max_items = max_items.get_int_value() + else: + py_max_items = 200 + + image_size = self.get_option(options, "ImageSize", evaluation) + if image_size is Symbol("Automatic"): + py_image_size = (800, 600) + elif ( + image_size.get_head_name() == "System`List" + and len(image_size.elements) == 2 + ): + py_image_size = [] + for element in image_size.elements: + if not isinstance(element, Integer): return - key = py_word.lower() if ignore_case else py_word - freq[key] += py_weight - - max_items = self.get_option(options, "MaxItems", evaluation) - if isinstance(max_items, Integer): - py_max_items = max_items.get_int_value() - else: - py_max_items = 200 - - image_size = self.get_option(options, "ImageSize", evaluation) - if image_size is Symbol("Automatic"): - py_image_size = (800, 600) - elif ( - image_size.get_head_name() == "System`List" - and len(image_size.elements) == 2 - ): - py_image_size = [] - for element in image_size.elements: - if not isinstance(element, Integer): - return - py_image_size.append(element.get_int_value()) - elif isinstance(image_size, Integer): - size = image_size.get_int_value() - py_image_size = (size, size) - else: - return + py_image_size.append(element.get_int_value()) + elif isinstance(image_size, Integer): + size = image_size.get_int_value() + py_image_size = (size, size) + else: + return - # inspired by http://minimaxir.com/2016/05/wordclouds/ - import random + # inspired by http://minimaxir.com/2016/05/wordclouds/ + import random - def color_func( - word, font_size, position, orientation, random_state=None, **kwargs - ): - return self.default_colors[random.randint(0, 7)] - - font_base_path = osp.join(osp.dirname(osp.abspath(__file__)), "..", "fonts") - - font_path = osp.realpath(font_base_path + "AmaticSC-Bold.ttf") - if not osp.exists(font_path): - font_path = None - - from wordcloud import WordCloud - - wc = WordCloud( - width=py_image_size[0], - height=py_image_size[1], - font_path=font_path, - max_font_size=300, - mode="RGB", - background_color="white", - max_words=py_max_items, - color_func=color_func, - random_state=42, - stopwords=set(), - ) - wc.generate_from_frequencies(freq) + def color_func( + word, font_size, position, orientation, random_state=None, **kwargs + ): + return self.default_colors[random.randint(0, 7)] + + font_base_path = osp.join(osp.dirname(osp.abspath(__file__)), "..", "fonts") + + font_path = osp.realpath(font_base_path + "AmaticSC-Bold.ttf") + if not osp.exists(font_path): + font_path = None + + from wordcloud import WordCloud + + wc = WordCloud( + width=py_image_size[0], + height=py_image_size[1], + font_path=font_path, + max_font_size=300, + mode="RGB", + background_color="white", + max_words=py_max_items, + color_func=color_func, + random_state=42, + stopwords=set(), + ) + wc.generate_from_frequencies(freq) - image = wc.to_image() - return Image(numpy.array(image), "RGB") + image = wc.to_image() + return Image(numpy.array(image), "RGB") From 990b4c07e7a61311826715c597063e8ce3757239 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sun, 25 Dec 2022 02:23:39 -0500 Subject: [PATCH 093/121] Make a pass over importexport --- SYMBOLS_MANIFEST.txt | 1 - mathics/builtin/files_io/importexport.py | 259 +++++++++++------------ 2 files changed, 121 insertions(+), 139 deletions(-) diff --git a/SYMBOLS_MANIFEST.txt b/SYMBOLS_MANIFEST.txt index 692493094..ef4f558e4 100644 --- a/SYMBOLS_MANIFEST.txt +++ b/SYMBOLS_MANIFEST.txt @@ -245,7 +245,6 @@ System`Continue System`ContinuedFraction System`Convert`B64Dump`B64Decode System`Convert`B64Dump`B64Encode -System`Convert`CommonDump`RemoveLinearSyntax System`ConvertersDump`$extensionMappings System`ConvertersDump`$formatMappings System`CoprimeQ diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index e9815aa0d..8dc96433a 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -2,12 +2,15 @@ """ Importing and Exporting + +Many kinds data formats can be read into \Mathics . """ # This tells documentation how to sort this module # Here we are also hiding "file_io" since this can erroneously appear at the top level. sort_order = "mathics.builtin.importing-and-exporting" +import base64 import mimetypes import os import sys @@ -21,6 +24,7 @@ from mathics.core.attributes import A_NO_ATTRIBUTES, A_PROTECTED, A_READ_PROTECTED from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_python +from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.streams import stream_manager @@ -945,10 +949,10 @@ class ImportFormats(Predefined): = {...CSV,...JSON,...Text...} """ - summary_text = "list supported import formats" name = "$ImportFormats" + summary_text = "list supported import formats" - def evaluate(self, evaluation): + def evaluate(self, evaluation: Evaluation): return to_mathics_list(*sorted(IMPORTERS.keys()), elements_conversion_fn=String) @@ -968,7 +972,7 @@ class ExportFormats(Predefined): name = "$ExportFormats" summary_text = "list supported export formats" - def evaluate(self, evaluation): + def evaluate(self, evaluation: Evaluation): return to_mathics_list(*sorted(EXPORTERS.keys()), elements_conversion_fn=String) @@ -977,8 +981,8 @@ class ConverterDumpsExtensionMappings(Predefined): ## <url>:internal native symbol:</url> <dl> - <dt>'$extensionMappings' - <dd>Returns a list of associations between file extensions and file types. + <dt>'$extensionMappings' + <dd>Returns a list of associations between file extensions and file types. </dl> """ @@ -987,7 +991,7 @@ class ConverterDumpsExtensionMappings(Predefined): name = "$extensionMappings" attributes = A_NO_ATTRIBUTES - def evaluate(self, evaluation): + def evaluate(self, evaluation: Evaluation): return from_python(EXTENSIONMAPPINGS) @@ -1001,27 +1005,31 @@ class ConverterDumpsFormatMappings(Predefined): </dl> """ - summary_text = "associations between file extensions and file types" + attributes = A_NO_ATTRIBUTES context = "System`ConvertersDump`" # TODO: Check why this does not follows the convention of # starting words in identifiers with caps. name = "$formatMappings" - attributes = A_NO_ATTRIBUTES + summary_text = "associations between file extensions and file types" - def evaluate(self, evaluation): + def evaluate(self, evaluation: Evaluation): return from_python(FORMATMAPPINGS) class RegisterImport(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/RegisterImport.html</url> + <url>: + WMA link: + https://reference.wolfram.com/language/ref/RegisterImport.html</url> <dl> - <dt>'RegisterImport["$format$", $defaultFunction$]' + <dt>'RegisterImport["$format$", $defaultFunction$]' <dd>register '$defaultFunction$' as the default function used when importing from a file of type '"$format$"'. - <dt>'RegisterImport["$format$", {"$elem1$" :> $conditionalFunction1$, "$elem2$" :> $conditionalFunction2$, ..., $defaultFunction$}]' + + <dt>'RegisterImport["$format$", {"$elem1$" :> $conditionalFunction1$, "$elem2$" :> $conditionalFunction2$, ..., $defaultFunction$}]' <dd>registers multiple elements ($elem1$, ...) and their corresponding converter functions ($conditionalFunction1$, ...) in addition to the $defaultFunction$. - <dt>'RegisterImport["$format$", {"$conditionalFunctions$, $defaultFunction$, "$elem3$" :> $postFunction3$, "$elem4$" :> $postFunction4$, ...}]' + + <dt>'RegisterImport["$format$", {"$conditionalFunctions$, $defaultFunction$, "$elem3$" :> $postFunction3$, "$elem4$" :> $postFunction4$, ...}]' <dd>also registers additional elements ($elem3$, ...) whose converters ($postFunction3$, ...) act on output from the low-level funcions. </dl> @@ -1087,31 +1095,31 @@ class RegisterImport(Builtin): """ - summary_text = "Register an importer for a file format" context = "ImportExport`" attributes = A_PROTECTED | A_READ_PROTECTED # XXX OptionsIssue options = { - "Path": "Automatic", - "FunctionChannels": '{"FileNames"}', - "Sources": "None", - "DefaultElement": "Automatic", + "AlphaChannel": "False", "AvailableElements": "None", - "Options": "{}", - "OriginalChannel": "False", "BinaryFormat": "False", + "DefaultElement": "Automatic", "Encoding": "False", "Extensions": "{}", - "AlphaChannel": "False", + "FunctionChannels": '{"FileNames"}', + "Options": "{}", + "OriginalChannel": "False", + "Path": "Automatic", + "Sources": "None", } rules = { "ImportExport`RegisterImport[formatname_String, function_]": "ImportExport`RegisterImport[formatname, function, {}]", } + summary_text = "Register an importer for a file format" - def apply(self, formatname, function, posts, evaluation, options): + def eval(self, formatname, function, posts, evaluation: Evaluation, options): """ImportExport`RegisterImport[formatname_String, function_, posts_, OptionsPattern[ImportExport`RegisterImport]]""" @@ -1147,10 +1155,12 @@ def apply(self, formatname, function, posts, evaluation, options): class RegisterExport(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/RegisterExport.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/RegisterExport.html</url> <dl> - <dt>'RegisterExport["$format$", $func$]' + <dt>'RegisterExport["$format$", $func$]' <dd>register '$func$' as the default function used when exporting from a file of type '"$format$"'. </dl> @@ -1196,20 +1206,22 @@ class RegisterExport(Builtin): "AlphaChannel": "False", } - def apply(self, formatname, function, evaluation, options): + def eval(self, formatname: String, function, evaluation: Evaluation, options): """ImportExport`RegisterExport[formatname_String, function_, OptionsPattern[ImportExport`RegisterExport]]""" - EXPORTERS[formatname.get_string_value()] = (function, options) + EXPORTERS[formatname.value] = (function, options) return SymbolNull class URLFetch(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/URLFetch.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/URLFetch.html</url> <dl> - <dt>'URLFetch[$URL$]' + <dt>'URLFetch[$URL$]' <dd> Returns the content of $URL$ as a string. </dl> @@ -1226,7 +1238,7 @@ class URLFetch(Builtin): "httperr": "`1` could not be retrieved; `2`.", } - def apply(self, url, elements, evaluation, options={}): + def eval(self, url: String, elements, evaluation: Evaluation, options={}): "URLFetch[url_String, elements_, OptionsPattern[]]" import os @@ -1360,17 +1372,17 @@ class Import(Builtin): summary_text = "import elements from a file" - def apply(self, filename, evaluation, options={}): + def eval(self, filename, evaluation, options={}): "Import[filename_, OptionsPattern[]]" - return self.apply_elements(filename, ListExpression(), evaluation, options) + return self.eval_elements(filename, ListExpression(), evaluation, options) - def apply_element(self, filename, element, evaluation, options={}): + def eval_element(self, filename, element: String, evaluation, options={}): "Import[filename_, element_String, OptionsPattern[]]" - return self.apply_elements( + return self.eval_elements( filename, ListExpression(element), evaluation, options ) - def apply_elements(self, filename, elements, evaluation, options={}): + def eval_elements(self, filename, elements, evaluation, options={}): "Import[filename_, elements_List?(AllTrue[#, NotOptionQ]&), OptionsPattern[]]" # Check filename path = filename.to_python() @@ -1578,7 +1590,9 @@ def get_results(tmp_function, findfile): class ImportString(Import): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImportString.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ImportString.html</url> <dl> <dt>'ImportString["$data$", "$format$"]' @@ -1619,33 +1633,32 @@ class ImportString(Import): = ... """ - summary_text = "import elements from a string" messages = { "string": "First argument `1` is not a string.", "noelem": ("The Import element `1` is not present when importing as `2`."), "fmtnosup": "`1` is not a supported Import format.", } - - rules = {} - options = { "$OptionSyntax": "System`Ignore", } + rules = {} + summary_text = "import elements from a string" - def apply(self, data, evaluation, options={}): + def eval(self, data, evaluation, options={}): "ImportString[data_, OptionsPattern[]]" - return self.apply_elements(data, ListExpression(), evaluation, options) + return self.eval_elements(data, ListExpression(), evaluation, options) - def apply_element(self, data, element, evaluation, options={}): + def eval_element(self, data, element: String, evaluation, options={}): "ImportString[data_, element_String, OptionsPattern[]]" - return self.apply_elements(data, ListExpression(element), evaluation, options) + return self.eval_elements(data, ListExpression(element), evaluation, options) - def apply_elements(self, data, elements, evaluation, options={}): + def eval_elements(self, data, elements, evaluation, options={}): "ImportString[data_, elements_List?(AllTrue[#, NotOptionQ]&), OptionsPattern[]]" if not (isinstance(data, String)): evaluation.message("ImportString", "string", data) return SymbolFailed + path = data.value def determine_filetype(): if not FileFormat.detector: @@ -1659,7 +1672,7 @@ def determine_filetype(): if key in mime: result.append(mimetype_dict[key]) - # the following fixes an extremely annoying behaviour on some (not all) + # The following fixes an extremely annoying behaviour on some (not all) # installations of Windows, where we end up classifying .csv files als XLS. if ( len(result) == 1 @@ -1781,7 +1794,21 @@ class Export(Builtin): summary_text = "export elements to a file" - def apply(self, filename, expr, evaluation, options={}): + def _check_filename(self, filename, evaluation: Evaluation): + path = filename.to_python() + if isinstance(path, str) and path[0] == path[-1] == '"': + return True + evaluation.message("Export", "chtype", filename) + return False + + def _infer_form(self, filename, evaluation: Evaluation): + ext = Expression(SymbolFileExtension, filename).evaluate(evaluation) + ext = ext.get_string_value().lower() + # TODO: This dictionary should be accesible from the WL API + # to allow defining specific converters + return self._extdict.get(ext) + + def eval(self, filename, expr, evaluation, options={}): "Export[filename_, expr_, OptionsPattern[Export]]" # Check filename @@ -1795,17 +1822,15 @@ def apply(self, filename, expr, evaluation, options={}): evaluation.message("Export", "infer", filename) return SymbolFailed else: - return self.apply_elements( - filename, expr, String(form), evaluation, options - ) + return self.eval_elements(filename, expr, String(form), evaluation, options) - def apply_element(self, filename, expr, element, evaluation, options={}): + def eval_element(self, filename, expr, element: String, evaluation, options={}): "Export[filename_, expr_, element_String, OptionsPattern[]]" - return self.apply_elements( + return self.eval_elements( filename, expr, ListExpression(element), evaluation, options ) - def apply_elements(self, filename, expr, elems, evaluation, options={}): + def eval_elements(self, filename, expr, elems, evaluation, options={}): "Export[filename_, expr_, elems_List?(AllTrue[#, NotOptionQ]&), OptionsPattern[]]" # Check filename @@ -1891,20 +1916,6 @@ def apply_elements(self, filename, expr, elems, evaluation, options={}): evaluation.predetermined_out = current_predetermined_out return SymbolFailed - def _check_filename(self, filename, evaluation): - path = filename.to_python() - if isinstance(path, str) and path[0] == path[-1] == '"': - return True - evaluation.message("Export", "chtype", filename) - return False - - def _infer_form(self, filename, evaluation): - ext = Expression(SymbolFileExtension, filename).evaluate(evaluation) - ext = ext.get_string_value().lower() - # TODO: This dictionary should be accesible from the WL API - # to allow defining specific converters - return self._extdict.get(ext) - class ExportString(Builtin): """ @@ -1933,25 +1944,25 @@ class ExportString(Builtin): = String """ - summary_text = "export elements to a string" - options = { - "$OptionSyntax": "System`Ignore", - } - messages = { "noelem": "`1` is not a valid set of export elements for the `2` format.", "emptyfch": "Function Channel not defined.", } + options = { + "$OptionSyntax": "System`Ignore", + } + rules = { "ExportString[expr_, elems_?NotListQ]": ("ExportString[expr, {elems}]"), } + summary_text = "export elements to a string" - def apply_element(self, expr, element, evaluation, **options): + def eval_element(self, expr, element: String, evaluation: Evaluation, **options): "ExportString[expr_, element_String, OptionsPattern[ExportString]]" - return self.apply_elements(expr, ListExpression(element), evaluation, **options) + return self.eval_elements(expr, ListExpression(element), evaluation, **options) - def apply_elements(self, expr, elems, evaluation, **options): + def eval_elements(self, expr, elems, evaluation: Evaluation, **options): "ExportString[expr_, elems_List?(AllTrue[#, NotOptionQ]&), OptionsPattern[ExportString]]" # Process elems {comp* format?, elem1*} elements = elems.get_elements() @@ -2154,7 +2165,7 @@ class FileFormat(Builtin): detector = None - def apply(self, filename, evaluation): + def eval(self, filename: String, evaluation: Evaluation): "FileFormat[filename_String]" findfile = Expression(SymbolFindFile, filename).evaluate(evaluation) @@ -2164,7 +2175,7 @@ def apply(self, filename, evaluation): ) return findfile - path = findfile.get_string_value() + path = findfile.value if not FileFormat.detector: loader = magic.MagicLoader() loader.load() @@ -2199,47 +2210,6 @@ def apply(self, filename, evaluation): return from_python(result) -import base64 - - -class B64Encode(Builtin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/B64Encode.html</url> - - <dl> - <dt> 'System`Convert`B64Dump`B64Encode[$expr$]' - <dd>Encodes $expr$ in Base64 coding - </dl> - - >> System`Convert`B64Dump`B64Encode["Hello world"] - = SGVsbG8gd29ybGQ= - >> System`Convert`B64Dump`B64Decode[%] - = Hello world - >> System`Convert`B64Dump`B64Encode[Integrate[f[x],{x,0,2}]] - = SW50ZWdyYXRlW2ZbeF0sIHt4LCAwLCAyfV0= - >> System`Convert`B64Dump`B64Decode[%] - = Integrate[f[x], {x, 0, 2}] - """ - - summary_text = "encode an element as a base64 string" - context = "System`Convert`B64Dump`" - name = "B64Encode" - - def apply(self, expr, evaluation): - "System`Convert`B64Dump`B64Encode[expr_]" - if isinstance(expr, String): - stringtocodify = expr.get_string_value() - elif expr.get_head_name() == "System`ByteArray": - return String(expr._elements[0].__str__()) - else: - stringtocodify = ( - Expression(SymbolToString, expr).evaluate(evaluation).get_string_value() - ) - return String( - base64.b64encode(bytearray(stringtocodify, "utf8")).decode("utf8") - ) - - class B64Decode(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/B64Decode.html</url> @@ -2261,12 +2231,10 @@ class B64Decode(Builtin): "b64invalidstr": 'String "`1`" is not a valid b64 encoded string.', } - def apply(self, expr, evaluation): + def eval(self, expr: String, evaluation: Evaluation): "System`Convert`B64Dump`B64Decode[expr_String]" try: - clearstring = base64.b64decode( - bytearray(expr.get_string_value(), "utf8") - ).decode("utf8") + clearstring = base64.b64decode(bytearray(expr.value, "utf8")).decode("utf8") clearstring = String(str(clearstring)) except Exception: evaluation.message( @@ -2276,26 +2244,41 @@ def apply(self, expr, evaluation): return clearstring -class ConvertCommonDumpRemoveLinearSyntax(Builtin): +class B64Encode(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ConvertCommonDumpRemoveLinearSyntax.html</url> + <url> + :WMA link + :https://reference.wolfram.com/language/ref/B64Encode.html</url> <dl> - <dt> 'System`Convert`CommonDump`RemoveLinearSyntax[$something$]' - <dd> Keine anung... Undocumented in wma + <dt> 'System`Convert`B64Dump`B64Encode[$expr$]' + <dd>Encodes $expr$ in Base64 coding </dl> + + >> System`Convert`B64Dump`B64Encode["Hello world"] + = SGVsbG8gd29ybGQ= + >> System`Convert`B64Dump`B64Decode[%] + = Hello world + >> System`Convert`B64Dump`B64Encode[Integrate[f[x],{x,0,2}]] + = SW50ZWdyYXRlW2ZbeF0sIHt4LCAwLCAyfV0= + >> System`Convert`B64Dump`B64Decode[%] + = Integrate[f[x], {x, 0, 2}] """ - summary_text = "document me..." - options = { - "System`Convert`CommonDump`ConvertRecursive": "False", - } - # options = {"ConvertRecursive" : "False", } - attributes = A_READ_PROTECTED | A_PROTECTED - context = "System`Convert`CommonDump`" - name = "RemoveLinearSyntax" - - def apply(self, arg, evaluation): - "System`Convert`CommonDump`RemoveLinearSyntax[arg_]" - print("No idea what should this do. By now, do nothing...") - return arg + context = "System`Convert`B64Dump`" + name = "B64Encode" + summary_text = "encode an element as a base64 string" + + def eval(self, expr, evaluation: Evaluation): + "System`Convert`B64Dump`B64Encode[expr_]" + if isinstance(expr, String): + stringtocodify = expr.value + elif expr.get_head_name() == "System`ByteArray": + return String(expr._elements[0].__str__()) + else: + stringtocodify = ( + Expression(SymbolToString, expr).evaluate(evaluation).get_string_value() + ) + return String( + base64.b64encode(bytearray(stringtocodify, "utf8")).decode("utf8") + ) From c995fcafa335e6696bb84bce0b19ab9f6f130bab Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sun, 25 Dec 2022 03:13:09 -0500 Subject: [PATCH 094/121] Mark some more internal functions --- mathics/builtin/files_io/importexport.py | 47 +++++++++++++++--------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py index 8dc96433a..a50f8a821 100644 --- a/mathics/builtin/files_io/importexport.py +++ b/mathics/builtin/files_io/importexport.py @@ -3,7 +3,18 @@ """ Importing and Exporting -Many kinds data formats can be read into \Mathics . +Many kinds data formats can be read into \Mathics. Variable <url> +:$ExportFormats: +/doc/reference-of-built-in-symbols/importing-and-exporting/$exportformats</url> \ +contains a list of file formats that are supported by <url> +:Export: +/doc/reference-of-built-in-symbols/importing-and-exporting/export</url>, \ +while <url> +:$InputFormats: +/doc/reference-of-built-in-symbols/importing-and-exporting/$inputformats</url> \ +does the corresponding thing for <url> +:Import: +/doc/reference-of-built-in-symbols/importing-and-exporting/import</url>. """ # This tells documentation how to sort this module @@ -981,15 +992,18 @@ class ConverterDumpsExtensionMappings(Predefined): ## <url>:internal native symbol:</url> <dl> - <dt>'$extensionMappings' + <dt>'System`ConvertersDump`$ExtensionMappings' <dd>Returns a list of associations between file extensions and file types. </dl> + + >> System`ConvertersDump`$ExtensionMappings + = ... """ - summary_text = "associations between file extensions and file types" - context = "System`ConvertersDump`" - name = "$extensionMappings" attributes = A_NO_ATTRIBUTES + context = "System`ConvertersDump`" + name = "$ExtensionMappings" + summary_text = "get associations file extensions and their abstract file type" def evaluate(self, evaluation: Evaluation): return from_python(EXTENSIONMAPPINGS) @@ -1000,17 +1014,20 @@ class ConverterDumpsFormatMappings(Predefined): ## <url>:internal native symbol:</url> <dl> - <dt>'$formatMappings' - <dd>Returns a list of associations between file extensions and file types. + <dt>'System`ConverterDump$FormatMappings' + <dd>Returns a list of associations between file extensions and file types. </dl> + + >> System`ConvertersDump`$FormatMappings + = ... """ attributes = A_NO_ATTRIBUTES context = "System`ConvertersDump`" # TODO: Check why this does not follows the convention of # starting words in identifiers with caps. - name = "$formatMappings" - summary_text = "associations between file extensions and file types" + name = "$FormatMappings" + summary_text = "get associations between mime types their abstract file type" def evaluate(self, evaluation: Evaluation): return from_python(FORMATMAPPINGS) @@ -1018,9 +1035,7 @@ def evaluate(self, evaluation: Evaluation): class RegisterImport(Builtin): """ - <url>: - WMA link: - https://reference.wolfram.com/language/ref/RegisterImport.html</url> + ## <url>:internal native symbol:</url> <dl> <dt>'RegisterImport["$format$", $defaultFunction$]' @@ -1117,7 +1132,7 @@ class RegisterImport(Builtin): rules = { "ImportExport`RegisterImport[formatname_String, function_]": "ImportExport`RegisterImport[formatname, function, {}]", } - summary_text = "Register an importer for a file format" + summary_text = "register an importer for a file format" def eval(self, formatname, function, posts, evaluation: Evaluation, options): """ImportExport`RegisterImport[formatname_String, function_, posts_, @@ -1155,9 +1170,7 @@ def eval(self, formatname, function, posts, evaluation: Evaluation, options): class RegisterExport(Builtin): """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/RegisterExport.html</url> + ## <url>:internal native symbol:</url> <dl> <dt>'RegisterExport["$format$", $func$]' @@ -1189,7 +1202,7 @@ class RegisterExport(Builtin): #> DeleteFile["sample.txt"] """ - summary_text = "Register an exporter for a file format" + summary_text = "register an exporter for a file format" context = "ImportExport`" options = { From 1d712d5fc140f0e6efe3ac72163746f9de620886 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sun, 25 Dec 2022 16:48:35 -0500 Subject: [PATCH 095/121] Go over and fix ImageTake --- CHANGES.rst | 1 + mathics/builtin/drawing/image.py | 49 +++++++++++++-------- test/builtin/drawing/test_image.py | 70 ++++++++++++++++++++++++++++++ test/builtin/image/test_image.py | 56 ------------------------ 4 files changed, 103 insertions(+), 73 deletions(-) create mode 100644 test/builtin/drawing/test_image.py delete mode 100644 test/builtin/image/test_image.py diff --git a/CHANGES.rst b/CHANGES.rst index 293c74eae..0535b17bb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -72,6 +72,7 @@ Bugs # ``0`` with a given precision (like in ```0`3```) is now parsed as ``0``, an integer number. #. ``RandomSample`` with one list argument now returns a random ordering of the list items. Previously it would return just one item. #. Origin placement corrected on ``ListPlot`` and ``LinePlot``. +#. Fix long-standing bugs in Image handling Enhancements diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index eebf2499e..26c93f20d 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -531,7 +531,7 @@ def eval_resize_width_height(self, image, width, height, evaluation, options): ) elif resampling_name == "Bicubic": return image.filter( - lambda im: im.resize((w, h), resample=PIL.Image.BICUBIC) + lambda im: im.resize((w, h), resample=PIL.Image.Resampling.BICUBIC) ) elif resampling_name != "Gaussian": return evaluation.message("ImageResize", "imgrsm", resampling) @@ -1712,18 +1712,37 @@ class ImageTake(_ImageBuiltin): <dt>'ImageTake[$image$, {$r1$, $r2$}, {$c1$, $c2$}]' <dd>gives a cropped version of $image$. </dl> + + Crop to the include only the upper half (244 rows) of an image: + >> alice = Import["ExampleData/MadTeaParty.gif"]; ImageTake[alice, 244] + = "-Image-" + + Now crop to the include the lower half of that image: + >> ImageTake[alice, -244] + = "-Image-" """ - summary_text = "create an image from a range of lines of another image" + summary_text = "crop image" def eval(self, image, n: Integer, evaluation: Evaluation): "ImageTake[image_Image, n_Integer]" py_n = n.value + max_y, max_x = image.pixels.shape[:2] if py_n >= 0: - pixels = image.pixels[:py_n] + adjusted_n = min(py_n, max_y) + pixels = image.pixels[:adjusted_n] + box_coords = (0, 0, max_x, adjusted_n) elif py_n < 0: - pixels = image.pixels[py_n:] - return Image(pixels, image.color_space) + adjusted_n = max(0, max_y + py_n) + pixels = image.pixels[adjusted_n:] + box_coords = (0, adjusted_n, max_x, max_y) + + if hasattr(image, "pillow"): + pillow = image.pillow.crop(box_coords) + pixels = numpy.asarray(pillow) + return Image(pixels, image.color_space, pillow=pillow) + + return Image(pixels, image.color_space, pillow=pillow) def _slice(self, image, i1: Integer, i2: Integer, axis): n = image.pixels.shape[axis] @@ -1754,8 +1773,9 @@ def eval_rows_cols( class PixelValue(_ImageBuiltin): """ - - <url>:WMA link:https://reference.wolfram.com/language/ref/PixelValue.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/PixelValue.html</url> <dl> <dt>'PixelValue[$image$, {$x$, $y$}]' @@ -1857,14 +1877,13 @@ def eval(self, image, val, d, evaluation: Evaluation): class ImageDimensions(_ImageBuiltin): """ - <url> :WMA link: https://reference.wolfram.com/language/ref/ImageDimensions.html</url> <dl> <dt>'ImageDimensions[$image$]' - <dd>Returns the dimensions of $image$ in pixels. + <dd>Returns the dimensions {$width$, $height$} of $image$ in pixels. </dl> >> lena = Import["ExampleData/lena.tif"]; @@ -1873,14 +1892,9 @@ class ImageDimensions(_ImageBuiltin): >> ImageDimensions[RandomImage[1, {50, 70}]] = {50, 70} - - #> Image[{{0, 1}, {1, 0}, {1, 1}}] // ImageDimensions - = {2, 3} - #> Image[{{0.2, 0.4}, {0.9, 0.6}, {0.3, 0.8}}] // ImageDimensions - = {2, 3} """ - summary_text = "get pixel dimensions of the raster associated with an image" + summary_text = "get the pixel dimensions of an image" def eval(self, image, evaluation: Evaluation): "ImageDimensions[image_Image]" @@ -2255,8 +2269,9 @@ def to_python(self, *args, **kwargs): class ImageAtom(AtomBuiltin): """ - - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageAtom.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ImageAtom.html</url> <dl> <dt>'Image[...]' diff --git a/test/builtin/drawing/test_image.py b/test/builtin/drawing/test_image.py new file mode 100644 index 000000000..f21a7ec1e --- /dev/null +++ b/test/builtin/drawing/test_image.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +""" +Tests for mathics.core.drawing.image: + +Image[] and image related functions. +""" +import os +from test.helper import evaluate + +import pytest + +from mathics.builtin.base import check_requires_list +from mathics.core.symbols import SymbolNull + +# Note we test with tif, jpg, and gif. Add others? +image_tests = [ + ('lena = Import["ExampleData/lena.tif"];', None, ""), + ("BinaryImageQ[lena]", "False", ""), + ("BinaryImageQ[Binarize[lena]]", "True", ""), + ( + """ein = Import["ExampleData/Einstein.jpg"]; ImageDimensions[ein]""", + "{615, 768}", + "", + ), + ("ImageDimensions[ImageResize[ein, {61, 76}]]", "{61, 76}", ""), + ("ImageDimensions[ImageTake[ein, 50]]", "{615, 50}", ""), + ("ImageDimensions[ImageTake[ein, -50]]", "{615, 50}", ""), + ("ImageDimensions[ImageTake[ein, 100000]]", "{615, 768}", ""), + ("ImageDimensions[ImageTake[ein, -100000]]", "{615, 768}", ""), + ( + """alice = Import["ExampleData/MadTeaParty.gif"]; ImageDimensions[alice]""", + "{640, 487}", + "", + ), + ("ImageDimensions[ImageResize[alice, {64, 48}]]", "{64, 48}", ""), + ("ImageDimensions[ImageTake[alice, 50]]", "{640, 50}", ""), + ("Image[{{0, 1}, {1, 0}, {1, 1}}] // ImageDimensions", "{2, 3}", ""), + ("Image[{{0.2, 0.4}, {0.9, 0.6}, {0.3, 0.8}}] // ImageDimensions", "{2, 3}", ""), + # FIXME: Our image handling is recovering from brokenness, but is not quite back... + # ('ImageResize[ein, 256, Resampling -> "Bicubic"]', "-Image-", ""), + # ('ImageResize[ein, {256, 256}, Resampling -> "Gaussian"]', "", + # "Gaussian resampling needs to maintain aspect ratio.") + # ('ImageResize[ein, 256, Resampling -> "Invalid"]', "", "Invalid resampling method Invalid."), + # ('ImageResize[ein, 256, Resampling -> Invalid]', "", "Invalid resampling method Invalid."), + # ('ImageResize[ein, {x}]', "", "The size {x} is not a valid image size specification."), + # ('ImageResize[ein, x]', "", "The size x is not a valid image size specification."), + # ("ImageType[Binarize[img]]", "Bit", ""), + # ("Binarize[img, 0.7]", "-Image-", ""), + # ("Binarize[img, {0.2, 0.6}", "-Image-", "") + # Are there others? +] + + +@pytest.mark.skipif( + not check_requires_list(["skimage"]), + reason="scikit-image (AKA skimage) is needed for working with Images", +) +@pytest.mark.skipif( + os.getenv("SANDBOX", False), + reason="Test doesn't work in a sandboxed environment with access to local files", +) +@pytest.mark.parametrize(("str_expr, str_expected, msg"), image_tests) +def test_image(str_expr: str, str_expected: str, msg: str, message=""): + result = evaluate(str_expr) + if result is not SymbolNull or str_expected is not None: + expected = evaluate(str_expected) + if msg: + assert result == expected, msg + else: + assert result == expected diff --git a/test/builtin/image/test_image.py b/test/builtin/image/test_image.py deleted file mode 100644 index bb6e7a523..000000000 --- a/test/builtin/image/test_image.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tests for mathics.core.drawing.image: - -Image[] and image related functions. -""" -import os -from test.helper import evaluate - -import pytest - -from mathics.builtin.base import check_requires_list -from mathics.core.symbols import SymbolNull - -image_tests = [('img = Import["ExampleData/lena.tif"];', None, "")] -if check_requires_list(["skimage"]): - image_tests += [ - ("BinaryImageQ[img]", "False", ""), - ("BinaryImageQ[Binarize[img]]", "True", ""), - ( - """ein = Import["ExampleData/Einstein.jpg"]; ImageDimensions[ein]""", - "{615, 768}", - "", - ), - # FIXME: I wonder if the testing framework is broken here. - # ('ImageResize[img], {400, 600}]', "-Image-", ""), - # ("ImageDimensions[%]", "{400, 600}", ""), - # ("ImageResize[ein, 256]", "-Image-", ""), - # ("ImageDimensions[%]", "{256, 320}", ""), - # ('ImageResize[ein, 256, Resampling -> "Bicubic"]', "-Image-", ""), - # ('ImageResize[ein, {256, 256}, Resampling -> "Gaussian"]', "", - # "Gaussian resampling needs to maintain aspect ratio.") - # ('ImageResize[ein, 256, Resampling -> "Invalid"]', "", "Invalid resampling method Invalid."), - # ('ImageResize[ein, 256, Resampling -> Invalid]', "", "Invalid resampling method Invalid."), - # ('ImageResize[ein, {x}]', "", "The size {x} is not a valid image size specification."), - # ('ImageResize[ein, x]', "", "The size x is not a valid image size specification."), - # ("ImageType[Binarize[img]]", "Bit", ""), - # ("Binarize[img, 0.7]", "-Image-", ""), - # ("Binarize[img, {0.2, 0.6}", "-Image-", "") - # Are there others? - ] - - -@pytest.mark.skipif( - os.getenv("SANDBOX", False), - reason="Test doesn't work in a sandboxed environment with access to local files", -) -@pytest.mark.parametrize(("str_expr, str_expected, msg"), image_tests) -def test_image(str_expr: str, str_expected: str, msg: str, message=""): - result = evaluate(str_expr) - if result is not SymbolNull or str_expected is not None: - expected = evaluate(str_expected) - if msg: - assert result == expected, msg - else: - assert result == expected From 3adeb8b981c976163185529cbccbcc61a5a89ebd Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sun, 25 Dec 2022 17:12:39 -0500 Subject: [PATCH 096/121] Binarize works if no scikit_image... it just doesn't have Cluster. Tolerate 3.6 PIL --- mathics/builtin/drawing/image.py | 80 ++++++++++++++---------------- mathics/eval/image.py | 29 +++++++++++ test/builtin/drawing/test_image.py | 8 +-- 3 files changed, 69 insertions(+), 48 deletions(-) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 26c93f20d..eb9290f21 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -17,7 +17,7 @@ import os.path as osp from collections import defaultdict from copy import deepcopy -from typing import Optional, Tuple +from typing import Tuple from mathics.builtin.base import AtomBuiltin, Builtin, String, Test from mathics.builtin.box.image import ImageBox @@ -44,6 +44,7 @@ from mathics.eval.image import ( convolve, extract_exif, + get_image_size_spec, matrix_to_numpy, numpy_flip, numpy_to_matrix, @@ -73,6 +74,14 @@ except ImportError: pass + +try: + import skimage.filters +except ImportError: + have_skimage_filters = False +else: + have_skimage_filters = True + from io import BytesIO # The following classes are used to allow inclusion of @@ -445,33 +454,6 @@ class ImageResize(_ImageBuiltin): options = {"Resampling": "Automatic"} summary_text = "resize an image" - def _get_image_size_spec(self, old_size, new_size) -> Optional[float]: - predefined_sizes = { - "System`Tiny": 75, - "System`Small": 150, - "System`Medium": 300, - "System`Large": 450, - "System`Automatic": 0, # placeholder - } - result = new_size.round_to_float() - if result is not None: - result = int(result) - if result <= 0: - return None - return result - - if isinstance(new_size, Symbol): - name = new_size.get_name() - if name == "System`All": - return old_size - return predefined_sizes.get(name, None) - if new_size.has_form("Scaled", 1): - s = new_size.elements[0].round_to_float() - if s is None: - return None - return max(1, old_size * s) # handle negative s values silently - return None - def eval_resize_width(self, image, s, evaluation, options): "ImageResize[image_Image, s_, OptionsPattern[ImageResize]]" old_w = image.pixels.shape[1] @@ -479,7 +461,7 @@ def eval_resize_width(self, image, s, evaluation, options): width = s.elements[0] else: width = s - w = self._get_image_size_spec(old_w, width) + w = get_image_size_spec(old_w, width) if w is None: return evaluation.message("ImageResize", "imgrssz", s) if s.has_form("List", 1): @@ -502,8 +484,8 @@ def eval_resize_width_height(self, image, width, height, evaluation, options): # find new size old_w, old_h = image.pixels.shape[1], image.pixels.shape[0] - w = self._get_image_size_spec(old_w, width) - h = self._get_image_size_spec(old_h, height) + w = get_image_size_spec(old_w, width) + h = get_image_size_spec(old_h, height) if h is None or w is None: return evaluation.message( "ImageResize", "imgrssz", to_mathics_list(width, height) @@ -530,9 +512,14 @@ def eval_resize_width_height(self, image, width, height, evaluation, options): lambda im: im.resize((w, h), resample=PIL.Image.NEAREST) ) elif resampling_name == "Bicubic": - return image.filter( - lambda im: im.resize((w, h), resample=PIL.Image.Resampling.BICUBIC) + # After Python 3.6 support is dropped, this can be simplified + # to for Pillow 9+ and use PIL.Image.Resampling.BICUBIC only. + bicubic = ( + PIL.Image.Resampling.BICUBIC + if hasattr(PIL.Image, "Resampling") + else PIL.Image.BICUBIC ) + return image.filter(lambda im: im.resize((w, h), resample=bicubic)) elif resampling_name != "Gaussian": return evaluation.message("ImageResize", "imgrsm", resampling) @@ -581,11 +568,13 @@ class ImageReflect(_ImageBuiltin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/ImageReflect.html</url> <dl> - <dt>'ImageReflect[$image$]' + <dt>'ImageReflect[$image$]' <dd>Flips $image$ top to bottom. - <dt>'ImageReflect[$image$, $side$]' + + <dt>'ImageReflect[$image$, $side$]' <dd>Flips $image$ so that $side$ is interchanged with its opposite. - <dt>'ImageReflect[$image$, $side_1$ -> $side_2$]' + + <dt>'ImageReflect[$image$, $side_1$ -> $side_2$]' <dd>Flips $image$ so that $side_1$ is interchanged with $side_2$. </dl> @@ -1377,7 +1366,7 @@ def eval(self, image, n: Integer, evaluation: Evaluation): return Image(numpy.array(im), "RGB") -class Threshold(_SkimageBuiltin): +class Threshold(_ImageBuiltin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/Threshold.html</url> @@ -1401,7 +1390,10 @@ class Threshold(_SkimageBuiltin): """ summary_text = "estimate a threshold value for binarize an image" - options = {"Method": '"Cluster"'} + if have_skimage_filters: + options = {"Method": '"Cluster"'} + else: + options = {"Method": '"Median"'} messages = { "illegalmethod": "Method `` is not supported.", @@ -1419,8 +1411,9 @@ def eval(self, image, evaluation: Evaluation, options): else method.to_python() ) if method_name == "Cluster": - import skimage.filters - + if not have_skimage_filters: + evaluation.message("ImageResize", "skimage") + return threshold = skimage.filters.threshold_otsu(pixels) elif method_name == "Median": threshold = numpy.median(pixels) @@ -1432,7 +1425,7 @@ def eval(self, image, evaluation: Evaluation, options): return MachineReal(float(threshold)) -class Binarize(_SkimageBuiltin): +class Binarize(_ImageBuiltin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/Binarize.html</url> @@ -1482,7 +1475,6 @@ def eval_t1_t2(self, image, t1, t2, evaluation: Evaluation): class ColorSeparate(_ImageBuiltin): """ - <url> :WMA link: https://reference.wolfram.com/language/ref/ColorSeparate.html</url> @@ -1715,11 +1707,11 @@ class ImageTake(_ImageBuiltin): Crop to the include only the upper half (244 rows) of an image: >> alice = Import["ExampleData/MadTeaParty.gif"]; ImageTake[alice, 244] - = "-Image-" + = -Image- Now crop to the include the lower half of that image: >> ImageTake[alice, -244] - = "-Image-" + = -Image- """ summary_text = "crop image" diff --git a/mathics/eval/image.py b/mathics/eval/image.py index 40aeac77a..c033759fd 100644 --- a/mathics/eval/image.py +++ b/mathics/eval/image.py @@ -15,6 +15,7 @@ from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression +from mathics.core.symbols import Symbol from mathics.core.systemsymbols import SymbolRule, SymbolSimplify try: @@ -112,6 +113,34 @@ def extract_exif(image, evaluation: Evaluation) -> Optional[Expression]: return Expression(SymbolRule, String("RawExif"), ListExpression(*exif_options)) +def get_image_size_spec(old_size, new_size) -> Optional[float]: + predefined_sizes = { + "System`Tiny": 75, + "System`Small": 150, + "System`Medium": 300, + "System`Large": 450, + "System`Automatic": 0, # placeholder + } + result = new_size.round_to_float() + if result is not None: + result = int(result) + if result <= 0: + return None + return result + + if isinstance(new_size, Symbol): + name = new_size.get_name() + if name == "System`All": + return old_size + return predefined_sizes.get(name, None) + if new_size.has_form("Scaled", 1): + s = new_size.elements[0].round_to_float() + if s is None: + return None + return max(1, old_size * s) # handle negative s values silently + return None + + def matrix_to_numpy(a): def matrix(): for y in a.elements: diff --git a/test/builtin/drawing/test_image.py b/test/builtin/drawing/test_image.py index f21a7ec1e..9107f046e 100644 --- a/test/builtin/drawing/test_image.py +++ b/test/builtin/drawing/test_image.py @@ -51,10 +51,10 @@ ] -@pytest.mark.skipif( - not check_requires_list(["skimage"]), - reason="scikit-image (AKA skimage) is needed for working with Images", -) +# @pytest.mark.skipif( +# not check_requires_list(["skimage"]), +# reason="scikit-image (AKA skimage) is needed for working with Images", +# ) @pytest.mark.skipif( os.getenv("SANDBOX", False), reason="Test doesn't work in a sandboxed environment with access to local files", From 67095ffe845930bc51f7859b5263f12aea311603 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sun, 25 Dec 2022 20:09:44 -0500 Subject: [PATCH 097/121] Window CI woes --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d351e1609..786cdc8b9 100755 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -27,7 +27,7 @@ jobs: set LLVM_DIR="C:\Program Files\LLVM" # We can comment out after next Mathics-Scanner release python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full] - make develop + make develop-full - name: Install Mathics run: | python setup.py install From 7ef32e6459ee53aa7858103001f79eca6643e63c Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Sun, 25 Dec 2022 21:58:02 -0500 Subject: [PATCH 098/121] Correct ImageResize --- mathics/builtin/drawing/image.py | 109 ++++++++----------------------- mathics/eval/image.py | 92 ++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 81 deletions(-) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index eb9290f21..bfaa64348 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -51,6 +51,7 @@ pixels_as_float, pixels_as_ubyte, pixels_as_uint, + resize_width_height, ) SymbolColorQuantize = Symbol("ColorQuantize") @@ -85,7 +86,7 @@ from io import BytesIO # The following classes are used to allow inclusion of -# Buultin Functions only when certain Python packages +# Builtin Functions only when certain Python packages # are available. They do this by setting the `requires` class variable. @@ -399,7 +400,6 @@ def eval(self, minval, maxval, w, h, evaluation, options): class ImageResize(_ImageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageResize.html</url> <dl> @@ -410,38 +410,37 @@ class ImageResize(_ImageBuiltin): <dd> </dl> - S> ein = Import["ExampleData/Einstein.jpg"] - = -Image- - - S> ImageDimensions[ein] - = {615, 768} - S> ImageResize[ein, {400, 600}] - = -Image- - S> ImageDimensions[%] - = {400, 600} - - S> ImageResize[ein, {256}] - = -Image- - - S> ImageDimensions[%] - = {256, 256} - The Resampling option can be used to specify how to resample the image. Options are: <ul> <li>Automatic <li>Bicubic - <li>Gaussian + <li>Bilinear + <li>Box + <li>Hamming + <li>Lanczos <li>Nearest </ul> - The default sampling method is Bicubic. + See <url> + :Pillow Filters: + https://pillow.readthedocs.io/en/stable/handbook/concepts.html#filters</url>\ + for a description of these. - S> ImageResize[ein, 256, Resampling -> "Bicubic"] + S> alice = Import["ExampleData/MadTeaParty.gif"] = -Image- - S> ImageResize[ein, 256, Resampling -> "Gaussian"] - = ... - : ... + S> shape = ImageDimensions[alice] + = {640, 487} + + S> ImageResize[alice, shape / 2] + = -Image- + + The default sampling method is "Bicubic" which has pretty good upscaling \ + and downscaling quality. However "Box" is the fastest: + + + S> ImageResize[alice, shape / 2, Resampling -> "Box"] + = -Image- """ messages = { @@ -480,7 +479,7 @@ def eval_resize_width_height(self, image, width, height, evaluation, options): ): resampling_name = "Bicubic" else: - resampling_name = resampling.get_string_value() + resampling_name = resampling.value # find new size old_w, old_h = image.pixels.shape[1], image.pixels.shape[0] @@ -507,66 +506,14 @@ def eval_resize_width_height(self, image, width, height, evaluation, options): h, w = int(round(h)), int(round(w)) # perform the resize - if resampling_name == "Nearest": - return image.filter( - lambda im: im.resize((w, h), resample=PIL.Image.NEAREST) - ) - elif resampling_name == "Bicubic": - # After Python 3.6 support is dropped, this can be simplified - # to for Pillow 9+ and use PIL.Image.Resampling.BICUBIC only. - bicubic = ( - PIL.Image.Resampling.BICUBIC - if hasattr(PIL.Image, "Resampling") - else PIL.Image.BICUBIC - ) - return image.filter(lambda im: im.resize((w, h), resample=bicubic)) - elif resampling_name != "Gaussian": - return evaluation.message("ImageResize", "imgrsm", resampling) - - try: - from skimage import __version__ as skimage_version, transform - - multichannel = image.pixels.ndim == 3 - - sy = h / old_h - sx = w / old_w - if sy > sx: - err = abs((sy * old_w) - (sx * old_w)) - s = sy - else: - err = abs((sy * old_h) - (sx * old_h)) - s = sx - if err > 1.5: - # TODO overcome this limitation - return evaluation.message("ImageResize", "gaussaspect") - elif s > 1: - pixels = transform.pyramid_expand( - image.pixels, upscale=s, multichannel=multichannel - ).clip(0, 1) - else: - kwargs = {"downscale": (1.0 / s)} - # scikit_image in version 0.19 changes the resize parameter deprecating - # "multichannel". scikit_image also doesn't support older Pythons like 3.6.15. - # If we drop suport for 3.6 we can probably remove - if skimage_version >= "0.19": - # Not totally sure that we want channel_axis=1, but it makes the - # test work. multichannel is deprecated in scikit-image-19.2 - # Previously we used multichannel (=3) - # as in the above s > 1 case. - kwargs["channel_axis"] = 2 - else: - kwargs["multichannel"] = multichannel - - pixels = transform.pyramid_reduce(image.pixels, **kwargs).clip(0, 1) - - return Image(pixels, image.color_space) - except ImportError: - evaluation.message("ImageResize", "skimage") + return resize_width_height(image, w, h, resampling_name, evaluation) class ImageReflect(_ImageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageReflect.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ImageReflect.html</url> <dl> <dt>'ImageReflect[$image$]' <dd>Flips $image$ top to bottom. diff --git a/mathics/eval/image.py b/mathics/eval/image.py index c033759fd..3f78371bd 100644 --- a/mathics/eval/image.py +++ b/mathics/eval/image.py @@ -8,6 +8,7 @@ from typing import List, Optional import numpy +import PIL from mathics.builtin.base import String from mathics.core.atoms import Rational @@ -34,6 +35,27 @@ 40963: "PixelYDimension", } +# After Python 3.6 support is dropped, this can be simplified +# to for Pillow 9+ and use PIL.Image.Resampling only. +if hasattr(PIL.Image, "Resampling"): + pil_resize = PIL.Image.Resampling +else: + pil_resize = PIL.Image + +# See: +# https://pillow.readthedocs.io/en/stable/handbook/concepts.html#filters +# For a list and comparison of the filters. + +resampling_names2PIL = { + "Automatic": getattr(pil_resize, "BICUBIC"), + "Bicubic": getattr(pil_resize, "BICUBIC"), + "Bilinear": getattr(pil_resize, "BILINEAR"), + "Box": getattr(pil_resize, "BOX"), + "Hamming": getattr(pil_resize, "HAMMING"), + "Lanczos": getattr(pil_resize, "LANCZOS"), + "Nearest": getattr(pil_resize, "NEAREST"), +} + def convolve(in1, in2, fixed=True): """ @@ -204,3 +226,73 @@ def pixels_as_uint(pixels): return pixels.astype(numpy.uint8) * 65535 else: raise NotImplementedError + + +def resize_width_height( + image, width, height, resampling_name: str, evaluation: Evaluation +): + """ + workhorse part of ImageResize[] after mathic options have been processed. + """ + from mathics.builtin.drawing.image import Image + + if resampling_name not in resampling_names2PIL.keys(): + return evaluation.message("ImageResize", "imgrsm", resampling_name) + resample = resampling_names2PIL[resampling_name] + + # perform the resize + if hasattr(image, "pillow"): + if resampling_name not in resampling_names2PIL.keys(): + return evaluation.message("ImageResize", "imgrsm", resampling_name) + pillow = image.pillow.resize(size=(width, height), resample=resample) + pixels = numpy.asarray(pillow) + return Image(pixels, image.color_space, pillow=pillow) + + return image.filter(lambda im: im.resize((width, height), resample=resample)) + + # The Below code is hand-crapted Guassian resampling code, which is what + # WMA does. For now, are going to punt on this, and we use PIL methods only. + + # Gaussian need sto unrounded values to compute scaling ratios. + # round to closest pixel for other methods. + + # h, w = int(round(height)), int(round(width)) + # try: + # from skimage import __version__ as skimage_version, transform + + # multichannel = image.pixels.ndim == 3 + + # sy = height / old_h + # sx = width / old_w + # if sy > sx: + # err = abs((sy * old_w) - (sx * old_w)) + # s = sy + # else: + # err = abs((sy * old_h) - (sx * old_h)) + # s = sx + # if err > 1.5: + # # TODO overcome this limitation + # return evaluation.message("ImageResize", "gaussaspect") + # elif s > 1: + # pixels = transform.pyramid_expand( + # image.pixels, upscale=s, multichannel=multichannel + # ).clip(0, 1) + # else: + # kwargs = {"downscale": (1.0 / s)} + # # scikit_image in version 0.19 changes the resize parameter deprecating + # # "multichannel". scikit_image also doesn't support older Pythons like 3.6.15. + # # If we drop suport for 3.6 we can probably remove + # if skimage_version >= "0.19": + # # Not totally sure that we want channel_axis=1, but it makes the + # # test work. multichannel is deprecated in scikit-image-19.2 + # # Previously we used multichannel (=3) + # # as in the above s > 1 case. + # kwargs["channel_axis"] = 2 + # else: + # kwargs["multichannel"] = multichannel + + # pixels = transform.pyramid_reduce(image.pixels, **kwargs).clip(0, 1) + + # return Image(pixels, image.color_space) + # except ImportError: + # evaluation.message("ImageResize", "skimage") From b2e42de0bf80ac82c0ca08966068d7af0748fef2 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Mon, 26 Dec 2022 22:54:09 -0500 Subject: [PATCH 099/121] Add example for TranslationTransform --- mathics/builtin/tensors.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/mathics/builtin/tensors.py b/mathics/builtin/tensors.py index f8f620b5f..aca59104d 100644 --- a/mathics/builtin/tensors.py +++ b/mathics/builtin/tensors.py @@ -487,27 +487,40 @@ class TransformationFunction(Builtin): class TranslationTransform(Builtin): """ - <url>:WMA link: https://reference.wolfram.com/language/ref/TranslationTransform.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/TranslationTransform.html</url> <dl> <dt>'TranslationTransform[$v$]' - <dd>gives the translation by the vector $v$. + <dd>gives a 'TransformationFunction' that translates points by vector $v$. </dl> - >> TranslationTransform[{1, 2}] - = TransformationFunction[{{1, 0, 1}, {0, 1, 2}, {0, 0, 1}}] + >> t = TranslationTransform[{x0, y0}] + = TransformationFunction[{{1, 0, x0}, {0, 1, y0}, {0, 0, 1}}] + + >> t[{x, y}] + = {x + x0, y + y0} + + From <url> + :Creating a Sierpinsky gasket with the missing triangles filled in: + "https://mathematica.stackexchange.com/questions/7360/creating-a-sierpinski-gasket-with-the-missing-triangles-filled-in/7361#7361</url>: + >> Show[Graphics[Table[Polygon[TranslationTransform[{Sqrt[3] (i - j/2), 3 j/2}] /@ {{Sqrt[3]/2, -1/2}, {0, 1}, {-Sqrt[3]/2, -1/2}}], {i, 7}, {j, i}]]] + = -Graphics- """ rules = { "TranslationTransform[v_]": "TransformationFunction[IdentityMatrix[Length[v] + 1] + " "(Join[ConstantArray[0, Length[v]], {#}]& /@ Join[v, {0}])]", } - summary_text = "symbolic representation of translation" + summary_text = "create a vector translation function" class Transpose(Builtin): """ - <url>:Transpose: https://en.wikipedia.org/wiki/Transpose</url> (<url>:WMA: https://reference.wolfram.com/language/ref/Transpose.html</url>) + <url> + :Transpose: https://en.wikipedia.org/wiki/Transpose</url> (<url> + :WMA: https://reference.wolfram.com/language/ref/Transpose.html</url>) <dl> <dt>'Tranpose[$m$]' From ba30341e7bbf4029c0c9824818acfddab5cac49f Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Tue, 27 Dec 2022 06:39:54 -0500 Subject: [PATCH 100/121] RowBox fix and BoxExpression refactor... Move BoxExpression out of mathics.builtin.base and into mathics.box.expression BoxExpression didn't have has_form and therefore RowBoxing was giving us too-small boxes when put in a Table. BoxExpression now has .elements just like an Expression does. Deprecated BoxConstruct removed. --- mathics/builtin/base.py | 194 +--------------- mathics/builtin/box/compilation.py | 2 +- mathics/builtin/box/expression.py | 215 ++++++++++++++++++ mathics/builtin/box/graphics.py | 2 +- mathics/builtin/box/image.py | 2 +- mathics/builtin/box/layout.py | 15 +- mathics/builtin/drawing/graphics_internals.py | 3 +- mathics/core/evaluation.py | 2 +- mathics/format/mathml.py | 10 +- 9 files changed, 238 insertions(+), 207 deletions(-) create mode 100644 mathics/builtin/box/expression.py diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index e520c0201..9e62e9ee0 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -10,26 +10,18 @@ import sympy from mathics.core.atoms import Integer, MachineReal, PrecisionReal, String -from mathics.core.attributes import A_NO_ATTRIBUTES, A_PROTECTED, A_READ_PROTECTED +from mathics.core.attributes import A_NO_ATTRIBUTES, A_PROTECTED from mathics.core.convert.expression import to_expression, to_numeric_sympy_args from mathics.core.convert.op import ascii_operator_to_symbol from mathics.core.convert.python import from_bool from mathics.core.convert.sympy import from_sympy from mathics.core.definitions import Definition -from mathics.core.element import BoxElementMixin from mathics.core.exceptions import MessageException from mathics.core.expression import Expression, SymbolDefault -from mathics.core.list import ListExpression from mathics.core.number import PrecisionValueError, get_precision from mathics.core.parser.util import PyMathicsDefinitions, SystemDefinitions from mathics.core.rules import BuiltinRule, Pattern, Rule -from mathics.core.symbols import ( - BaseElement, - Symbol, - SymbolHoldForm, - ensure_context, - strip_context, -) +from mathics.core.symbols import BaseElement, Symbol, ensure_context, strip_context from mathics.core.systemsymbols import SymbolMessageName, SymbolRule # Signals to Mathics doc processing not to include this module in its documentation. @@ -75,28 +67,6 @@ def has_option(options, name, evaluation): return get_option(options, name, evaluation, evaluate=False) is not None -def split_name(name: str) -> str: - """ - insert spaces in front of upper case letters - and numbers. For instance, - ``split_name("BezierCurve3D")`` results in - ``"bezier curve 3D"`` - - """ - if name == "": - return "" - result = name[0] - for i in range(1, len(name)): - if name[i].isupper(): - if not name[i - 1].isdigit(): - result = result + " " - elif name[i].isdigit(): - if not name[i - 1].isdigit(): - result = result + " " - result = result + name[i] - return result.lower() - - mathics_to_python = {} # here we have: name -> string @@ -718,162 +688,6 @@ def prepare_mathics(self, sympy_expr): return sympy_expr -class BoxExpression(BuiltinElement, BoxElementMixin): - # This is the base class for the "Final form" - # of formatted expressions. - # - # These objects should not be evaluated, so in the evaluation process should be - # considered "inert". However, it could happend that an Expression having them as an element - # be evaluable, and try to apply rules. For example, - # InputForm[ToBoxes[a+b]] - # should be evaluated to ``Expression(SymbolRowBox, '"a"', '"+"', '"b"')``. - # - # Changes to do, after the refactor of mathics.core: - # - - # * Review the implementation - - attributes = A_PROTECTED | A_READ_PROTECTED - - def __new__(cls, *elements, **kwargs): - instance = super().__new__(cls, *elements, **kwargs) - # This should not be here. - article = ( - "an " - if instance.get_name()[0].lower() in ("a", "e", "i", "o", "u") - else "a " - ) - - instance.summary_text = ( - "box representation for " - + article - + split_name(cls.get_name(short=True)[:-3]) - ) - if not instance.__doc__: - instance.__doc__ = rf""" - <dl> - <dt>'{instance.get_name()}' - <dd> box structure. - </dl> - """ - - # the __new__ method from BuiltinElement - # calls self.init. It is expected that it set - # self._elements. However, if it didn't happens, - # we set it with a default value. - # There should be a better way to implement this - # behaviour... - if not hasattr(instance, "_elements"): - instance._elements = tuple(elements) - return instance - - @property - def is_literal(self) -> bool: - """ - True if the value can't change, i.e. a value is set and it does not - depend on definition bindings. That is why, in contrast to - `is_uncertain_final_definitions()` we don't need a `definitions` - parameter. - """ - return False - - @property - def elements(self): - return self._elements - - @elements.setter - def elements(self, value): - self._elements = value - return self._elements - - def tex_block(self, tex, only_subsup=False): - if len(tex) == 1: - return tex - else: - if not only_subsup or "_" in tex or "^" in tex: - return "{%s}" % tex - else: - return tex - - def to_expression(self) -> Expression: - # FIXME: All classes should store their symbol name. - # So there should be a self.head. - return Expression(Symbol(self.get_name()), *self._elements) - - def replace_vars(self, vars, options=None, in_scoping=True, in_function=True): - expr = self.to_expression() - result = expr.replace_vars(vars, options, in_scoping, in_function) - return result - - # Deprecated: remove eventually - def get_elements(self): - return self._elements - - def get_head_name(self): - return self.get_name() - - def get_lookup_name(self): - return self.get_name() - - def get_sort_key(self) -> tuple: - return self.to_expression().get_sort_key() - - def get_string_value(self): - return "-@" + self.get_head_name() + "@-" - - def sameQ(self, expr) -> bool: - """Mathics SameQ""" - return expr.sameQ(self) - - def do_format(self, evaluation, format): - return self - - def format(self, evaluation, fmt): - expr = Expression(SymbolHoldForm, self.to_expression()) - fexpr = expr.format(evaluation, fmt) - return fexpr - - def get_head(self): - return Symbol(self.get_name()) - - @property - def head(self): - return self.get_head() - - @head.setter - def head(self, value): - raise ValueError("BoxExpression.head is write protected.") - - @property - def leaves(self): - return self.get_elements() - - @leaves.setter - def leaves(self, value): - raise ValueError("BoxExpression.elements is write protected.") - - def flatten_pattern_sequence(self, evaluation) -> "BoxExpression": - return self - - def flatten_with_respect_to_head(self, symbol): - return self.to_expression().flatten_with_respect_to_head(symbol) - - def get_option_values(self, elements, **options): - evaluation = options.get("evaluation", None) - if evaluation: - default = evaluation.definitions.get_options(self.get_name()).copy() - options = ListExpression(*elements).get_option_values(evaluation) - default.update(options) - else: - from mathics.core.parser import parse_builtin_rule - - default = {} - for option, value in self.options.items(): - option = ensure_context(option) - default[option] = parse_builtin_rule(value) - return default - - class PatternError(Exception): def __init__(self, name, tag, *args): super().__init__() @@ -1034,7 +848,3 @@ def from_expression(expr): return CountableInteger(0, upper_limit=True) return None # leave original expression unevaluated - - -# Backward compatibility -BoxConstruct = BoxExpression diff --git a/mathics/builtin/box/compilation.py b/mathics/builtin/box/compilation.py index 9e2a06a83..5fb7f515c 100644 --- a/mathics/builtin/box/compilation.py +++ b/mathics/builtin/box/compilation.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from mathics.builtin.base import BoxExpression +from mathics.builtin.box.expression import BoxExpression class CompiledCodeBox(BoxExpression): diff --git a/mathics/builtin/box/expression.py b/mathics/builtin/box/expression.py new file mode 100644 index 000000000..078b9df8c --- /dev/null +++ b/mathics/builtin/box/expression.py @@ -0,0 +1,215 @@ +from mathics.builtin.base import BuiltinElement +from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED +from mathics.core.element import BoxElementMixin +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.symbols import Symbol, SymbolHoldForm, ensure_context + + +def split_name(name: str) -> str: + """ + insert spaces in front of upper case letters + and numbers. For instance, + ``split_name("BezierCurve3D")`` results in + ``"bezier curve 3D"`` + + """ + if name == "": + return "" + result = name[0] + for i in range(1, len(name)): + if name[i].isupper(): + if not name[i - 1].isdigit(): + result = result + " " + elif name[i].isdigit(): + if not name[i - 1].isdigit(): + result = result + " " + result = result + name[i] + return result.lower() + + +# TODO: Review this +class BoxExpression(BuiltinElement, BoxElementMixin): + """ + This is the base class for Boxed (compound) Expressions. + + Boxing of a BoxExpression generally does not need a general M-Expression kind + of evaluation, although it can happen. + + For example: + InputForm[ToBoxes[a+b]] + + should be evaluated to ``Expression(SymbolRowBox, '"a"', '"+"', '"b"')``. + """ + + attributes = A_PROTECTED | A_READ_PROTECTED + + def __new__(cls, *elements, **kwargs): + instance = super().__new__(cls, *elements, **kwargs) + # This should not be here. + article = ( + "an " + if instance.get_name()[0].lower() in ("a", "e", "i", "o", "u") + else "a " + ) + + instance.summary_text = ( + "box representation for " + + article + + split_name(cls.get_name(short=True)[:-3]) + ) + if not instance.__doc__: + instance.__doc__ = rf""" + <dl> + <dt>'{instance.get_name()}' + <dd> box structure. + </dl> + """ + + # the __new__ method from BuiltinElement + # calls self.init. It is expected that it set + # self._elements. However, if it didn't happens, + # we set it with a default value. + # There should be a better way to implement this + # behaviour... + if not hasattr(instance, "_elements"): + instance._elements = tuple(elements) + return instance + + def do_format(self, evaluation, format): + return self + + @property + def elements(self): + return self._elements + + @elements.setter + def elements(self, value): + self._elements = value + return self._elements + + def format(self, evaluation, fmt): + expr = Expression(SymbolHoldForm, self.to_expression()) + fexpr = expr.format(evaluation, fmt) + return fexpr + + def get_head(self): + return Symbol(self.get_name()) + + # Deprecated: remove eventually + def get_elements(self): + return self._elements + + def get_head_name(self): + return self.get_name() + + def get_lookup_name(self): + return self.get_name() + + def get_sort_key(self) -> tuple: + return self.to_expression().get_sort_key() + + def get_string_value(self): + return "-@" + self.get_head_name() + "@-" + + @property + def head(self): + return self.get_head() + + def has_form(self, heads, *element_counts): + """ + element_counts: + (,): no elements allowed + (None,): no constraint on number of elements + (n, None): element count >= n + (n1, n2, ...): element count in {n1, n2, ...} + """ + + head_name = self.get_name() + + if isinstance(heads, (tuple, list, set)): + if head_name not in [ensure_context(h) for h in heads]: + return False + else: + if head_name != ensure_context(heads): + return False + if not element_counts: + return False + if element_counts and element_counts[0] is not None: + count = len(self._elements) + if count not in element_counts: + if ( + len(element_counts) == 2 + and element_counts[1] is None # noqa + and count >= element_counts[0] + ): + return True + else: + return False + return True + + @head.setter + def head(self, value): + raise ValueError("BoxExpression.head is write protected.") + + @property + def is_literal(self) -> bool: + """ + True if the value can't change, i.e. a value is set and it does not + depend on definition bindings. That is why, in contrast to + `is_uncertain_final_definitions()` we don't need a `definitions` + parameter. + """ + return False + + def replace_vars(self, vars, options=None, in_scoping=True, in_function=True): + expr = self.to_expression() + result = expr.replace_vars(vars, options, in_scoping, in_function) + return result + + def sameQ(self, expr) -> bool: + """Mathics SameQ""" + return expr.sameQ(self) + + def tex_block(self, tex, only_subsup=False): + if len(tex) == 1: + return tex + else: + if not only_subsup or "_" in tex or "^" in tex: + return "{%s}" % tex + else: + return tex + + def to_expression(self) -> Expression: + # FIXME: All classes should store their symbol name. + # So there should be a self.head. + return Expression(Symbol(self.get_name()), *self._elements) + + @property + def leaves(self): + return self.get_elements() + + @leaves.setter + def leaves(self, value): + raise ValueError("BoxExpression.elements is write protected.") + + def flatten_pattern_sequence(self, evaluation) -> "BoxExpression": + return self + + def flatten_with_respect_to_head(self, symbol): + return self.to_expression().flatten_with_respect_to_head(symbol) + + def get_option_values(self, elements, **options): + evaluation = options.get("evaluation", None) + if evaluation: + default = evaluation.definitions.get_options(self.get_name()).copy() + options = ListExpression(*elements).get_option_values(evaluation) + default.update(options) + else: + from mathics.core.parser import parse_builtin_rule + + default = {} + for option, value in self.options.items(): + option = ensure_context(option) + default[option] = parse_builtin_rule(value) + return default diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index 102c2a576..d1d9a2eea 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -6,7 +6,7 @@ from math import atan2, ceil, cos, degrees, floor, log10, pi, sin -from mathics.builtin.base import BoxExpression +from mathics.builtin.box.expression import BoxExpression from mathics.builtin.colors.color_directives import ( ColorError, Opacity, diff --git a/mathics/builtin/box/image.py b/mathics/builtin/box/image.py index 6bd1afa43..186a2716b 100644 --- a/mathics/builtin/box/image.py +++ b/mathics/builtin/box/image.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from mathics.builtin.base import BoxExpression +from mathics.builtin.box.expression import BoxExpression class ImageBox(BoxExpression): diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index 7c643df16..74b83a2e6 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -6,7 +6,8 @@ The routines here assist in boxing at the bottom of the hierarchy. At the other end, the top level, we have a Notebook which is just a collection of Expressions usually contained in boxes. """ -from mathics.builtin.base import BoxExpression, Builtin +from mathics.builtin.base import Builtin +from mathics.builtin.box.expression import BoxExpression from mathics.builtin.options import options_to_rules from mathics.core.atoms import Atom, String from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_PROTECTED, A_READ_PROTECTED @@ -15,7 +16,7 @@ from mathics.core.exceptions import BoxConstructError from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import Symbol, SymbolMakeBoxes +from mathics.core.symbols import Symbol, SymbolFullForm from mathics.core.systemsymbols import ( SymbolFractionBox, SymbolRowBox, @@ -25,6 +26,7 @@ SymbolSubsuperscriptBox, SymbolSuperscriptBox, ) +from mathics.eval.makeboxes import eval_makeboxes def to_boxes(x, evaluation: Evaluation, options={}) -> BoxElementMixin: @@ -239,7 +241,7 @@ def check_item(item): return item self.items = tuple((check_item(item) for item in items)) - self._elements = None + self._elements = self.items def to_expression(self) -> Expression: """ @@ -247,7 +249,7 @@ def to_expression(self) -> Expression: to implement the interface of normal Expressions, for example, when a boxed expression is manipulated to produce a new boxed expression. - For instance, consider the folling definition: + For instance, consider the following definition: ``` MakeBoxes[{items___}, StandardForm] := RowBox[{"[", Sequence @@ Riffle[MakeBoxes /@ {items}, " "], "]"}] ``` @@ -256,13 +258,12 @@ def to_expression(self) -> Expression: in the apply method, this function must be called. """ if self._elements is None: - items = tuple( + self._elements = tuple( item.to_expression() if isinstance(item, BoxElementMixin) else item for item in self.items ) - self._elements = Expression(SymbolRowBox, ListExpression(*items)) - return self._elements + return Expression(SymbolRowBox, ListExpression(*self._elements)) class ShowStringCharacters(Builtin): diff --git a/mathics/builtin/drawing/graphics_internals.py b/mathics/builtin/drawing/graphics_internals.py index 27dc5c9e1..07d84be2d 100644 --- a/mathics/builtin/drawing/graphics_internals.py +++ b/mathics/builtin/drawing/graphics_internals.py @@ -4,7 +4,8 @@ # Also no docstring which may confuse the doc system -from mathics.builtin.base import BoxExpression, BuiltinElement, split_name +from mathics.builtin.base import BuiltinElement +from mathics.builtin.box.expression import BoxExpression, split_name # Signals to Mathics doc processing not to include this module in its documentation. no_doc = True diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py index 1f9c3c836..d69d1b13c 100644 --- a/mathics/core/evaluation.py +++ b/mathics/core/evaluation.py @@ -470,7 +470,7 @@ def format_output(self, expr, format=None): raise ValueError try: - # With the new implementation, if result is not a ``BoxConstruct`` + # With the new implementation, if result is not a ``BoxExpression`` # then we should raise a BoxError here. boxes = result.boxes_to_text(evaluation=self) except BoxError: diff --git a/mathics/format/mathml.py b/mathics/format/mathml.py index d26421018..8e432fa27 100644 --- a/mathics/format/mathml.py +++ b/mathics/format/mathml.py @@ -154,6 +154,7 @@ def boxes_to_mathml(box, **options): ) result += "</mtr>\n" result += "</mtable>" + # print(f"gridbox: {result}") return result @@ -235,10 +236,10 @@ def is_list_interior(content): is_list_row = False if ( - len(self.items) == 3 - and self.items[0].get_string_value() == "{" # nopep8 + len(self.items) >= 3 + and self.items[0].get_string_value() == "{" and self.items[2].get_string_value() == "}" - and self.items[1].has_form("RowBox", 1) + and self.items[1].has_form("RowBox", 1, None) ): content = self.items[1].items if is_list_interior(content): @@ -254,6 +255,9 @@ def is_list_interior(content): for element in self.items: result.append(lookup_conversion_method(element, "mathml")(element, **options)) + + # print(f"mrow: {result}") + return "<mrow>%s</mrow>" % " ".join(result) From 8ffe0b57c3b2f1f70b264f30282df6ee87db6a67 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Tue, 27 Dec 2022 06:50:57 -0500 Subject: [PATCH 101/121] Remove some of the deprecated features --- mathics/builtin/box/expression.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/mathics/builtin/box/expression.py b/mathics/builtin/box/expression.py index 078b9df8c..8b5329adb 100644 --- a/mathics/builtin/box/expression.py +++ b/mathics/builtin/box/expression.py @@ -68,7 +68,7 @@ def __new__(cls, *elements, **kwargs): # the __new__ method from BuiltinElement # calls self.init. It is expected that it set - # self._elements. However, if it didn't happens, + # self._elements. However, if it didn't happen, # we set it with a default value. # There should be a better way to implement this # behaviour... @@ -96,10 +96,6 @@ def format(self, evaluation, fmt): def get_head(self): return Symbol(self.get_name()) - # Deprecated: remove eventually - def get_elements(self): - return self._elements - def get_head_name(self): return self.get_name() @@ -185,14 +181,6 @@ def to_expression(self) -> Expression: # So there should be a self.head. return Expression(Symbol(self.get_name()), *self._elements) - @property - def leaves(self): - return self.get_elements() - - @leaves.setter - def leaves(self, value): - raise ValueError("BoxExpression.elements is write protected.") - def flatten_pattern_sequence(self, evaluation) -> "BoxExpression": return self From 8b680bf76e4b9954b76fd10209e448aff717c3b5 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Tue, 27 Dec 2022 06:59:44 -0500 Subject: [PATCH 102/121] Remove leaves deprecation ... and add SymbolCompiledFunction - this is used by front-ends --- mathics/core/expression.py | 3 --- mathics/core/pattern.py | 3 --- mathics/core/symbols.py | 3 --- mathics/core/systemsymbols.py | 1 + 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 782cc0c24..b25a62d77 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -680,9 +680,6 @@ def get_elements(self): # print("Use of get_elements is deprecated. Use elements instead.") return self._elements - # Compatibily with old code. Deprecated, but remove after a little bit - get_leaves = get_elements - def get_head(self): return self._head diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py index 660d7d275..c42d6d12f 100644 --- a/mathics/core/pattern.py +++ b/mathics/core/pattern.py @@ -157,9 +157,6 @@ def get_head(self): def get_elements(self): return self.expr.get_elements() - # Compatibily with old code. Deprecated, but remove after a little bit - get_leaves = get_elements - def get_sort_key(self, pattern_sort=False) -> tuple: return self.expr.get_sort_key(pattern_sort=pattern_sort) diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 565e895b3..61f70add1 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -259,9 +259,6 @@ def get_atoms(self, include_heads=True) -> List["Atom"]: def get_elements(self): return [] - # Compatibility with old code. Deprecated, but remove after a little bit. - get_leaves = get_elements - def get_head(self) -> "Symbol": return Symbol(self.class_head_name) diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index f1bc2431d..9c37c84ce 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -47,6 +47,7 @@ SymbolColorConvert = Symbol("System`ColorConvert") SymbolColorData = Symbol("System`ColorData") SymbolCompile = Symbol("System`Compile") +SymbolCompiledFunction = Symbol("System`CompiledFunction") SymbolComplex = Symbol("System`Complex") SymbolComplexInfinity = Symbol("System`ComplexInfinity") SymbolCondition = Symbol("System`Condition") From d7bb1ea2dd9c54811e0d0f1d4d58e3bdf5cb33e5 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Tue, 27 Dec 2022 07:08:11 -0500 Subject: [PATCH 103/121] Adjust test for moved BoxExpression --- test/builtin/box/__init__.py | 1 + test/{ => builtin/box}/test_custom_boxexpression.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 test/builtin/box/__init__.py rename test/{ => builtin/box}/test_custom_boxexpression.py (96%) diff --git a/test/builtin/box/__init__.py b/test/builtin/box/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/test/builtin/box/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/test/test_custom_boxexpression.py b/test/builtin/box/test_custom_boxexpression.py similarity index 96% rename from test/test_custom_boxexpression.py rename to test/builtin/box/test_custom_boxexpression.py index e32cba955..0223a19d0 100644 --- a/test/test_custom_boxexpression.py +++ b/test/builtin/box/test_custom_boxexpression.py @@ -1,11 +1,12 @@ -from mathics.builtin.base import BoxExpression, Predefined +from test.helper import evaluate, session + +from mathics.builtin.base import Predefined +from mathics.builtin.box.expression import BoxExpression from mathics.builtin.graphics import GRAPHICS_OPTIONS from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED, A_READ_PROTECTED from mathics.core.expression import Expression from mathics.core.symbols import Symbol -from .helper import evaluate, session - SymbolCustomGraphicsBox = Symbol("CustomGraphicsBox") From f3c52a112fb9833baaeb5883cdd231f36578ed5f Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Tue, 27 Dec 2022 07:10:23 -0500 Subject: [PATCH 104/121] Exclude BoxExpression from list of Builtins --- mathics/builtin/box/expression.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mathics/builtin/box/expression.py b/mathics/builtin/box/expression.py index 8b5329adb..4c0648383 100644 --- a/mathics/builtin/box/expression.py +++ b/mathics/builtin/box/expression.py @@ -201,3 +201,6 @@ def get_option_values(self, elements, **options): option = ensure_context(option) default[option] = parse_builtin_rule(value) return default + + +DOES_NOT_ADD_BUILTIN_DEFINITION = [BoxExpression] From 2719a4cac2cba06368b6861c784f9cd2687bb59e Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Tue, 27 Dec 2022 07:18:04 -0500 Subject: [PATCH 105/121] Still need get_elements --- mathics/builtin/box/expression.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mathics/builtin/box/expression.py b/mathics/builtin/box/expression.py index 4c0648383..6b73a00a5 100644 --- a/mathics/builtin/box/expression.py +++ b/mathics/builtin/box/expression.py @@ -93,6 +93,10 @@ def format(self, evaluation, fmt): fexpr = expr.format(evaluation, fmt) return fexpr + # Deprecated: remove eventually + def get_elements(self): + return self._elements + def get_head(self): return Symbol(self.get_name()) From e022d8efdfe4cad876bc616ad16b95d29f73a2d8 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera <matera@fisica.unlp.edu.ar> Date: Tue, 27 Dec 2022 09:31:40 -0300 Subject: [PATCH 106/121] clean and improve test/consistency-and-style/test_summary_text.py (#695) Some deprecated functions were removed, and the tests for the existence of "<dl>" tags and the balancing test were split. --- .../test_summary_text.py | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/test/consistency-and-style/test_summary_text.py b/test/consistency-and-style/test_summary_text.py index 7f36b9259..cea174582 100644 --- a/test/consistency-and-style/test_summary_text.py +++ b/test/consistency-and-style/test_summary_text.py @@ -94,14 +94,6 @@ ] -def _is_builtin(var): - if var == Builtin: - return True - if hasattr(var, "__bases__"): - return any(_is_builtin(base) for base in var.__bases__) - return False - - def import_module(module_name: str): try: module = importlib.import_module("mathics.builtin." + module_name) @@ -152,7 +144,10 @@ def check_grammar(text: str): def check_well_formatted_docstring(docstr: str, instance: Builtin, module_name: str): - assert docstr.count("<dl>") >= 1 and docstr.count("</dl>") == docstr.count( + assert ( + docstr.count("<dl>") >= 1 + ), f"missing <dl> </dl> tags in {instance.get_name()} from {module_name}" + assert docstr.count("</dl>") == docstr.count( "<dl>" ), f"unbalanced <dl> </dl> tags in {instance.get_name()} from {module_name}" assert ( @@ -176,15 +171,6 @@ def check_well_formatted_docstring(docstr: str, instance: Builtin, module_name: ), f"unbalanced <url> </url> tags in {instance.get_name()} from {module_name}" -def is_builtin(var: object) -> bool: - return ( - hasattr(var, "__module__") - and var.__module__.startswith("mathics.builtin.") - and var.__module__ != "mathics.builtin.base" - and _is_builtin(var) - ) - - @pytest.mark.skipif( not os.environ.get("MATHICS_LINT"), reason="Checking done only when MATHICS_LINT=t specified", From c2341c6ffd0c2e730cb576393d18db0194e6566d Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Tue, 27 Dec 2022 11:33:07 -0500 Subject: [PATCH 107/121] apply->eval for all SymPyFunctions... Some URL line shortening and dd corrections too. --- mathics/builtin/arithfns/basic.py | 4 +- mathics/builtin/arithmetic.py | 44 +++++----- mathics/builtin/base.py | 3 - mathics/builtin/numbers/numbertheory.py | 111 +++++++++++++----------- mathics/builtin/specialfns/erf.py | 4 +- 5 files changed, 87 insertions(+), 79 deletions(-) diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index 79bfb4907..b4875bae9 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -620,12 +620,12 @@ def eval_check(self, x, y, evaluation): return SymbolComplexInfinity if isinstance(x, Complex) and x.real.is_zero: yhalf = Expression(SymbolTimes, y, RationalOneHalf) - factor = self.apply(Expression(SymbolSequence, x.imag, y), evaluation) + factor = self.eval(Expression(SymbolSequence, x.imag, y), evaluation) return Expression( SymbolTimes, factor, Expression(SymbolPower, IntegerM1, yhalf) ) - result = self.apply(Expression(SymbolSequence, x, y), evaluation) + result = self.eval(Expression(SymbolSequence, x, y), evaluation) if result is None or result != SymbolNull: return result diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index 91d114855..049955ad6 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -114,7 +114,7 @@ def get_mpmath_function(self, args): return None return getattr(mpmath, self.mpmath_name) - def apply(self, z, evaluation): + def eval(self, z, evaluation): "%(name)s[z__]" args = numerify(z, evaluation).get_sequence() @@ -314,17 +314,17 @@ class Arg(_MPMathFunction): mpmath_name = "arg" sympy_name = "arg" - def apply(self, z, evaluation, options={}): + def eval(self, z, evaluation, options={}): "%(name)s[z_, OptionsPattern[%(name)s]]" if Expression(SymbolPossibleZeroQ, z).evaluate(evaluation) is SymbolTrue: return Integer0 preference = self.get_option(options, "Method", evaluation).get_string_value() if preference is None or preference == "Automatic": - return super(Arg, self).apply(z, evaluation) + return super(Arg, self).eval(z, evaluation) elif preference == "mpmath": - return _MPMathFunction.apply(self, z, evaluation) + return _MPMathFunction.eval(self, z, evaluation) elif preference == "sympy": - return SympyFunction.apply(self, z, evaluation) + return SympyFunction.eval(self, z, evaluation) # TODO: add NumpyFunction evaluation.message( "meth", f'Arg Method {preference} not in ("sympy", "mpmath")' @@ -353,7 +353,7 @@ class Assuming(Builtin): summary_text = "set assumptions during the evaluation" attributes = A_HOLD_REST | A_PROTECTED - def apply_assuming(self, assumptions, expr, evaluation): + def eval_assuming(self, assumptions, expr, evaluation): "Assuming[assumptions_, expr_]" assumptions = assumptions.evaluate(evaluation) if assumptions is SymbolTrue: @@ -412,7 +412,7 @@ class Boole(Builtin): summary_text = "translate 'True' to 1, and 'False' to 0" attributes = A_LISTABLE | A_PROTECTED - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "%(name)s[expr_]" if expr is SymbolTrue: return Integer1 @@ -483,7 +483,7 @@ class Complex_(Builtin): summary_text = "head for complex numbers" name = "Complex" - def apply(self, r, i, evaluation): + def eval(self, r, i, evaluation): "%(name)s[r_?NumberQ, i_?NumberQ]" if isinstance(r, Complex) or isinstance(i, Complex): @@ -536,7 +536,7 @@ class ConditionalExpression(Builtin): "expr1_ ^ ConditionalExpression[expr2_, cond_]": "ConditionalExpression[expr1^expr2, cond]", } - def apply_generic(self, expr, cond, evaluation): + def eval_generic(self, expr, cond, evaluation): "ConditionalExpression[expr_, cond_]" # What we need here is a way to evaluate # cond as a predicate, using assumptions. @@ -659,7 +659,7 @@ class DirectedInfinity(SympyFunction): "DirectedInfinity[a_?NumericQ] /; N[Abs[a]] != 1": "DirectedInfinity[a / Abs[a]]", "DirectedInfinity[a_] * DirectedInfinity[b_]": "DirectedInfinity[a*b]", "DirectedInfinity[] * DirectedInfinity[args___]": "DirectedInfinity[]", - # Rules already implemented in Times.apply + # Rules already implemented in Times.eval # "z_?NumberQ * DirectedInfinity[]": "DirectedInfinity[]", # "z_?NumberQ * DirectedInfinity[a_]": "DirectedInfinity[z * a]", "DirectedInfinity[a_] + DirectedInfinity[b_] /; b == -a": ( @@ -756,17 +756,17 @@ class Im(SympyFunction): summary_text = "imaginary part" attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED - def apply_complex(self, number, evaluation): + def eval_complex(self, number, evaluation): "Im[number_Complex]" return number.imag - def apply_number(self, number, evaluation): + def eval_number(self, number, evaluation): "Im[number_?NumberQ]" return Integer0 - def apply(self, number, evaluation): + def eval(self, number, evaluation): "Im[number_]" return from_sympy(sympy.im(number.to_sympy().expand(complex=True))) @@ -857,7 +857,7 @@ class Piecewise(SympyFunction): attributes = A_HOLD_ALL | A_PROTECTED - def apply(self, items, evaluation): + def eval(self, items, evaluation): "%(name)s[items__]" result = self.to_sympy( Expression(SymbolPiecewise, *items.get_sequence()), evaluation=evaluation @@ -954,7 +954,7 @@ class PossibleZeroQ(SympyFunction): sympy_name = "_iszero" - def apply(self, expr, evaluation): + def eval(self, expr, evaluation): "%(name)s[expr_]" from sympy.matrices.utilities import _iszero @@ -1085,7 +1085,7 @@ class Rational_(Builtin): summary_text = "head for rational numbers" name = "Rational" - def apply(self, n: Integer, m: Integer, evaluation): + def eval(self, n: Integer, m: Integer, evaluation): "%(name)s[n_Integer, m_Integer]" if m.value == 1: @@ -1119,17 +1119,17 @@ class Re(SympyFunction): attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED sympy_name = "re" - def apply_complex(self, number, evaluation): + def eval_complex(self, number, evaluation): "Re[number_Complex]" return number.real - def apply_number(self, number, evaluation): + def eval_number(self, number, evaluation): "Re[number_?NumberQ]" return number - def apply(self, number, evaluation): + def eval(self, number, evaluation): "Re[number_]" return from_sympy(sympy.re(number.to_sympy().expand(complex=True))) @@ -1279,7 +1279,7 @@ class Sign(SympyFunction): "argx": "Sign called with `1` arguments; 1 argument is expected.", } - def apply(self, x, evaluation): + def eval(self, x, evaluation): "%(name)s[x_]" # Sympy and mpmath do not give the desired form of complex number if isinstance(x, Complex): @@ -1292,9 +1292,9 @@ def apply(self, x, evaluation): sympy_x = x.to_sympy() if sympy_x is None: return None - return super().apply(x, evaluation) + return super().eval(x, evaluation) - def apply_error(self, x, seqs, evaluation): + def eval_error(self, x, seqs, evaluation): "Sign[x_, seqs__]" return evaluation.message("Sign", "argx", Integer(len(seqs.get_sequence()) + 1)) diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 9e62e9ee0..4c1ad141a 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -645,9 +645,6 @@ def eval(self, z, evaluation): except: return - # FIXME: remove after all apply->eval conversions have been done - apply = eval - def get_constant(self, precision, evaluation, have_mpmath=False): try: d = get_precision(precision, evaluation) diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index 06b012689..f0da60dd0 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -19,6 +19,7 @@ from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_bool, from_python from mathics.core.convert.sympy import SympyPrime, from_sympy +from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolDivide, SymbolFalse @@ -37,7 +38,10 @@ class ContinuedFraction(SympyFunction): """ - <url>:Continued fraction: https://en.wikipedia.org/wiki/Continued_fraction</url> (<url>:SymPy: https://docs.sympy.org/latest/modules/ntheory.html#module-sympy.ntheory.continued_fraction</url>, <url>:WMA: https://reference.wolfram.com/language/ref/ContinuedFraction.html</url>) + <url>:Continued fraction: + https://en.wikipedia.org/wiki/Continued_fraction</url> (<url> + :SymPy: https://docs.sympy.org/latest/modules/ntheory.html#module-sympy.ntheory.continued_fraction</url>, <url> + :WMA: https://reference.wolfram.com/language/ref/ContinuedFraction.html</url>) <dl> <dt>'ContinuedFraction[$x$, $n$]' <dd>generate the first $n$ terms in the continued fraction representation of $x$. @@ -60,13 +64,13 @@ class ContinuedFraction(SympyFunction): summary_text = "continued fraction expansion" sympy_name = "continued_fraction" - def apply_1(self, x, evaluation): + def eval(self, x, evaluation: Evaluation): "%(name)s[x_]" - return super().apply(x, evaluation) + return super().eval(x, evaluation) - def apply_2(self, x, n, evaluation): + def eval_with_n(self, x, n: Integer, evaluation: Evaluation): "%(name)s[x_, n_Integer]" - py_n = n.to_python() + py_n = n.value sympy_x = x.to_sympy() it = sympy.continued_fraction_iterator(sympy_x) return from_sympy([next(it) for _ in range(py_n)]) @@ -102,7 +106,7 @@ class Divisors(Builtin): attributes = A_LISTABLE | A_PROTECTED summary_text = "integer divisors" - def apply(self, n, evaluation): + def eval(self, n:Integer, evaluation: Evaluation): "Divisors[n_Integer]" if n == Integer0: return None @@ -131,7 +135,7 @@ def apply(self, n, evaluation): # # attributes = A_LISTABLE | A_PROTECTED # -# def apply(self, ns, evaluation): +# def eval(self, ns, evaluation: Evaluation): # 'ExtendedGCD[ns___Integer]' # # ns = ns.get_sequence() @@ -184,9 +188,9 @@ class EulerPhi(SympyFunction): summary_text = "Euler totient function" sympy_name = "totient" - def apply(self, n, evaluation): + def eval(self, n: Integer, evaluation: Evaluation): "EulerPhi[n_Integer]" - return super().apply(abs(n), evaluation) + return super().eval(abs(n), evaluation) class FactorInteger(Builtin): @@ -214,7 +218,7 @@ class FactorInteger(Builtin): # TODO: GausianIntegers option # e.g. FactorInteger[5, GaussianIntegers -> True] - def apply(self, n, evaluation): + def eval(self, n, evaluation: Evaluation): "FactorInteger[n_]" if isinstance(n, Integer): @@ -238,7 +242,7 @@ def apply(self, n, evaluation): return evaluation.message("FactorInteger", "exact", n) -def _fractional_part(self, n, expr, evaluation): +def _fractional_part(self, n, expr, evaluation: Evaluation): n_sympy = n.to_sympy() if n_sympy.is_constant(): if n_sympy >= 0: @@ -291,12 +295,12 @@ class FractionalPart(Builtin): attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_READ_PROTECTED | A_PROTECTED summary_text = "fractional part of a number" - def apply(self, n, evaluation): + def eval(self, n, evaluation: Evaluation): "FractionalPart[n_]" expr = Expression(SymbolFractionalPart, n) return _fractional_part(self.__class__.__name__, n, expr, evaluation) - def apply_2(self, n, evaluation): + def eval_complex_n(self, n, evaluation: Evaluation): "FractionalPart[n_Complex]" expr = Expression(SymbolFractionalPart, n) n_real = Expression(SymbolRe, n).evaluate(evaluation) @@ -313,7 +317,9 @@ def apply_2(self, n, evaluation): class FromContinuedFraction(SympyFunction): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/FromContinuedFraction.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/FromContinuedFraction.html</url> <dl> <dt>'FromContinuedFraction[$list$]' @@ -332,7 +338,7 @@ class FromContinuedFraction(SympyFunction): summary_text = "reconstructs a number from its continued fraction representation" sympy_name = "continued_fraction_reduce" - def apply_1(self, expr, evaluation): + def eval(self, expr, evaluation: Evaluation): "%(name)s[expr_List]" nums = expr.to_python() if all(isinstance(i, int) for i in nums): @@ -341,13 +347,16 @@ def apply_1(self, expr, evaluation): class MantissaExponent(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/MantissaExponent.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/MantissaExponent.html</url> <dl> - <dt>'MantissaExponent[$n$]' - <dd>finds a list containing the mantissa and exponent of a given number $n$. - <dt>'MantissaExponent[$n$, $b$]' - <dd>finds the base b mantissa and exponent of $n$. + <dt>'MantissaExponent[$n$]' + <dd>finds a list containing the mantissa and exponent of a given number $n$. + + <dt>'MantissaExponent[$n$, $b$]' + <dd>finds the base b mantissa and exponent of $n$. </dl> >> MantissaExponent[2.5*10^20] @@ -415,7 +424,27 @@ class MantissaExponent(Builtin): } summary_text = "decomposes numbers as mantissa and exponent" - def apply(self, n, b, evaluation): + def eval(self, n, evaluation: Evaluation): + "MantissaExponent[n_]" + n_sympy = n.to_sympy() + expr = Expression(SymbolMantissaExponent, n) + + if isinstance(n.to_python(), complex): + evaluation.message("MantissaExponent", "realx", n) + return expr + # Handle Input with special cases such as PI and E + if n_sympy.is_constant(): + temp_n = eval_N(n, evaluation) + py_n = temp_n.to_python() + else: + return expr + + base_exp = int(mpmath.log10(py_n)) + exp = Integer((base_exp + 1) if base_exp >= 0 else base_exp) + + return ListExpression(Expression(SymbolDivide, n, Integer10**exp), exp) + + def eval_with_b(self, n, b, evaluation: Evaluation): "MantissaExponent[n_, b_]" # Handle Input with special cases such as PI and E n_sympy, b_sympy = n.to_sympy(), b.to_sympy() @@ -447,26 +476,6 @@ def apply(self, n, b, evaluation): exp = Integer((base_exp + 1) if base_exp >= 0 else base_exp) return ListExpression(Expression(SymbolDivide, n, b**exp), exp) - def apply_2(self, n, evaluation): - "MantissaExponent[n_]" - n_sympy = n.to_sympy() - expr = Expression(SymbolMantissaExponent, n) - - if isinstance(n.to_python(), complex): - evaluation.message("MantissaExponent", "realx", n) - return expr - # Handle Input with special cases such as PI and E - if n_sympy.is_constant(): - temp_n = eval_N(n, evaluation) - py_n = temp_n.to_python() - else: - return expr - - base_exp = int(mpmath.log10(py_n)) - exp = Integer((base_exp + 1) if base_exp >= 0 else base_exp) - - return ListExpression(Expression(SymbolDivide, n, Integer10**exp), exp) - class NextPrime(Builtin): """ @@ -504,7 +513,7 @@ class NextPrime(Builtin): } summary_text = "closest, smallest prime number" - def apply(self, n, k, evaluation): + def eval(self, n, k: Integer, evaluation: Evaluation): "NextPrime[n_?NumberQ, k_Integer]" def to_int_value(x): @@ -541,7 +550,8 @@ def to_int_value(x): class PartitionsP(SympyFunction): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/PartitionsP.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/PartitionsP.html</url> <dl> <dt>'PartitionsP[$n$]' @@ -556,14 +566,15 @@ class PartitionsP(SympyFunction): summary_text = "number of unrestricted partitions" sympy_name = "npartitions" - def apply(self, n, evaluation): + def eval(self, n, evaluation: Evaluation): "PartitionsP[n_Integer]" - return super().apply(n, evaluation) + return super().eval(n, evaluation) class Prime(SympyFunction): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Prime.html</url> + <url>:WMA link: + https://reference.wolfram.com/language/ref/Prime.html</url> <dl> <dt>'Prime[$n$]' @@ -595,7 +606,7 @@ class Prime(SympyFunction): attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED summary_text = "n-esim prime number" - def apply(self, n, evaluation): + def eval(self, n, evaluation: Evaluation): "Prime[n_]" return from_sympy(SympyPrime(n.to_sympy())) @@ -637,7 +648,7 @@ class PrimePi(SympyFunction): # TODO: Traditional Form - def apply(self, n, evaluation): + def eval(self, n, evaluation: Evaluation): "PrimePi[n_?NumericQ]" result = sympy.ntheory.primepi(eval_N(n, evaluation).to_python()) return Integer(result) @@ -692,7 +703,7 @@ class PrimePowerQ(Builtin): = True """ - def apply(self, n, evaluation): + def eval(self, n, evaluation: Evaluation): "PrimePowerQ[n_]" n = n.get_int_value() if n is None: @@ -765,7 +776,7 @@ class RandomPrime(Builtin): # TODO: Use random state as in other randomised methods within mathics - def apply(self, interval, n, evaluation): + def eval(self, interval, n, evaluation: Evaluation): "RandomPrime[interval_List, n_]" if not isinstance(n, Integer): diff --git a/mathics/builtin/specialfns/erf.py b/mathics/builtin/specialfns/erf.py index b88388582..eba6f9267 100644 --- a/mathics/builtin/specialfns/erf.py +++ b/mathics/builtin/specialfns/erf.py @@ -150,11 +150,11 @@ class InverseErf(_MPMathFunction): "Derivative[1][InverseErf]": "Sqrt[Pi] Exp[InverseErf[#]^2] / 2 &", } - def apply(self, z, evaluation): + def eval(self, z, evaluation): "%(name)s[z__]" try: - return super(InverseErf, self).apply(z, evaluation) + return super(InverseErf, self).eval(z, evaluation) except ValueError as exc: if str(exc) == "erfinv(x) is defined only for -1 <= x <= 1": return From 192d7b9f77944f201d035cd2afe283dbe17ce771 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Tue, 27 Dec 2022 11:41:40 -0500 Subject: [PATCH 108/121] Shorten URL lines --- mathics/builtin/numbers/numbertheory.py | 2 +- mathics/builtin/specialfns/erf.py | 52 +++++++++++++++++++++---- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/mathics/builtin/numbers/numbertheory.py b/mathics/builtin/numbers/numbertheory.py index f0da60dd0..a98d7532c 100644 --- a/mathics/builtin/numbers/numbertheory.py +++ b/mathics/builtin/numbers/numbertheory.py @@ -106,7 +106,7 @@ class Divisors(Builtin): attributes = A_LISTABLE | A_PROTECTED summary_text = "integer divisors" - def eval(self, n:Integer, evaluation: Evaluation): + def eval(self, n: Integer, evaluation: Evaluation): "Divisors[n_Integer]" if n == Integer0: return None diff --git a/mathics/builtin/specialfns/erf.py b/mathics/builtin/specialfns/erf.py index eba6f9267..a79aac241 100644 --- a/mathics/builtin/specialfns/erf.py +++ b/mathics/builtin/specialfns/erf.py @@ -11,7 +11,13 @@ class Erf(_MPMathMultiFunction): """ - <url>:Error function: https://en.wikipedia.org/wiki/Error_function</url> (<url>:SymPy: https://docs.sympy.org/latest/modules/functions/special.html#sympy.functions.special.error_functions.erf</url>, <url>:WMA: https://reference.wolfram.com/language/ref/Erf.html</url>) + <url> + :Error function: + https://en.wikipedia.org/wiki/Error_function</url> (<url> + :SymPy: + https://docs.sympy.org/latest/modules/functions/special.html#sympy.functions.special.error_functions.erf</url>, <url> + :WMA: https://reference.wolfram.com/language/ref/Erf.html</url>) + <dl> <dt>'Erf[$z$]' <dd>returns the error function of $z$. @@ -51,7 +57,12 @@ class Erf(_MPMathMultiFunction): class Erfc(_MPMathFunction): """ - <url>:Complementary Error function:.https://en.wikipedia.org/wiki/Error_function</url> (<url>:SymPy: https://docs.sympy.org/latest/modules/functions/special.html#sympy.functions.special.error_functions.erfc</url>, <url>:WMA: https://reference.wolfram.com/language/ref/Erfc.html</url>) + <url> + :Complementary Error function: + https://en.wikipedia.org/wiki/Error_function</url> (<url> + :SymPy: https://docs.sympy.org/latest/modules/functions/special.html#sympy.functions.special.error_functions.erfc</url>, <url> + :WMA: + https://reference.wolfram.com/language/ref/Erfc.html</url>) <dl> <dt>'Erfc[$z$]' @@ -77,7 +88,13 @@ class Erfc(_MPMathFunction): class FresnelC(_MPMathFunction): """ - <url>:Fresnel integral: https://en.wikipedia.org/wiki/Fresnel_integral</url> (<url>:mpmath: https://mpmath.org/doc/current/functions/expintegrals.html?highlight=fresnelc#mpmath.fresnelc</url>, <url>:WMA: https://reference.wolfram.com/language/ref/FresnelC.html</url>) + <url> + :Fresnel integral: + https://en.wikipedia.org/wiki/Fresnel_integral</url> (<url> + :mpmath: + https://mpmath.org/doc/current/functions/expintegrals.html?highlight=fresnelc#mpmath.fresnelc</url>, <url> + :WMA: + https://reference.wolfram.com/language/ref/FresnelC.html</url>) <dl> <dt>'FresnelC[$z$]' <dd>is the Fresnel C integral $C$($z$). @@ -100,11 +117,17 @@ class FresnelC(_MPMathFunction): class FresnelS(_MPMathFunction): """ - <url>:Fresnel integral: https://en.wikipedia.org/wiki/Fresnel_integral</url> (<url>:mpmath: https://mpmath.org/doc/current/functions/expintegrals.html#mpmath.fresnels</url>, <url>:WMA: https://reference.wolfram.com/language/ref/FresnelS.html</url>) + <url> + :Fresnel integral: + https://en.wikipedia.org/wiki/Fresnel_integral</url> (<url> + :mpmath: + https://mpmath.org/doc/current/functions/expintegrals.html#mpmath.fresnels</url>, <url> + :WMA: + https://reference.wolfram.com/language/ref/FresnelS.html</url>) <dl> - <dt>'FresnelS[$z$]' - <dd>is the Fresnel S integral $S$($z$). + <dt>'FresnelS[$z$]' + <dd>is the Fresnel S integral $S$($z$). </dl> >> FresnelS[{0, Infinity}] @@ -124,7 +147,14 @@ class FresnelS(_MPMathFunction): class InverseErf(_MPMathFunction): """ - <url>:Inverse error function: https://en.wikipedia.org/wiki/Error_function#Inverse_functions</url> (<url>:SymPy: https://docs.sympy.org/latest/modules/functions/special.html?highlight=erfinv#sympy.functions.special.error_functions.erfinv</url>, <url>:WMA: https://reference.wolfram.com/language/ref/InverseErf.html</url>) + <url> + :Inverse error function: + https://en.wikipedia.org/wiki/Error_function#Inverse_functions</url> (<url> + :SymPy: + https://docs.sympy.org/latest/modules/functions/special.html?highlight=erfinv#sympy.functions.special.error_functions.erfinv</url>, <url> + :WMA: + https://reference.wolfram.com/language/ref/InverseErf.html</url>) + <dl> <dt>'InverseErf[$z$]' <dd>returns the inverse error function of $z$. @@ -164,7 +194,13 @@ def eval(self, z, evaluation): class InverseErfc(_MPMathFunction): """ - <url>:Complementary error function: https://en.wikipedia.org/wiki/Error_function#Complementary_error_function</url> (<url>:SymPy: https://docs.sympy.org/latest/modules/functions/special.html?highlight=erfinv#sympy.functions.special.error_functions.erfcinv</url>, <url>:WMA: https://reference.wolfram.com/language/ref/InverseErfc.html</url>) + <url> + :Complementary error function: + https://en.wikipedia.org/wiki/Error_function#Complementary_error_function</url> (<url> + :SymPy: + https://docs.sympy.org/latest/modules/functions/special.html?highlight=erfinv#sympy.functions.special.error_functions.erfcinv</url>, <url> + :WMA: + https://reference.wolfram.com/language/ref/InverseErfc.html</url>) <dl> <dt>'InverseErfc[$z$]' <dd>returns the inverse complementary error function of $z$. From b66935c585f9f1d08f9b1b65d0d5ca9f26a3adaa Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Wed, 28 Dec 2022 07:13:10 -0500 Subject: [PATCH 109/121] Add ImageTake rows, row+cols forms --- mathics/builtin/drawing/image.py | 118 ++++++++++++++++++++++++++----- 1 file changed, 100 insertions(+), 18 deletions(-) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index bfaa64348..ecb20d00c 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -1636,7 +1636,7 @@ def eval(self, image, stype: String, evaluation: Evaluation): class ImageTake(_ImageBuiltin): """ - <url>:WMA link: + Crop Image <url>:WMA link: https://reference.wolfram.com/language/ref/ImageTake.html</url> <dl> <dt>'ImageTake[$image$, $n$]' @@ -1659,10 +1659,28 @@ class ImageTake(_ImageBuiltin): Now crop to the include the lower half of that image: >> ImageTake[alice, -244] = -Image- + + Just the text around the hat: + >> ImageTake[alice, {40, 150}, {500, 600}] + = -Image- + """ summary_text = "crop image" + def _slice(self, image, i1: Integer, i2: Integer, axis): + n = image.pixels.shape[axis] + py_i1 = min(max(i1.value - 1, 0), n - 1) + py_i2 = min(max(i2.value - 1, 0), n - 1) + + def flip(pixels): + if py_i1 > py_i2: + return numpy_flip(pixels, axis) + else: + return pixels + + return slice(min(py_i1, py_i2), 1 + max(py_i1, py_i2)), flip + def eval(self, image, n: Integer, evaluation: Evaluation): "ImageTake[image_Image, n_Integer]" py_n = n.value @@ -1683,31 +1701,95 @@ def eval(self, image, n: Integer, evaluation: Evaluation): return Image(pixels, image.color_space, pillow=pillow) - def _slice(self, image, i1: Integer, i2: Integer, axis): - n = image.pixels.shape[axis] - py_i1 = min(max(i1.value - 1, 0), n - 1) - py_i2 = min(max(i2.value - 1, 0), n - 1) + def eval_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): + "ImageTake[image_Image, {r1_Integer, r2_Integer}]" - def flip(pixels): - if py_i1 > py_i2: - return numpy_flip(pixels, axis) - else: - return pixels + first_row = r1.value + last_row = r2.value - return slice(min(py_i1, py_i2), 1 + max(py_i1, py_i2)), flip + max_row, max_col = image.pixels.shape[:2] + adjusted_first_row = ( + min(first_row, max_row) if first_row > 0 else max(0, max_row + first_row) + ) + adjusted_last_row = ( + min(last_row, max_row) if last_row > 0 else max(0, max_row + first_row) + ) - def eval_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): - "ImageTake[image_Image, {r1_Integer, r2_Integer}]" - s, f = self._slice(image, r1, r2, 0) - return Image(f(image.pixels[s]), image.color_space) + # More complicated in that it reverses the data? + # if adjusted_first_row > adjusted_last_row: + # adjusted_first_row, adjusted_last_row = adjusted_last_row, adjusted_first_row + + pixels = image.pixels[adjusted_first_row:adjusted_last_row] + + if hasattr(image, "pillow"): + box_coords = (0, adjusted_first_row, max_col, adjusted_last_row) + pillow = image.pillow.crop(box_coords) + pixels = numpy.asarray(pillow) + return Image(pixels, image.color_space, pillow=pillow) + + pixels = image.pixels[adjusted_first_row:adjusted_last_row] + return Image(pixels, image.color_space, pillow=pillow) def eval_rows_cols( self, image, r1: Integer, r2: Integer, c1: Integer, c2: Integer, evaluation ): "ImageTake[image_Image, {r1_Integer, r2_Integer}, {c1_Integer, c2_Integer}]" - sr, fr = self._slice(image, r1, r2, 0) - sc, fc = self._slice(image, c1, c2, 1) - return Image(fc(fr(image.pixels[sr, sc])), image.color_space) + + first_row = r1.value + last_row = r2.value + first_col = c1.value + last_col = c2.value + + max_row, max_col = image.pixels.shape[:2] + adjusted_first_row = ( + min(first_row, max_row) if first_row > 0 else max(0, max_row + first_row) + ) + adjusted_last_row = ( + min(last_row, max_row) if last_row > 0 else max(0, max_row + last_row) + ) + adjusted_first_col = ( + min(first_col, max_col) if first_col > 0 else max(0, max_col + first_col) + ) + adjusted_last_col = ( + min(last_col, max_col) if last_col > 0 else max(0, max_col + last_col) + ) + + # if adjusted_first_row > adjusted_last_row: + # adjusted_first_row, adjusted_last_row = adjusted_last_row, adjusted_first_row + + # if adjusted_first_col > adjusted_last_col: + # adjusted_first_col, adjusted_last_col = adjusted_last_col, adjusted_first_col + + pixels = image.pixels[ + adjusted_first_col:adjusted_last_col, adjusted_last_row:adjusted_last_row + ] + + if hasattr(image, "pillow"): + box_coords = ( + adjusted_first_col, + adjusted_first_row, + adjusted_last_col, + adjusted_last_row, + ) + pillow = image.pillow.crop(box_coords) + pixels = numpy.asarray(pillow) + return Image(pixels, image.color_space, pillow=pillow) + + pixels = image.pixels[adjusted_first_row:adjusted_last_row] + return Image(pixels, image.color_space, pillow=pillow) + + # def eval_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): + # "ImageTake[image_Image, {r1_Integer, r2_Integer}]" + # s, f = self._slice(image, r1, r2, 0) + # return Image(f(image.pixels[s]), image.color_space) + + # def eval_rows_cols( + # self, image, r1: Integer, r2: Integer, c1: Integer, c2: Integer, evaluation + # ): + # "ImageTake[image_Image, {r1_Integer, r2_Integer}, {c1_Integer, c2_Integer}]" + # sr, fr = self._slice(image, r1, r2, 0) + # sc, fc = self._slice(image, c1, c2, 1) + # return Image(fc(fr(image.pixels[sr, sc])), image.color_space) class PixelValue(_ImageBuiltin): From 2c56a325d95166ebc61b85a8a1415a4546e50a95 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Wed, 28 Dec 2022 09:55:25 -0500 Subject: [PATCH 110/121] streamline a little & document TODO's. --- mathics/builtin/drawing/image.py | 52 ++++++++++++++++++++++---------- mathics/core/systemsymbols.py | 1 + 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index ecb20d00c..2f73ba073 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -40,7 +40,7 @@ from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull, SymbolTrue -from mathics.core.systemsymbols import SymbolRule +from mathics.core.systemsymbols import SymbolImage, SymbolRule from mathics.eval.image import ( convolve, extract_exif, @@ -175,9 +175,6 @@ def eval(self, path: String, evaluation: Evaluation): return ListExpression(*image_list_expression) -# image math - - class _ImageArithmetic(_ImageBuiltin): messages = {"bddarg": "Expecting a number, image, or graphics instead of `1`."} @@ -395,9 +392,6 @@ def eval(self, minval, maxval, w, h, evaluation, options): return Image(data, cs) -# simple image manipulation - - class ImageResize(_ImageBuiltin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/ImageResize.html</url> @@ -711,9 +705,6 @@ def eval(self, image, w: Integer, h: Integer, evaluation: Evaluation): return from_python(parts) -# simple image filters - - class ImageAdjust(_ImageBuiltin): """ @@ -1668,7 +1659,15 @@ class ImageTake(_ImageBuiltin): summary_text = "crop image" - def _slice(self, image, i1: Integer, i2: Integer, axis): + # FIXME: this probably should be moved out since WMA docs + # suggest this kind of thing is done across many kinds of + # images. + def _image_slice(self, image, i1: Integer, i2: Integer, axis): + """ + Extracts a slice of an image and return a slice + indicting a slice, a function flip, that will + reverse the pixels in an image if necessary. + """ n = image.pixels.shape[axis] py_i1 = min(max(i1.value - 1, 0), n - 1) py_i2 = min(max(i2.value - 1, 0), n - 1) @@ -1681,7 +1680,16 @@ def flip(pixels): return slice(min(py_i1, py_i2), 1 + max(py_i1, py_i2)), flip - def eval(self, image, n: Integer, evaluation: Evaluation): + # The reason it is hard to make a rules that turn Image[image, n], + # or Image[, {r1, r2} into the generic form Image[image, {r1, r2}, + # {c1, c2}] there can be negative numbers, e.g. -n. Also, that + # missing values, in particular r2 and c2, when filled out can be + # dependent on the size of the image. + + # FIXME: create common functions to process ranges. + # FIXME: fix up and use _image_slice. + + def eval_n(self, image, n: Integer, evaluation: Evaluation): "ImageTake[image_Image, n_Integer]" py_n = n.value max_y, max_x = image.pixels.shape[:2] @@ -1778,6 +1786,8 @@ def eval_rows_cols( pixels = image.pixels[adjusted_first_row:adjusted_last_row] return Image(pixels, image.color_space, pillow=pillow) + # Older code we can remove after we condence existing code that looks like this + # # def eval_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): # "ImageTake[image_Image, {r1_Integer, r2_Integer}]" # s, f = self._slice(image, r1, r2, 0) @@ -2076,13 +2086,23 @@ def test(self, expr): class Image(Atom): class_head_name = "System`Image" - def __init__(self, pixels, color_space, metadata={}, **kwargs): - if "pillow" in kwargs: - self.pillow = kwargs.pop("pillow") + # FIXME: pixels should be optional if pillow is provided. + def __init__(self, pixels, color_space, pillow=None, metadata={}, **kwargs): super(Image, self).__init__(**kwargs) + + if pillow is not None: + self.pillow = pillow + + self.pixels = pixels + if len(pixels.shape) == 2: pixels = pixels.reshape(list(pixels.shape) + [1]) + + # FIXME: assigning pixels should be done lazily on demand. + # Turn pixels into a property? Include a setter? + self.pixels = pixels + self.color_space = color_space self.metadata = metadata @@ -2093,7 +2113,7 @@ def __init__(self, pixels, color_space, metadata={}, **kwargs): # event that different objects have the same Python value self.hash = hash( ( - "Image", + SymbolImage, self.pixels.tobytes(), self.color_space, frozenset(self.metadata.items()), diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 9c37c84ce..978ee2f52 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -97,6 +97,7 @@ SymbolHue = Symbol("System`Hue") SymbolIf = Symbol("System`If") SymbolIm = Symbol("System`Im") +SymbolImage = Symbol("System`Image") SymbolImplies = Symbol("System`Implies") SymbolIn = Symbol("System`In") SymbolIndeterminate = Symbol("System`Indeterminate") From 25d12cb10d66e15d4853013ffba6e56c4f1e07ef Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Wed, 28 Dec 2022 13:55:02 -0500 Subject: [PATCH 111/121] More direct Image writing via saved PIL.Image Some Exports were not working... --- mathics/builtin/drawing/image.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index 2f73ba073..f63e99465 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -2226,7 +2226,12 @@ def grayscale(self): return self.color_convert("Grayscale") def pil(self): + + if hasattr(self, "pillow") and self.pillow is not None: + return self.pillow + # see https://pillow.readthedocs.io/en/stable/handbook/concepts.html + n = self.channels() if n == 1: From c34682ba96850dc17eb3565bd373ec21ebb9e816 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Thu, 29 Dec 2022 00:15:19 -0500 Subject: [PATCH 112/121] Intended to go along with Image stuff... Image has an optional depencency on pyocr for TextRecognize TextRecognize.png: data file for trying out OCR recognition --- mathics/data/ExampleData/TextRecognize.png | Bin 0 -> 9691 bytes requirements-full.txt | 1 + 2 files changed, 1 insertion(+) create mode 100644 mathics/data/ExampleData/TextRecognize.png diff --git a/mathics/data/ExampleData/TextRecognize.png b/mathics/data/ExampleData/TextRecognize.png new file mode 100644 index 0000000000000000000000000000000000000000..3b1d25c4016d872eae7cb8451e9e0882fa792ed0 GIT binary patch literal 9691 zcmdUVXFSz$*!R&Ogi58%G|0**dxpr~A*<{ydpk;!osp4Mgk&6hA0r_S4#|#V?~y$Z z_vIdMp3n2S-#srLFVZ>3|NMWy>vvt(cU=EqRb^Q+5^53z0zoGCR9YQ@AUJ^kTV5c9 ze{Xj1l)<la&XRJP7vRU|g6aFyw_Tp-xM(<-ySTq}GDBF{JJ^|VIGZ?`nb|vAI=HNz ztrtTeZXx8PA8UFfuZ+6uTpc)QSm&&{b*ppme1@^?W0XwZpwgrMpO(g9Ud|{r4GwPO zs-k8?<MaV|D;S-mQ}m+7T2r&oKmD^>;cJ%<W-oV-j>lW2zf$uq&f-NPRf->$%+=0y zEZ21;KP-7B&(iF?+La>Qkt|4AG89%;#v2tCm8Vxh?jI?3aKYQ#n}>&|u%g1EJ(gvD z`=Isv+V4yS+u_pSR8enrw3n;h{(MhHqL{BxnRTD>_F~`t%=h8pW;5*{*$t{=dCb}` zUBCV=BV)>>??gK^GBVp~ru{~^jB<+5lg>}PX|7KtFEFNFTC$*%cp1x~7boaEYl*_+ z@nzB{X(vZS{QMQn&v7Bw*b;<XS!FV!f+?A2b&N_a)fW5nbJ6xg#rJ6VY{yqeYmRnC z9BL01b5$OtX6Ne^8UJi)5nJMVdr4B{Jg0Q}$-|xHp{8T}VU_b7dEa-fsQ?PG&x(mW z`c+PWO+ggB=}$<%YvmG3JbikhHIgCQq&4F1-Mg1=+_=!N(3`DW>%mP%!}k+|VZ$Bd z<>etD7Mz;2x)HR3KXz6|g2TeTdaO^DI^a@aQ&vl``1Rf0x!KMn%{-kb`_bz4b~cpd zQg3hXP^lH=>({TZvZ%jhQu?G?Vy?vJFs2wlNM_mh{hC4?d!s22_lpL^5gsq-GJmP7 zt7~g%AQEDIw$OP_SzSZJb1A=qD^W4e5KW!YlaSAmDgQCuW}qO@KIxL=`#FCvf|Zd9 z6#^1fI6c9B3K~8|g70!MOxE+=U)no6l|J5AlN_z})JPHaekLd&@C{Dp+xzzyT-PU` z%*@Y!lmEz?dHc?tJ{58N*Y=NW*@*283?~}{0|T%7s(xmydWKB6x<3Rdj)a6n9d^-< z!x^DZjt@yx>FMcNd3noJ9q(uUnQn`@_PITt6XmtLn*WK{CJS-x(xuO_>efHsUnWt7 zC~8W*yQl_N$Y=NFtks_w=4RN9l#{3$RJ*9<>s37SXEmrUr55+AeO_k$QC>lz_rQ92 zu;{z<TvvN<uLi+)Km36_OQs@I%<rY`=g6@tXIWnBKTrEmMn<0A-nk^#Sno?zRaV}Q zY18~xPR#h$Lrh0MUEF;&xwyDkeFhV+y|AzV*O%K4r6b_N`>?Mheuro9(>^ky=jr9e zN<%~AI^WHh9|m#IBv{07)^|K}dzMZVTUJ)~d%=Z7)y>UqaVk>$S%m}cfeS5!y1k`E zMpswYOd-O1cU5b54X54M*m%0V=z9vR0gbSER#sMdg7&sHx#s5P9EJD?ED~Dz&$U=S zCKlS+*<Jgbo{{lUUH;}pLXO$C7-qK2ScvfNxXiC#zlDWeT-k;wrRH~Xb91+kjv5eH zL(v4TnlQe-ge3~CaX}vKImW7Mep7u-C+voJwYzG7kd%@l@Yw9+PlSrekc}i94`T59 z{3P^xU_r*?0K#+U&b`{-FcT3K<)#kbTEnFkn_;dCMU+@zqf=8Ol9Ff&3JQh~hs$h; z8lcv=k~%f>pI_6{)4R3#+jHS}W^hQzZ2^HBF>UAkQ&Q*<LMh}BurQfBtnq?zu!3_u zt<xo6v$Cvb#5-*HHdkxboK}W;-;vSCpbXI@eSD|OrU+Ho3Jwk)Mn=*KrgNN3hH{qP zCFw4=({mA`>+kP>WH-E}(x(M|MeK0-;cQ1@lSP_uOVl0N(xQVmlkY=9&NMVP-y0~- z$jEq|Y;sIaPL7a%@}%dB6w!ChoFADA@d=Ntt`HLw-(_a*EjCktwEmdUk^Ur9O#QGW zf(`>c%M!ZALyN9Iaa84zfzTklugA~Z{P{-U9}`1aV&2(a-et>oHqjz&w8lN#YkTp= zMZ#*A1xX1BiHPZhGGD)zaGF+VNCIxlH*%llr>mri1%^!RtxqAOrKL^vvSWXk@=UC* zl1rGHnwIZE!BP4{Ihi;(s6Yjso{ONzzgt2n5k)4glS@l_FaDk%DmEkS%~ERW><o8Z z>VIj6Z~da4Aa-5y!n#kl=n^8e)NN&0EklOv%$YMfHEtX^IXU_@ZsBm26H7}9US2|? zUR&ePhmMXmv3WX0Z+`uHTJ648(8+_XE~UGARr=K{E;!w;5-}<&Drh_B{81>*wzjsw zkfxCl8VU-E=;-JK9y5}OiHReq_)`1PPf&=z3k-x;>W(07F4NO@eU*#hq7MI)uNM!A zakkKX%}`xk-Ey>=7iu@tv@QCy`3jnMh=&vwavkquk?FC<Ki(4mMSI6LG)VmT@#DWw zKFUJy@i<lpd0bmuQli0$-inr2R_-b~-k8BGmJJwcKp76-Eg>YM{sxUK*P(7-X2C^> z*KtzX?hyJI3mY37uUY$5s9B4yWHnftmVa?Yg|_eEj+Q*jHhve!mZF^PwX>X<Uk8QL zSLtYS{@Z!d>sMLHp`?h3)x^ZahK10EsOJ^-`I3^7vFh@4;(q$4y+hBz%q$bOmi@q} zF0XHW*al^_#BijIG8Fxoknjz<jgzyphO)Bq@HzC}o`)7h*_IM-=(le+GdY=l<;s;m zkPpSp$=fKv0agx<d{sNj)6*+dU))FXjHQ`z-FP1sHk}!7m@XZBC30)*?b~xu^f{V2 z>gAYzl{*o!u~z{np5&-!7AwVKqGMyFH8rn7Ar7@H59H}et&O1*;e=4N8zk~9aG`FQ z^|c!Jwb=n9WTzYMu=k&UFZ<g|lM5x4+y_KMLqlo+#NykAR8;XI%<ZBJ0<^TWr9r#X zEp2VY%*@%e7?eQ`p~Od4ElXUT=m)_ThMPCr071)!_EdVOwu%5tva#vdc@6_b#fuKL z>x8lqv|j*GW7&116B2GrPE9QgpcnwI9%q+STHOHnrhk3ci}cdv%Z-7gR9rVM3J3~L zEG#6hr20X*BaNcxp~x<fG7Y%?*Y9A%3yg0IU;NM8|Ic6cV}<;Ga3%icPU^%8U4$h~ z25w5;J=k)&WA5BiLM{cX@tF9P=?$O!cq5li1sh^dxa;-5yc{HT^iJ{c+uL7Z5)?e| zFB49^v4NQ>$**_17X}sZiQge2BjYTBfFPTTy4R{dug!=$xADabY^twj!E!{l#ft5c zJ+7{)C!-ZnSW=SVJQsnqvW0}v_GF~QL)WQtymPzNiJzZe-vA@}QOIGewi!wi@yg9D z9om_Wo;YqaJ~=u4-Uu@=hMtPG!>}9NwEon*-z^ljq~7^-1(G(FTJ|Js8|I+;&?c=9 zsChU1j2*8&v$kfnz^0lE70(C=3m4fA8QU31N{T?5pMNn{yH@G3P767X^bCWgl<Kl% z{wX0(zeOk|Ursc8bcj0}92q$`6OSqJ*_S`qUTR7ac1P$`*l(NkmBB59T)jkFH=uoO z?$6b^EDyeZ|NbfLvGX07%{N2zfl1$P>0RdR@!4llfPbn5`a{g>S#732M$tn2_HXB^ z$2#+`T)wQ`vyh6q%ox-5HrM9K>R5P(t8a?XwxHAWE#N{(M^jkWmZ%Z#SD<E@wOLE# zNi?vz7Y+{Czg5l&BA&N_*Lg1&iyzGQ>c`h_c8ye4$C<RpqW*1;bp49Dv$ug+k2Jvb zPR`FuSkA{NYy^IZZI7CciP0D=wF=!45)z_Dp5TY8d2RZmqoVd@^;Nr18D5qh4ZYvT zKvJaXNIBixz`!}3RIRRnE0mPF<+g&dk;hpay-VA`ikW+#LLN1W;097Rxy(Dh(PDrr zwM@5B61)!%e%X;wlq}*IvN1E7z-_`>X6;$%IAuQDh3DMaamzEP38@(K3WA>15`IYW z^5p~&yf|nPkVj+^b6s>d1)Zb|xCI0RKBOMEYQ3vnJO{AsbGRZ>Za=CFXA1i^+uz8X zcM*ar4nnQn=t>h;0h*8?=v*C_j7_U=fSbxaefrrDjePZQoDry%T-5C+0<MdX;1EsM z43WtxDL2IZ*vo7N&Wwk0$~aB_l(V(9)v5R6y{8a&ZhX28QMYG&m%`|0qOd#p_R;{R zg6zDS{7W7)q~dyWXgs9B+4`g6kFbjvjcnx-(>5MBy>tKu;BGMsy%s%H&dlX@BaM^I z3RZ)K1Y%-Jy7hi%*Vl>v<V-a;yk;meZstMsqj3IcU$rs68X{5eIt+z4d%E2SpU>We z1Uaq1uS|t69^U-po&4TcE<0+4HV+R22Yw3MAnBurc5;=|G#wkM9%6_d%TS2#>PQg| z(#Y=Wu5lMXb0(rEL)Lw<f7|%ihY1Lht(~22k@3wLOwIaY?#c~;e`i^>zPB_693q@V zNskVe94cLx_?T4)|6w?jNb-7HTHdi}WI1onD!~pUd(xlW<Tk#_6hoY%ESn~FG95s1 z0Q<e|wz4rb>V8;=JfQ~~vsO0}be?opBUAp`aH;1<K*<lEtQ#<x827P-&evyybZR{= z6H(9`w}k1k8`WL)*Dc91;Y+f@)loV+IzCUYN1lkG>c{AfaGY?`dj}Crw{J%SJY5@n zR|~p^$D&JLIa%OwB)x-~-rR&*%sVo4aCrFn&yNGNFOZ=-;0EtQLo*z3Vz#5zDbP83 zVgKZGLSd1S6hMsf^sC<G>+zdBqL!1Zij0qE1~Q<SA{6uaGXg=)Z!g^zeQ)ny)9ce4 znY6GU9k~@+V8>{iIM>j-4^Zg-DD%!wJRbj;V_7t2v9yLzlRrEeIY;N1+1bOoyV<UC z8YU|xmA}FtxQ4Ynl9b{ECUqP@gp`b7>b%KoeGbBIwbr+^t)r_u<DF9hDcN0QLKJe{ zwYw{f^t<!^#<h-AQA%cJ<}EK#=h==Q5dNhBhZkI5PY)4ch(w<S#`pG}Vap4yOEfgH zQ!U}ITseYZ7tqEsl<7D@pLH*|_#7<AR#sI_&dkVZYL;F%c}A0$SKK=GT}weGl0Nw3 z$7=yZ6n7pxh$yqsHny?RdqJ}?IX*7Y^lq$nL*BQ*;83*n0Dnl)U0W5obMPbZ1=I&f zw0m<{qg&kEQOYTXt^NIRl+4PLQ@yd_H2l3^nrtLylY^^Wmp%_H5mVHMlx#9k&MK#g zNsW}-A)t=#fEqU0UM%}tL)G-={ANkBBhcd7T9NgwyugeM#cE2)BVV1rH_<%_<PttU zXP%v;Er{W_Ojw`geYP{Pum}nbRd^lK0vsX5<T^5mFQ3Jr`ceIfUkciLS1mVA_O<IL zMf2rlTj$ucIP+iYM1jiLO)t1!z=c7wN(bAAR9fFPCl>o~fJM6Hb_V+Th*BZaJEEc~ z$9SU2<>ij(d*ar<stJCW@ceuZgBrIin}M#%>SrBAemF6tOxT{jx9^520_XFP=9l6h zv=>?m3bxV@PZ$vqQCV48=$y?k)rypJa^lI=Mm4r(vmjDa%eWI2eKtGw+v7Nz{0Z>u z8yn}we+I|YuFLR4n4)cxgD4mnc{YZf`PEdx0N@Kf?9bILAvD9pHMX@ysfg`=AUJ#6 zIaD$SUcy-(hnOUBBmoimvdQY-eW2NC!FCoF3hL@t*4Nh`*)InS*>;uD%gQpZ;_9)L zj+CT%B~n2H38LQQA3PJ0d#{XsG@;bXthFYmD1o*MAYQ+I{I}9kipFMOA<ttCJ@%(y zrLi%<o8j=8U_he;%Fy?fv$NUpQ5AFxzavL(;94w&WJnp)X{8SHhoDjJ-n-{`;>QXS zFbh)d)<Z$}<v{}fvqa>-^K>cUURS88<*S^`hN@kKBO;U^+0H9TOW*ZEr)J1fT)yHI z^}wLI8+}Og+(1pOXP7}LCx;NOnSC!CU3+PAZ9Wh`s~|6*Zq<7oZqrj)6zS%jVu-T9 zAMoZG)n5i>4GQTExB?nQ#^<kd90Z$pK6g6Yv0GkQdHVEeLu>Z66a3ol^D5BUSsK~) zwfp8OKECzc(M@@}Mw9?$j|0RAJ}V_XV>dW>82`Yay+Yal=EJNj)Lwxy;h)qhR@v@n ze$yYKOHpoZ|A^VcB3pi^KcOL{pl{ijQD9O3dcn%d>c&MYpZytuS*%fm@LFx_bXzaD z5*O)&k|6?MU<84Vcy_RZrvjS(a=alDF+W<vIX0$rfka3lk{%86ivygTC!v1E*i`iL z=u%{bJ7q08iOcG@<`v2q5?iU4gTuqtQ|liDzeYtnrNNg@OixQoNFc~)%*Rw5coHjL zG3qEBXcxS7+*uX`C?-gFXz>6rqZwqwVExH05P@fIL3KlEZjQR+EOKkKQ5E}(xozP1 zq^yj%kFOfa>BUDoF@Q}l?*{zuo+sm41}A$)4<uXX9fM)Q(qO0aXigRumR6}}8@Uff zMIj#XOMA%Y&#BAHk@{n4!n^gL@3?&SxNdEh7p>Ll`G<#-8TqVxM(m2qd6qTW@@Z6; zvgJQ7J2O5NNz{|2WMhX1^wX_!;)VX@vpXuns_Ahvqvu;01`5^2CKP&-Jj=y*kKh<f zUESOgPmqvcPeO-nicC9WjVk}iLND;I@xFH&SHSkZcLa#&n3$r9ih+Y&u<m^RH3oqU zUI)d^WjlDuZsd+^#QI9bCPDy~2o}-Axat=1_GeJL;_N~_^}o!!lEVW6?l2%lo+?x- z#2<I8)f-t%{&d8$7Yq*Sz&a4}aqLk`E7o#yasom^VjB!P`$KiU%#4hTdk205(4<7R z7kQ_r+cIKVwOFyc&8@8(Ad6@*U~>gW)DijT+g+BAy}$m`_!?MK!Oco7C079j+JTAW zXyuZFw)tD_8VZQY<vhD(L9M+$*-ZL()=EU4kBLb}q2q(#*jRx<l244t2b*Z3!lEKi z>qv#`r{9#^Q;$g9dbngHZC;WHeA9VK<{W(6;PCY?dKCpj?}Wkfo4)(sqJncd@08X{ zUR7+FCw+B%bbj@3RRGwV<|PpN_h_`Jp<ya^<y^tHZ}&kXahPA<pDJg*HGWmbL<nN+ z`6Eh{hLMqKeRCvSIoo8uyAdmt%)IzS$j#`hPBShkxrgerk0(E<bEUO0{I6fX8rs?t zn}Q-$OR|{#{A$u%J*vD;@DvF`+Y2hg3JMBS-~*_jlUwbu5p*?A6&3e-*N|heoNF>5 z_Og{z{BF;P=HU14iqxXgp4bNFZ=w#8iPtMl!|E<QE70$RwpQiEtEtzYA<MbtMYq(S z?`Q5fGXrN2=OemJ?4uhwM<;%a$$s8t)R#)tR_nPb=*i>%kqzm$T%7vH&*uvL*Z}r+ z`Hy~<)=UqzUpIXjr`^OW+f36zzYN7?W>N3qP9mH(XY*-Cva_=(FTD!s?(Vk5OifOn zesJlE6L%}NwWX!SX}sa_1Ea3vCZ1CSnxD6fj?oZsnJ?VYY+&c(<AW?D_z)G<2((YY zdxy5Mk@4+Y{k!+)*<~`gag`bxWVC`#VgCLaU$dw;mcf#kqs1^h;ACQY2d1lq4Q_jh zzchP(MoZa#g+=j@AvQVrN0yRa=|E3U&+ytH$k#&Xci=~#yuU~ygo(P-T54C~x9j(R zC+Be%ifU?Vv?3n&M>Nin_A+H&ef-#h_=14ec+9b}v2mr>HZ_o+q2bt(x~}{aklC2i z+YjunWjjqszN(v{`t$Awtp>%~(whjfUubbs4Q;~_5q%3o-+hY+Os!b2q=-`VJcAk( z!{%&ft1aI{>sVCz(cv0$JUcCJd|Wja@U_@&MaXWnF(4>`>*bZ!$h7HAtZyRkk`3Y% zSxB$5|Lp0BN<$hdAMVfvhlcjH>y^$khg|27PZL|~Ew}RqKcl&o0V#v_8tX{h<d6y? zpIbuJH)C??L0%#TYxk+BA6fnY6OW+Mn?D3}%3a`!VC%lSc(a9w;o|Sqi8Q^x11Q`$ z?&KI@V<QS(^Hebk9=x1W7FSNa_+qr0toG2J0Wc}n!t+!fjyF);<hQ^5VV~43hDoW} z^3klWa$L~JLz9*~pOKT#p;VmQaIWAaku>CJWZx7&W~)2#V?4cbtoDS*vYQ$FzhbZL z=kF+X%~~VqBxcvf4iDF|0VrF-+KyhQ<jp`$c^<CND<<*9fE~BqL=o!WF}IC+UPh>b z9o|~pHjMC4WTH&-<%<O-4)OC^?6+$GszcJ31*D6|ankh7o37$l6Do>nVr;Ve6uU#q zL(cPma%dcCdqlCmF?uJQMs?9}xW!hz=7_Fj;;#lZI~QsAM6esA)>1Dca)NeUAGgXF z;&l_MH-={?grNFTZH$f+r+84<tpKDd@%p1*rnR!C#Kxf>{~(50WqQDf-wLz4I@-Fk zB0N;(%!ZH(@@uh3Bg@oAxdR?0pz4p~>_&L7tS8&3Q)gfTXk*=>J;CG0iALwP7x4i= zz`6a7w8N-pFmH*C95-iOL1Nu_WQo8b>n|ZsIs^%rmCvabB(s4PNbuQaVAXc8@Up`i zq08DaAsEZO#wA5X=F`6}gK2bAyngf?k<s+O3H;#D(9F%<4~zjUYU$?y@6sKoWZ*9Z zWsOcn_zXt78)goK6vvNnew9u$w}GF1c3sl88Ym!3;<KBmS(^p=n`9$M;Z-RLi17k{ zpyXM8@|d2U*J`j^Pq^2nd;A)3c<1p3#>vUa`H>0=y$XAarT&$z`JNQW#i^+&DM`sQ zY}y4cm-;(ltfQ{JIt8Q~s36F%yZ7%S{J}0&D>fw-J9>4isHmtTftysko^E-lBpzrq zcKgO+Z#I88HSf94b|X8>IOOLBzR}9djon|GPGvt(rd`}xYpK=43*_W&Wf6H|q!%y# zz#%EXVc%a(3OcL@F=C6WlVU^p;k|sedTzyW8bw*`kByOS*fo{g36qFqLDxG^N8`C| zq4@`WW~rSm_3vWX`}ZCF{eO))EE&dtqT#TmmsDbsd&J*F&<USHI+)H1!1e6*p=!$> zmUwQ?Qr@`n7Qh$SU;S}48AYV{gLPpcq2C}$c2-AaVGcvjdg2NvtO#~2(76@4SKwv- zL7h<8)!cq!3w+AJd-SW^9b@B3dYzM26Du%5l)#uKCMC^Ao;+qZto;CP!8DCKjD!XY zUsA%Hq<O$yvj97bRr15DW=Bwgcl=p4(*1_0*Yq9H`Pqrq<?DZriB@n(|JhF8(gCUm z=cbmIrE{Ng2qKTqfV=LpxFu9xUY;!Ac!eP?%l2<&A~~JmSsvz%zI;6*w4sW}o>p?o ziN_)i+O*|JIT->()>oK@omvmxyT&?&FZ?2OK!U;6i~%=+uuy!>%ye8cRD5kWT!Ce9 z-<2QQ2?&rnp8D}5<mwj~3ti=~1|k>Wv#VsdhCLo;!kyL{v_%qE#hIO_GquxwX@NPp zh3nIO5cy6N{=9EipvR}_u;a_&bi(goP;uIpATKCkpa7gMM=e7LGP1jzM%3%>2SL9C zSHa`QgaM2r3AY;*IMLFeK)&e|ouOn3%XD9>bD94~y|8c-1jm49i>B&r@UdoBD!ez_ zm>D(>mgq`n_M7RhIK`z1<G~H;%th68&0reOi`nzlZy=UeLMJCC8UZg8`RttP%tS+u zhU*>D#P7%)t>2LVBfwJpt@FXwO|NaQAmio`NBjX9Egc;Q_0ta85{a$VuZLFS<MZjv z_<}A?OxsU>Tt}wu5YNRM-3*+ZDlj-XpmA`?(5rah*|IU>(7HdLfgP+z@;8T4&2=P7 zY<wa)e31k@4vvWEDnD)%yzW_4w0}pCs?@=0b3B<OhGmZ797SAO648iig?)-Rgp(O{ z%&uq<Nv-nOuCp^>1^MSRfun(rycx3X@AApZ&j)jg8#+C=d4~?-(<7^nI^V+*e7SG` z4#)FH76T9JX{TO^iFE=#>zA{g1x~j$zkdBg;ug`xdF(f}x-dFnrjd>@qAAbHx}X8J z%y}I0)<?D)8>8iHm9&YYeNxXBw8y%7a&q$g$gb$K{O1uhHNwHca)Jj>vU{ye2F`Cc zL6<BlDKYE(^by7qByMYCF&2Up-j(R{W#Xr$^?%B!en0A6P=_wd3Hw3V*Jp`9s1i?1 zXcLo>W$ITIgN;E4;*Oec3yhE}XhUpIM$iS~s;3}d=Ev}4`M}>SS3*U3`y@Q}`e@ey z@84VZeOImZa0IiOn4G*n+RD$)`uA7vQ}~6D-`ccrcVFGYY}$$}p(&Uak9L&o@eU3S zD7q3LmC!{^_;PL6d_6WS{p{{9b8o-8x&NOX<mF<!LsHOEM}B@)^Yd=UM4W{eB*V|2 zKc|P48R_HwVCF%Y>U!yX+pnjxIkokFQnSEB_%{_93j1gV#{g;^tWRC*zKw<f_eUW9 z0F&MVR=0#FHGnJ;@Y>>+lzcF`GV1@lOyqq;9e8=UlaDeGi1RN`p9Qc4d#|;?pr)w! zO@F>#RCM$Y7yw9t#{`T<$=LV`46>Ws+e3;>m<#>zGH$DXo4;$>K)#2=RF8<9*0kxJ z63nthL~Yd4rHLcxMO#)!y}v=3L7;W3T?8xaN7KO?WY;TC1{L00W<z!6>{&sdJtVtM z;Rl$zj<1Z~8mM#_tp;kmR9Ieaj)^n+xA9#m{y~b<mXf68ZJ3=ga&dh)wMQ_RhXWh8 z?>H1&_gQBtCA}#rp*-y*^W77F@^k~>aAxnf4j)94Djn|ZTi9SHnockk_|;XUzL2Oh z(}!@H3RO_+i+zVgtr2(`7ner>>|{_`@kS9A8`Ht_+g^OOgVHZvT<T0ZEHuM#f>*V^ zkj+qJ*%N7MnqY}L)`A)UHf7TNS*5Uy<4vT>!~6#&Lmj6Q_}Vokm}@8{@wGWt$UqlK z5Iv-UV7zNHog71Z>!ZEG*|dKQ*0>v`+F6hNC1PW{!M99$`m};X0}I3sMrmTKn%^YC zsryR$HeaL(3JYuG#t{KxZi>h_FClGgY`~c&IYnqf!ifdlB(PI7a@6nHkJTn-99?63 zHdA8+oZV=gd;4EqfliTR-6;|Agu$&FWMoBw5pp!nLWH8<%Ekt>F_T+dwvDBa_w>!e z#Ih(26pZF*w1zJB<z8F#mU}PDxZ6W@i-Us<Z5XMX4R#J4v`&}m4pK&#ayKx<Z^Nhv zMhS1gIkc+JPrb*=8Upyg4nw}F+1c+L_3$Xf<;zi;C_^%!;rYoq8Wv!9QLL_he5%A- zXg}%|!64o^(R2u+|Gk^{N03c7Z{7q)1gM!j3&dAV-fG0O8RYo!Nt*5<diU=nUsC6A zzGPpn_7se|+Dnj7eIQV=e6y)W;-+oYnH)=@!WB-pHm__4tf<M)j2shj7O`f+Wbt_- zVE_RPnZdkMm4C_g^`(PDc0ML#`xvj8j|`K^;JaS)Y9bOIryu~_*;+Z|geMEWlbw~A zy%dZ_`_@Sj!^-Y5F{wkv|L*PWfky*q&zG^>n-K%X0Rjuow!N&AVprG8%d6b?!$4hI z{t5X0Ai;k2l_g)t1{|(5ZuI{Cy)nw^g|2HbS)A}q>09$Wt@-RW)7!Ui^WdsUgjz}# zON)!!%QzXfE5S;|Za(y#{HfMqUb2I}{WFFs_*n3fS-Z<c!n44dyW<St$r3T&vALhY z8Q2Gf^+izMFerS3TBDLMzx_=c#mKBe@0;gnzP@`a&1#*!E$svL3m9Xsz~8c+S{BEb zzU8c^-3+^Pco-5K++(gNH7j6W<7RiaEmSfJTj~)9z$sYfxycEf2?0Y~9-iM;J?Vnm zi)||c*pMMx^U><<zm=C?=jO7Nmp_^B&tJMT%5?vDZ2BQb>NUA7m~p*5AEU7QU%#a} zK_lyM{m<L~>q~FO)eQbb9w+?v{ZnN<y%zZNKfi6ydHOdR=6o+3@Ai<wC)X9P{D1WF bm?C_Rl0PRXLLZ*aLdZQ)mM)Nd@#a4O^*&#o literal 0 HcmV?d00001 diff --git a/requirements-full.txt b/requirements-full.txt index 6647098e5..634447ef3 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -3,3 +3,4 @@ psutil # SystemMemory and MemoryAvailable scikit-image >= 0.17 # FindMinimum can use this; used by Image as well lxml # for HTML parsing used in builtin/fileformats/html wordcloud # Used in builtin/image.py by WordCloud() +pyocr # Used for TextRecognize From 7e464a3ced189af453f10ca1639fdcb197276dc2 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Thu, 29 Dec 2022 07:39:01 -0500 Subject: [PATCH 113/121] Start to split out Image into its own section Also, at this point numpy and PIL are not optional. So remove `_image_requires` which was numpy and PIL. --- mathics/builtin/__init__.py | 1 + mathics/builtin/colors/color_operations.py | 27 +- .../drawing/{image.py => image-misc.py} | 1123 +---------------- mathics/builtin/image/__init__.py | 3 + mathics/builtin/image/base.py | 311 +++++ mathics/builtin/image/basic.py | 204 +++ mathics/builtin/image/geometric.py | 278 ++++ mathics/builtin/image/structure.py | 242 ++++ mathics/builtin/image/test.py | 69 + mathics/builtin/options.py | 2 +- mathics/eval/image.py | 2 +- setup.py | 3 +- 12 files changed, 1162 insertions(+), 1103 deletions(-) rename mathics/builtin/drawing/{image.py => image-misc.py} (56%) create mode 100644 mathics/builtin/image/__init__.py create mode 100644 mathics/builtin/image/base.py create mode 100644 mathics/builtin/image/basic.py create mode 100644 mathics/builtin/image/geometric.py create mode 100644 mathics/builtin/image/structure.py create mode 100644 mathics/builtin/image/test.py diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index 8a601e25e..0f88784ef 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -194,6 +194,7 @@ def name_is_builtin_symbol(module, name: str) -> Optional[type]: "files_io", "forms", "functional", + "image", "intfns", "list", "matrices", diff --git a/mathics/builtin/colors/color_operations.py b/mathics/builtin/colors/color_operations.py index b5cbcc429..7861fff28 100644 --- a/mathics/builtin/colors/color_operations.py +++ b/mathics/builtin/colors/color_operations.py @@ -12,7 +12,7 @@ from mathics.builtin.base import Builtin from mathics.builtin.colors.color_directives import ColorError, RGBColor, _ColorObject from mathics.builtin.colors.color_internals import convert_color -from mathics.builtin.drawing.image import Image, _ImageBuiltin +from mathics.builtin.image.base import Image from mathics.core.atoms import Integer, MachineReal, Rational, Real from mathics.core.convert.expression import to_expression, to_mathics_list from mathics.core.expression import Expression @@ -22,13 +22,8 @@ _image_requires = ("numpy", "PIL") -try: - import numpy - import PIL.ImageOps - - _enabled = True -except ImportError: - _enabled = False +import numpy +import PIL.ImageOps class Blend(Builtin): @@ -204,9 +199,11 @@ def eval(self, input, colorspace, evaluation): return color_to_expression(converted_components, py_colorspace) -class ColorNegate(_ImageBuiltin): +class ColorNegate(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ColorNegate.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ColorNegate.html</url> <dl> <dt>'ColorNegate[$image$]' @@ -239,7 +236,9 @@ def eval_for_image(self, image, evaluation): class Darker(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Darker.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/Darker.html</url> <dl> <dt>'Darker[$c$, $f$]' @@ -262,9 +261,11 @@ class Darker(Builtin): summary_text = "make a color darker" -class DominantColors(_ImageBuiltin): +class DominantColors(Builtin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/DominantColors.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/DominantColors.html</url> <dl> <dt>'DominantColors[$image$]' diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image-misc.py similarity index 56% rename from mathics/builtin/drawing/image.py rename to mathics/builtin/drawing/image-misc.py index f63e99465..8b4097215 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image-misc.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# FIXME - move the rest into builtin.image """ Image[] and image-related functions @@ -11,70 +12,40 @@ # the top level. sort_order = "mathics.builtin.image-and-image-related-functions" -import base64 import functools import math import os.path as osp from collections import defaultdict -from copy import deepcopy -from typing import Tuple - -from mathics.builtin.base import AtomBuiltin, Builtin, String, Test -from mathics.builtin.box.image import ImageBox -from mathics.builtin.colors.color_internals import ( - colorspaces as known_colorspaces, - convert_color, -) -from mathics.core.atoms import ( - Atom, - Integer, - Integer0, - Integer1, - MachineReal, - Rational, - Real, -) + +import numpy +import PIL + +from mathics.builtin.base import Builtin, String +from mathics.builtin.colors.color_internals import colorspaces as known_colorspaces +from mathics.builtin.image.base import Image, _SkimageBuiltin +from mathics.core.atoms import Integer, Integer0, Integer1, MachineReal, Rational, Real from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_python from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull, SymbolTrue -from mathics.core.systemsymbols import SymbolImage, SymbolRule +from mathics.core.systemsymbols import SymbolRule from mathics.eval.image import ( convolve, extract_exif, - get_image_size_spec, matrix_to_numpy, - numpy_flip, numpy_to_matrix, pixels_as_float, pixels_as_ubyte, pixels_as_uint, - resize_width_height, ) SymbolColorQuantize = Symbol("ColorQuantize") -SymbolImage = Symbol("Image") SymbolMatrixQ = Symbol("MatrixQ") SymbolThreshold = Symbol("Threshold") -# Note a list of packages that are needed for image Builtins. -_image_requires = ("numpy", "PIL") -_skimage_requires = _image_requires + ("skimage", "scipy", "matplotlib", "networkx") - -try: - import warnings - - import numpy - import PIL - import PIL.ImageEnhance - import PIL.ImageFilter - import PIL.ImageOps - -except ImportError: - pass - +_skimage_requires = ("skimage", "scipy", "matplotlib", "networkx") try: import skimage.filters @@ -83,37 +54,15 @@ else: have_skimage_filters = True -from io import BytesIO - # The following classes are used to allow inclusion of # Builtin Functions only when certain Python packages # are available. They do this by setting the `requires` class variable. -class _ImageBuiltin(Builtin): - requires = _image_requires - - -class _ImageTest(Test): - """ - Testing Image Builtins -- those function names ending with "Q" -- that require scikit-image. - """ - - requires = _image_requires - - -class _SkimageBuiltin(_ImageBuiltin): - """ - Image Builtins that require scikit-image. - """ - - requires = _skimage_requires - - # Code related to Mathics Functions that import and export. -class ImageExport(_ImageBuiltin): +class ImageExport(Builtin): """ <dl> <dt> 'ImageExport["path", $image$]' @@ -134,7 +83,7 @@ def eval(self, path: String, expr, opts, evaluation: Evaluation): return evaluation.message("ImageExport", "noimage") -class ImageImport(_ImageBuiltin): +class ImageImport(Builtin): """ <dl> <dt> 'ImageImport["path"]' @@ -175,7 +124,7 @@ def eval(self, path: String, evaluation: Evaluation): return ListExpression(*image_list_expression) -class _ImageArithmetic(_ImageBuiltin): +class _ImageArithmetic(Builtin): messages = {"bddarg": "Expecting a number, image, or graphics instead of `1`."} @staticmethod @@ -320,7 +269,7 @@ class ImageSubtract(_ImageArithmetic): summary_text = "build an image substracting pixel values of another image " -class RandomImage(_ImageBuiltin): +class RandomImage(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/RandomImage.html</url> @@ -392,446 +341,7 @@ def eval(self, minval, maxval, w, h, evaluation, options): return Image(data, cs) -class ImageResize(_ImageBuiltin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageResize.html</url> - - <dl> - <dt>'ImageResize[$image$, $width$]' - <dd> - - <dt>'ImageResize[$image$, {$width$, $height$}]' - <dd> - </dl> - - The Resampling option can be used to specify how to resample the image. Options are: - <ul> - <li>Automatic - <li>Bicubic - <li>Bilinear - <li>Box - <li>Hamming - <li>Lanczos - <li>Nearest - </ul> - - See <url> - :Pillow Filters: - https://pillow.readthedocs.io/en/stable/handbook/concepts.html#filters</url>\ - for a description of these. - - S> alice = Import["ExampleData/MadTeaParty.gif"] - = -Image- - - S> shape = ImageDimensions[alice] - = {640, 487} - - S> ImageResize[alice, shape / 2] - = -Image- - - The default sampling method is "Bicubic" which has pretty good upscaling \ - and downscaling quality. However "Box" is the fastest: - - - S> ImageResize[alice, shape / 2, Resampling -> "Box"] - = -Image- - """ - - messages = { - "imgrssz": "The size `1` is not a valid image size specification.", - "imgrsm": "Invalid resampling method `1`.", - "gaussaspect": "Gaussian resampling needs to maintain aspect ratio.", - "skimage": "Please install scikit-image to use Resampling -> Gaussian.", - } - - options = {"Resampling": "Automatic"} - summary_text = "resize an image" - - def eval_resize_width(self, image, s, evaluation, options): - "ImageResize[image_Image, s_, OptionsPattern[ImageResize]]" - old_w = image.pixels.shape[1] - if s.has_form("List", 1): - width = s.elements[0] - else: - width = s - w = get_image_size_spec(old_w, width) - if w is None: - return evaluation.message("ImageResize", "imgrssz", s) - if s.has_form("List", 1): - height = width - else: - height = Symbol("Automatic") - return self.eval_resize_width_height(image, width, height, evaluation, options) - - def eval_resize_width_height(self, image, width, height, evaluation, options): - "ImageResize[image_Image, {width_, height_}, OptionsPattern[ImageResize]]" - # resampling method - resampling = self.get_option(options, "Resampling", evaluation) - if ( - isinstance(resampling, Symbol) - and resampling.get_name() == "System`Automatic" - ): - resampling_name = "Bicubic" - else: - resampling_name = resampling.value - - # find new size - old_w, old_h = image.pixels.shape[1], image.pixels.shape[0] - w = get_image_size_spec(old_w, width) - h = get_image_size_spec(old_h, height) - if h is None or w is None: - return evaluation.message( - "ImageResize", "imgrssz", to_mathics_list(width, height) - ) - - # handle Automatic - old_aspect_ratio = old_w / old_h - if w == 0 and h == 0: - # if both width and height are Automatic then use old values - w, h = old_w, old_h - elif w == 0: - w = max(1, h * old_aspect_ratio) - elif h == 0: - h = max(1, w / old_aspect_ratio) - - if resampling_name != "Gaussian": - # Gaussian need to unrounded values to compute scaling ratios. - # round to closest pixel for other methods. - h, w = int(round(h)), int(round(w)) - - # perform the resize - return resize_width_height(image, w, h, resampling_name, evaluation) - - -class ImageReflect(_ImageBuiltin): - """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/ImageReflect.html</url> - <dl> - <dt>'ImageReflect[$image$]' - <dd>Flips $image$ top to bottom. - - <dt>'ImageReflect[$image$, $side$]' - <dd>Flips $image$ so that $side$ is interchanged with its opposite. - - <dt>'ImageReflect[$image$, $side_1$ -> $side_2$]' - <dd>Flips $image$ so that $side_1$ is interchanged with $side_2$. - </dl> - - >> ein = Import["ExampleData/Einstein.jpg"]; - >> ImageReflect[ein] - = -Image- - >> ImageReflect[ein, Left] - = -Image- - >> ImageReflect[ein, Left -> Top] - = -Image- - - #> ein == ImageReflect[ein, Left -> Left] == ImageReflect[ein, Right -> Right] == ImageReflect[ein, Top -> Top] == ImageReflect[ein, Bottom -> Bottom] - = True - #> ImageReflect[ein, Left -> Right] == ImageReflect[ein, Right -> Left] == ImageReflect[ein, Left] == ImageReflect[ein, Right] - = True - #> ImageReflect[ein, Bottom -> Top] == ImageReflect[ein, Top -> Bottom] == ImageReflect[ein, Top] == ImageReflect[ein, Bottom] - = True - #> ImageReflect[ein, Left -> Top] == ImageReflect[ein, Right -> Bottom] (* Transpose *) - = True - #> ImageReflect[ein, Left -> Bottom] == ImageReflect[ein, Right -> Top] (* Anti-Transpose *) - = True - - #> ImageReflect[ein, x -> Top] - : x -> Top is not a valid 2D reflection specification. - = ImageReflect[-Image-, x -> Top] - """ - - summary_text = "reflect an image" - rules = { - "ImageReflect[image_Image]": "ImageReflect[image, Top -> Bottom]", - "ImageReflect[image_Image, Top|Bottom]": "ImageReflect[image, Top -> Bottom]", - "ImageReflect[image_Image, Left|Right]": "ImageReflect[image, Left -> Right]", - } - - messages = {"bdrfl2": "`1` is not a valid 2D reflection specification."} - - def eval(self, image, orig, dest, evaluation: Evaluation): - "ImageReflect[image_Image, Rule[orig_, dest_]]" - if isinstance(orig, Symbol) and isinstance(dest, Symbol): - specs = [orig.get_name(), dest.get_name()] - specs.sort() # `Top -> Bottom` is the same as `Bottom -> Top` - - def anti_transpose(i): - return numpy.flipud(numpy.transpose(numpy.flipud(i))) - - def no_op(i): - return i - - method = { - ("System`Bottom", "System`Top"): numpy.flipud, - ("System`Left", "System`Right"): numpy.fliplr, - ("System`Left", "System`Top"): numpy.transpose, - ("System`Right", "System`Top"): anti_transpose, - ("System`Bottom", "System`Left"): anti_transpose, - ("System`Bottom", "System`Right"): numpy.transpose, - ("System`Bottom", "System`Bottom"): no_op, - ("System`Top", "System`Top"): no_op, - ("System`Left", "System`Left"): no_op, - ("System`Right", "System`Right"): no_op, - }.get(tuple(specs), None) - - if method is None: - return evaluation.message( - "ImageReflect", "bdrfl2", Expression(SymbolRule, orig, dest) - ) - - return Image(method(image.pixels), image.color_space) - - -class ImageRotate(_ImageBuiltin): - """ - - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageRotate.html</url> - - <dl> - <dt>'ImageRotate[$image$]' - <dd>Rotates $image$ 90 degrees counterclockwise. - <dt>'ImageRotate[$image$, $theta$]' - <dd>Rotates $image$ by a given angle $theta$ - </dl> - - >> ein = Import["ExampleData/Einstein.jpg"]; - - >> ImageRotate[ein] - = -Image- - - >> ImageRotate[ein, 45 Degree] - = -Image- - - >> ImageRotate[ein, Pi / 4] - = -Image- - - #> ImageRotate[ein, ein] - : Angle -Image- should be a real number, one of Top, Bottom, Left, Right, or a rule from one to another. - = ImageRotate[-Image-, -Image-] - """ - - messages = { - "imgang": "Angle `1` should be a real number, one of Top, Bottom, Left, Right, or a rule from one to another." - } - - rules = {"ImageRotate[i_Image]": "ImageRotate[i, 90 Degree]"} - - summary_text = "rotate an image" - - def eval(self, image, angle, evaluation: Evaluation): - "ImageRotate[image_Image, angle_]" - - # FIXME: this test I suppose is okay in that it checks more or less what is needed. - # However there might be a better test like for Real-valued-ness which could be used - # instead. - py_angle = ( - angle.round_to_float(evaluation) - if hasattr(angle, "round_to_float") - else None - ) - - if py_angle is None: - return evaluation.message("ImageRotate", "imgang", angle) - - def rotate(im): - return im.rotate( - 180 * py_angle / math.pi, resample=PIL.Image.BICUBIC, expand=True - ) - - return image.filter(rotate) - - -class ImagePartition(_ImageBuiltin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImagePartition.html</url> - - <dl> - <dt>'ImagePartition[$image$, $s$]' - <dd>Partitions an image into an array of $s$ x $s$ pixel subimages. - - <dt>'ImagePartition[$image$, {$w$, $h$}]' - <dd>Partitions an image into an array of $w$ x $h$ pixel subimages. - </dl> - - >> lena = Import["ExampleData/lena.tif"]; - >> ImageDimensions[lena] - = {512, 512} - >> ImagePartition[lena, 256] - = {{-Image-, -Image-}, {-Image-, -Image-}} - - >> ImagePartition[lena, {512, 128}] - = {{-Image-}, {-Image-}, {-Image-}, {-Image-}} - - #> ImagePartition[lena, 257] - = {{-Image-}} - #> ImagePartition[lena, 512] - = {{-Image-}} - #> ImagePartition[lena, 513] - = {} - #> ImagePartition[lena, {256, 300}] - = {{-Image-, -Image-}} - - #> ImagePartition[lena, {0, 300}] - : {0, 300} is not a valid size specification for image partitions. - = ImagePartition[-Image-, {0, 300}] - """ - - summary_text = "divide an image in an array of sub-images" - rules = {"ImagePartition[i_Image, s_Integer]": "ImagePartition[i, {s, s}]"} - - messages = {"arg2": "`1` is not a valid size specification for image partitions."} - - def eval(self, image, w: Integer, h: Integer, evaluation: Evaluation): - "ImagePartition[image_Image, {w_Integer, h_Integer}]" - py_w = w.value - py_h = h.value - if py_w <= 0 or py_h <= 0: - return evaluation.message("ImagePartition", "arg2", ListExpression(w, h)) - pixels = image.pixels - shape = pixels.shape - - # drop blocks less than w x h - parts = [] - for yi in range(shape[0] // py_h): - row = [] - for xi in range(shape[1] // py_w): - p = pixels[yi * py_h : (yi + 1) * py_h, xi * py_w : (xi + 1) * py_w] - row.append(Image(p, image.color_space)) - if row: - parts.append(row) - return from_python(parts) - - -class ImageAdjust(_ImageBuiltin): - """ - - <url>:WMA link: - https://reference.wolfram.com/language/ref/ImageAdjust.html</url> - - <dl> - <dt>'ImageAdjust[$image$]' - <dd>adjusts the levels in $image$. - - <dt>'ImageAdjust[$image$, $c$]' - <dd>adjusts the contrast in $image$ by $c$. - - <dt>'ImageAdjust[$image$, {$c$, $b$}]' - <dd>adjusts the contrast $c$, and brightness $b$ in $image$. - - <dt>'ImageAdjust[$image$, {$c$, $b$, $g$}]' - <dd>adjusts the contrast $c$, brightness $b$, and gamma $g$ in $image$. - </dl> - - >> lena = Import["ExampleData/lena.tif"]; - >> ImageAdjust[lena] - = -Image- - """ - - summary_text = "adjust levels, brightness, contrast, gamma, etc" - rules = { - "ImageAdjust[image_Image, c_?RealNumberQ]": "ImageAdjust[image, {c, 0, 1}]", - "ImageAdjust[image_Image, {c_?RealNumberQ, b_?RealNumberQ}]": "ImageAdjust[image, {c, b, 1}]", - } - - def eval_auto(self, image, evaluation: Evaluation): - "ImageAdjust[image_Image]" - pixels = pixels_as_float(image.pixels) - - # channel limits - axis = (0, 1) - cmaxs, cmins = pixels.max(axis=axis), pixels.min(axis=axis) - - # normalise channels - scales = cmaxs - cmins - if not scales.shape: - scales = numpy.array([scales]) - scales[scales == 0.0] = 1 - pixels -= cmins - pixels /= scales - return Image(pixels, image.color_space) - - def eval_contrast_brightness_gamma(self, image, c, b, g, evaluation: Evaluation): - "ImageAdjust[image_Image, {c_?RealNumberQ, b_?RealNumberQ, g_?RealNumberQ}]" - - im = image.pil() - - # gamma - g = g.round_to_float() - if g != 1: - im = PIL.ImageEnhance.Color(im).enhance(g) - - # brightness - b = b.round_to_float() - if b != 0: - im = PIL.ImageEnhance.Brightness(im).enhance(b + 1) - - # contrast - c = c.round_to_float() - if c != 0: - im = PIL.ImageEnhance.Contrast(im).enhance(c + 1) - - return Image(numpy.array(im), image.color_space) - - -class Blur(_ImageBuiltin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Blur.html</url> - - <dl> - <dt>'Blur[$image$]' - <dd>gives a blurred version of $image$. - - <dt>'Blur[$image$, $r$]' - <dd>blurs $image$ with a kernel of size $r$. - </dl> - - >> lena = Import["ExampleData/lena.tif"]; - >> Blur[lena] - = -Image- - >> Blur[lena, 5] - = -Image- - """ - - summary_text = "blur an image" - rules = { - "Blur[image_Image]": "Blur[image, 2]", - "Blur[image_Image, r_?RealNumberQ]": "ImageConvolve[image, BoxMatrix[r] / Total[Flatten[BoxMatrix[r]]]]", - } - - -class Sharpen(_ImageBuiltin): - """ - - <url>:WMA link:https://reference.wolfram.com/language/ref/Sharpen.html</url> - - <dl> - <dt>'Sharpen[$image$]' - <dd>gives a sharpened version of $image$. - - <dt>'Sharpen[$image$, $r$]' - <dd>sharpens $image$ with a kernel of size $r$. - </dl> - - >> lena = Import["ExampleData/lena.tif"]; - >> Sharpen[lena] - = -Image- - >> Sharpen[lena, 5] - = -Image- - """ - - summary_text = "sharpen version of an image" - rules = {"Sharpen[i_Image]": "Sharpen[i, 2]"} - - def eval(self, image, r, evaluation: Evaluation): - "Sharpen[image_Image, r_?RealNumberQ]" - f = PIL.ImageFilter.UnsharpMask(r.round_to_float()) - return image.filter(lambda im: im.filter(f)) - - -class GaussianFilter(_ImageBuiltin): +class GaussianFilter(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/GaussianFilter.html</url> @@ -860,7 +370,7 @@ def eval_radius(self, image, radius, evaluation: Evaluation): # morphological image filters -class PillowImageFilter(_ImageBuiltin): +class PillowImageFilter(Builtin): """ ## <url>:PillowImageFilter:</url> @@ -994,7 +504,7 @@ def _matrix(rows): return ListExpression(*[ListExpression(*r) for r in rows]) -class BoxMatrix(_ImageBuiltin): +class BoxMatrix(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/BoxMatrix.html</url> @@ -1017,7 +527,7 @@ def eval(self, r, evaluation: Evaluation): return _matrix([[Integer1] * s] * s) -class DiskMatrix(_ImageBuiltin): +class DiskMatrix(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/DiskMatrix.html</url> @@ -1047,7 +557,7 @@ def rows(): return _matrix(rows()) -class DiamondMatrix(_ImageBuiltin): +class DiamondMatrix(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/DiamondMatrix.html</url> @@ -1085,7 +595,7 @@ def rows(): return _matrix(rows()) -class ImageConvolve(_ImageBuiltin): +class ImageConvolve(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/ImageConvolve.html</url> @@ -1239,7 +749,7 @@ def eval(self, image, t, evaluation: Evaluation): # color space -class ImageColorSpace(_ImageBuiltin): +class ImageColorSpace(Builtin): """ <url> :WMA link: @@ -1262,7 +772,7 @@ def eval(self, image, evaluation: Evaluation): return String(image.color_space) -class ColorQuantize(_ImageBuiltin): +class ColorQuantize(Builtin): """ <url> :WMA link: @@ -1304,7 +814,7 @@ def eval(self, image, n: Integer, evaluation: Evaluation): return Image(numpy.array(im), "RGB") -class Threshold(_ImageBuiltin): +class Threshold(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/Threshold.html</url> @@ -1363,7 +873,7 @@ def eval(self, image, evaluation: Evaluation, options): return MachineReal(float(threshold)) -class Binarize(_ImageBuiltin): +class Binarize(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/Binarize.html</url> @@ -1411,7 +921,7 @@ def eval_t1_t2(self, image, t1, t2, evaluation: Evaluation): return Image(mask1 * mask2, "Grayscale") -class ColorSeparate(_ImageBuiltin): +class ColorSeparate(Builtin): """ <url> :WMA link: @@ -1437,7 +947,7 @@ def eval(self, image, evaluation: Evaluation): return ListExpression(*images) -class ColorCombine(_ImageBuiltin): +class ColorCombine(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/ColorCombine.html</url> @@ -1506,7 +1016,7 @@ def _linearize(a): return numpy.where(a == h[lower], lower, upper).reshape(orig_shape), n -class Colorize(_ImageBuiltin): +class Colorize(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/Colorize.html</url> @@ -1574,7 +1084,7 @@ def eval(self, values, evaluation, options): # pixel access -class ImageData(_ImageBuiltin): +class ImageData(Builtin): """ <url>:WMA link: @@ -1625,234 +1135,7 @@ def eval(self, image, stype: String, evaluation: Evaluation): return from_python(numpy_to_matrix(pixels)) -class ImageTake(_ImageBuiltin): - """ - Crop Image <url>:WMA link: - https://reference.wolfram.com/language/ref/ImageTake.html</url> - <dl> - <dt>'ImageTake[$image$, $n$]' - <dd>gives the first $n$ rows of $image$. - - <dt>'ImageTake[$image$, -$n$]' - <dd>gives the last $n$ rows of $image$. - - <dt>'ImageTake[$image$, {$r1$, $r2$}]' - <dd>gives rows $r1$, ..., $r2$ of $image$. - - <dt>'ImageTake[$image$, {$r1$, $r2$}, {$c1$, $c2$}]' - <dd>gives a cropped version of $image$. - </dl> - - Crop to the include only the upper half (244 rows) of an image: - >> alice = Import["ExampleData/MadTeaParty.gif"]; ImageTake[alice, 244] - = -Image- - - Now crop to the include the lower half of that image: - >> ImageTake[alice, -244] - = -Image- - - Just the text around the hat: - >> ImageTake[alice, {40, 150}, {500, 600}] - = -Image- - - """ - - summary_text = "crop image" - - # FIXME: this probably should be moved out since WMA docs - # suggest this kind of thing is done across many kinds of - # images. - def _image_slice(self, image, i1: Integer, i2: Integer, axis): - """ - Extracts a slice of an image and return a slice - indicting a slice, a function flip, that will - reverse the pixels in an image if necessary. - """ - n = image.pixels.shape[axis] - py_i1 = min(max(i1.value - 1, 0), n - 1) - py_i2 = min(max(i2.value - 1, 0), n - 1) - - def flip(pixels): - if py_i1 > py_i2: - return numpy_flip(pixels, axis) - else: - return pixels - - return slice(min(py_i1, py_i2), 1 + max(py_i1, py_i2)), flip - - # The reason it is hard to make a rules that turn Image[image, n], - # or Image[, {r1, r2} into the generic form Image[image, {r1, r2}, - # {c1, c2}] there can be negative numbers, e.g. -n. Also, that - # missing values, in particular r2 and c2, when filled out can be - # dependent on the size of the image. - - # FIXME: create common functions to process ranges. - # FIXME: fix up and use _image_slice. - - def eval_n(self, image, n: Integer, evaluation: Evaluation): - "ImageTake[image_Image, n_Integer]" - py_n = n.value - max_y, max_x = image.pixels.shape[:2] - if py_n >= 0: - adjusted_n = min(py_n, max_y) - pixels = image.pixels[:adjusted_n] - box_coords = (0, 0, max_x, adjusted_n) - elif py_n < 0: - adjusted_n = max(0, max_y + py_n) - pixels = image.pixels[adjusted_n:] - box_coords = (0, adjusted_n, max_x, max_y) - - if hasattr(image, "pillow"): - pillow = image.pillow.crop(box_coords) - pixels = numpy.asarray(pillow) - return Image(pixels, image.color_space, pillow=pillow) - - return Image(pixels, image.color_space, pillow=pillow) - - def eval_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): - "ImageTake[image_Image, {r1_Integer, r2_Integer}]" - - first_row = r1.value - last_row = r2.value - - max_row, max_col = image.pixels.shape[:2] - adjusted_first_row = ( - min(first_row, max_row) if first_row > 0 else max(0, max_row + first_row) - ) - adjusted_last_row = ( - min(last_row, max_row) if last_row > 0 else max(0, max_row + first_row) - ) - - # More complicated in that it reverses the data? - # if adjusted_first_row > adjusted_last_row: - # adjusted_first_row, adjusted_last_row = adjusted_last_row, adjusted_first_row - - pixels = image.pixels[adjusted_first_row:adjusted_last_row] - - if hasattr(image, "pillow"): - box_coords = (0, adjusted_first_row, max_col, adjusted_last_row) - pillow = image.pillow.crop(box_coords) - pixels = numpy.asarray(pillow) - return Image(pixels, image.color_space, pillow=pillow) - - pixels = image.pixels[adjusted_first_row:adjusted_last_row] - return Image(pixels, image.color_space, pillow=pillow) - - def eval_rows_cols( - self, image, r1: Integer, r2: Integer, c1: Integer, c2: Integer, evaluation - ): - "ImageTake[image_Image, {r1_Integer, r2_Integer}, {c1_Integer, c2_Integer}]" - - first_row = r1.value - last_row = r2.value - first_col = c1.value - last_col = c2.value - - max_row, max_col = image.pixels.shape[:2] - adjusted_first_row = ( - min(first_row, max_row) if first_row > 0 else max(0, max_row + first_row) - ) - adjusted_last_row = ( - min(last_row, max_row) if last_row > 0 else max(0, max_row + last_row) - ) - adjusted_first_col = ( - min(first_col, max_col) if first_col > 0 else max(0, max_col + first_col) - ) - adjusted_last_col = ( - min(last_col, max_col) if last_col > 0 else max(0, max_col + last_col) - ) - - # if adjusted_first_row > adjusted_last_row: - # adjusted_first_row, adjusted_last_row = adjusted_last_row, adjusted_first_row - - # if adjusted_first_col > adjusted_last_col: - # adjusted_first_col, adjusted_last_col = adjusted_last_col, adjusted_first_col - - pixels = image.pixels[ - adjusted_first_col:adjusted_last_col, adjusted_last_row:adjusted_last_row - ] - - if hasattr(image, "pillow"): - box_coords = ( - adjusted_first_col, - adjusted_first_row, - adjusted_last_col, - adjusted_last_row, - ) - pillow = image.pillow.crop(box_coords) - pixels = numpy.asarray(pillow) - return Image(pixels, image.color_space, pillow=pillow) - - pixels = image.pixels[adjusted_first_row:adjusted_last_row] - return Image(pixels, image.color_space, pillow=pillow) - - # Older code we can remove after we condence existing code that looks like this - # - # def eval_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): - # "ImageTake[image_Image, {r1_Integer, r2_Integer}]" - # s, f = self._slice(image, r1, r2, 0) - # return Image(f(image.pixels[s]), image.color_space) - - # def eval_rows_cols( - # self, image, r1: Integer, r2: Integer, c1: Integer, c2: Integer, evaluation - # ): - # "ImageTake[image_Image, {r1_Integer, r2_Integer}, {c1_Integer, c2_Integer}]" - # sr, fr = self._slice(image, r1, r2, 0) - # sc, fc = self._slice(image, c1, c2, 1) - # return Image(fc(fr(image.pixels[sr, sc])), image.color_space) - - -class PixelValue(_ImageBuiltin): - """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/PixelValue.html</url> - - <dl> - <dt>'PixelValue[$image$, {$x$, $y$}]' - <dd>gives the value of the pixel at position {$x$, $y$} in $image$. - </dl> - - >> lena = Import["ExampleData/lena.tif"]; - >> PixelValue[lena, {1, 1}] - = {0.321569, 0.0862745, 0.223529} - #> {82 / 255, 22 / 255, 57 / 255} // N (* pixel byte values from bottom left corner *) - = {0.321569, 0.0862745, 0.223529} - - #> PixelValue[lena, {0, 1}]; - : Padding not implemented for PixelValue. - #> PixelValue[lena, {512, 1}] - = {0.72549, 0.290196, 0.317647} - #> PixelValue[lena, {513, 1}]; - : Padding not implemented for PixelValue. - #> PixelValue[lena, {1, 0}]; - : Padding not implemented for PixelValue. - #> PixelValue[lena, {1, 512}] - = {0.886275, 0.537255, 0.490196} - #> PixelValue[lena, {1, 513}]; - : Padding not implemented for PixelValue. - """ - - messages = {"nopad": "Padding not implemented for PixelValue."} - - summary_text = "get pixel value of image at a given position" - - def eval(self, image, x, y, evaluation: Evaluation): - "PixelValue[image_Image, {x_?RealNumberQ, y_?RealNumberQ}]" - x = int(x.round_to_float()) - y = int(y.round_to_float()) - height = image.pixels.shape[0] - width = image.pixels.shape[1] - if not (1 <= x <= width and 1 <= y <= height): - return evaluation.message("PixelValue", "nopad") - pixel = pixels_as_float(image.pixels)[height - y, x - 1] - if isinstance(pixel, (numpy.ndarray, numpy.generic, list)): - return ListExpression(*[MachineReal(float(x)) for x in list(pixel)]) - else: - return MachineReal(float(pixel)) - - -class PixelValuePositions(_ImageBuiltin): +class PixelValuePositions(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/PixelValuePositions.html</url> @@ -1906,7 +1189,7 @@ def eval(self, image, val, d, evaluation: Evaluation): # image attribute queries -class ImageDimensions(_ImageBuiltin): +class ImageDimensions(Builtin): """ <url> :WMA link: @@ -1932,7 +1215,7 @@ def eval(self, image, evaluation: Evaluation): return to_mathics_list(*image.dimensions(), elements_conversion_fn=Integer) -class ImageAspectRatio(_ImageBuiltin): +class ImageAspectRatio(Builtin): """ <url>:WMA link: https://reference.wolfram.com/language/ref/ImageAspectRatio.html</url> @@ -1958,7 +1241,7 @@ def eval(self, image, evaluation: Evaluation): return Expression(SymbolDivide, Integer(dim[1]), Integer(dim[0])) -class ImageChannels(_ImageBuiltin): +class ImageChannels(Builtin): """ <url>:WMA link: https://reference.wolfram.com/language/ref/ImageChannels.html</url> @@ -1983,7 +1266,7 @@ def eval(self, image, evaluation: Evaluation): return Integer(image.channels()) -class ImageType(_ImageBuiltin): +class ImageType(Builtin): """ <url> :WMA link:https://reference.wolfram.com/language/ref/ImageType.html</url> @@ -2012,340 +1295,6 @@ def eval(self, image, evaluation: Evaluation): return String(image.storage_type()) -class BinaryImageQ(_ImageTest): - """ - <url>:WMA link: - https://reference.wolfram.com/language/ref/BinaryImageQ.html</url> - - <dl> - <dt>'BinaryImageQ[$image]' - <dd>returns True if the pixels of $image are binary bit values, and False otherwise. - </dl> - - S> img = Import["ExampleData/lena.tif"]; - S> BinaryImageQ[img] - = False - - S> BinaryImageQ[Binarize[img]] - = ... - : ... - """ - - summary_text = "test whether pixels in an image are binary bit values" - - def test(self, expr): - return isinstance(expr, Image) and expr.storage_type() == "Bit" - - -# Image core classes - - -def _image_pixels(matrix): - try: - pixels = numpy.array(matrix, dtype="float64") - except ValueError: # irregular array, e.g. {{0, 1}, {0, 1, 1}} - return None - shape = pixels.shape - if len(shape) == 2 or (len(shape) == 3 and shape[2] in (1, 3, 4)): - return pixels - else: - return None - - -class ImageQ(_ImageTest): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageQ.html</url> - - <dl> - <dt>'ImageQ[Image[$pixels]]' - <dd>returns True if $pixels has dimensions from which an Image can be constructed, and False otherwise. - </dl> - - >> ImageQ[Image[{{0, 1}, {1, 0}}]] - = True - - >> ImageQ[Image[{{{0, 0, 0}, {0, 1, 0}}, {{0, 1, 0}, {0, 1, 1}}}]] - = True - - >> ImageQ[Image[{{{0, 0, 0}, {0, 1}}, {{0, 1, 0}, {0, 1, 1}}}]] - = False - - >> ImageQ[Image[{1, 0, 1}]] - = False - - >> ImageQ["abc"] - = False - """ - - summary_text = "test whether is a valid image" - - def test(self, expr): - return isinstance(expr, Image) - - -class Image(Atom): - class_head_name = "System`Image" - - # FIXME: pixels should be optional if pillow is provided. - def __init__(self, pixels, color_space, pillow=None, metadata={}, **kwargs): - super(Image, self).__init__(**kwargs) - - if pillow is not None: - self.pillow = pillow - - self.pixels = pixels - - if len(pixels.shape) == 2: - pixels = pixels.reshape(list(pixels.shape) + [1]) - - # FIXME: assigning pixels should be done lazily on demand. - # Turn pixels into a property? Include a setter? - - self.pixels = pixels - - self.color_space = color_space - self.metadata = metadata - - # Set a value for self.__hash__() once so that every time - # it is used this is fast. Note that in contrast to the - # cached object key, the hash key needs to be unique across all - # Python objects, so we include the class in the - # event that different objects have the same Python value - self.hash = hash( - ( - SymbolImage, - self.pixels.tobytes(), - self.color_space, - frozenset(self.metadata.items()), - ) - ) - - def atom_to_boxes(self, form, evaluation: Evaluation) -> ImageBox: - """ - Converts our internal Image object into a PNG base64-encoded. - """ - pixels = pixels_as_ubyte(self.color_convert("RGB", True).pixels) - shape = pixels.shape - - width = shape[1] - height = shape[0] - scaled_width = width - scaled_height = height - - # If the image was created from PIL, use that rather than - # reconstruct it from pixels which we can get wrong. - # In particular getting color-mapping info right can be - # tricky. - if hasattr(self, "pillow"): - pillow = deepcopy(self.pillow) - else: - pixels_format = "RGBA" if len(shape) >= 3 and shape[2] == 4 else "RGB" - pillow = PIL.Image.fromarray(pixels, pixels_format) - - # if the image is very small, scale it up using nearest neighbour. - min_size = 128 - if width < min_size and height < min_size: - scale = min_size / max(width, height) - scaled_width = int(scale * width) - scaled_height = int(scale * height) - pillow = pillow.resize( - (scaled_height, scaled_width), resample=PIL.Image.NEAREST - ) - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - - stream = BytesIO() - pillow.save(stream, format="png") - stream.seek(0) - contents = stream.read() - stream.close() - - encoded = base64.b64encode(contents) - encoded = b"data:image/png;base64," + encoded - - return ImageBox( - String(encoded.decode("utf-8")), - Integer(scaled_width), - Integer(scaled_height), - ) - - # __hash__ is defined so that we can store Number-derived objects - # in a set or dictionary. - def __hash__(self): - return self.hash - - def __str__(self): - return "-Image-" - - def color_convert(self, to_color_space, preserve_alpha=True): - if to_color_space == self.color_space and preserve_alpha: - return self - else: - pixels = pixels_as_float(self.pixels) - converted = convert_color( - pixels, self.color_space, to_color_space, preserve_alpha - ) - if converted is None: - return None - return Image(converted, to_color_space) - - def channels(self): - return self.pixels.shape[2] - - def default_format(self, evaluation, form): - return "-Image-" - - def dimensions(self) -> Tuple[int, int]: - shape = self.pixels.shape - return shape[1], shape[0] - - def do_copy(self): - return Image(self.pixels, self.color_space, self.metadata) - - def filter(self, f): # apply PIL filters component-wise - pixels = self.pixels - n = pixels.shape[2] - channels = [ - f(PIL.Image.fromarray(c, "L")) for c in (pixels[:, :, i] for i in range(n)) - ] - return Image(numpy.dstack(channels), self.color_space) - - def get_sort_key(self, pattern_sort=False) -> tuple: - if pattern_sort: - # If pattern_sort=True, returns the sort key that matches to an Atom. - return super(Image, self).get_sort_key(True) - else: - # If pattern is False, return a sort_key for the expression `Image[]`, - # but with a `2` instead of `1` in the 5th position, - # and adding two extra fields: the length in the 5th position, - # and a hash in the 6th place. - return (1, 3, SymbolImage, len(self.pixels), tuple(), 2, hash(self)) - - def grayscale(self): - return self.color_convert("Grayscale") - - def pil(self): - - if hasattr(self, "pillow") and self.pillow is not None: - return self.pillow - - # see https://pillow.readthedocs.io/en/stable/handbook/concepts.html - - n = self.channels() - - if n == 1: - dtype = self.pixels.dtype - - if dtype in (numpy.float32, numpy.float64): - pixels = self.pixels.astype(numpy.float32) - mode = "F" - elif dtype == numpy.uint32: - pixels = self.pixels - mode = "I" - else: - pixels = pixels_as_ubyte(self.pixels) - mode = "L" - - pixels = pixels.reshape(pixels.shape[:2]) - elif n == 3: - if self.color_space == "LAB": - mode = "LAB" - pixels = self.pixels - elif self.color_space == "HSB": - mode = "HSV" - pixels = self.pixels - elif self.color_space == "RGB": - mode = "RGB" - pixels = self.pixels - else: - mode = "RGB" - pixels = self.color_convert("RGB").pixels - - pixels = pixels_as_ubyte(pixels) - elif n == 4: - if self.color_space == "CMYK": - mode = "CMYK" - pixels = self.pixels - elif self.color_space == "RGB": - mode = "RGBA" - pixels = self.pixels - else: - mode = "RGBA" - pixels = self.color_convert("RGB").pixels - - pixels = pixels_as_ubyte(pixels) - else: - raise NotImplementedError - - return PIL.Image.fromarray(pixels, mode) - - def options(self): - return ListExpression( - Expression(SymbolRule, String("ColorSpace"), String(self.color_space)), - Expression(SymbolRule, String("MetaInformation"), self.metadata), - ) - - def sameQ(self, other) -> bool: - """Mathics SameQ""" - if not isinstance(other, Image): - return False - if self.color_space != other.color_space or self.metadata != other.metadata: - return False - return numpy.array_equal(self.pixels, other.pixels) - - def storage_type(self): - dtype = self.pixels.dtype - if dtype in (numpy.float32, numpy.float64): - return "Real" - elif dtype == numpy.uint32: - return "Bit32" - elif dtype == numpy.uint16: - return "Bit16" - elif dtype == numpy.uint8: - return "Byte" - elif dtype == bool: - return "Bit" - else: - return str(dtype) - - def to_python(self, *args, **kwargs): - return self.pixels - - -class ImageAtom(AtomBuiltin): - """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/ImageAtom.html</url> - - <dl> - <dt>'Image[...]' - <dd> produces the internal representation of an image from an array \ - of values for the pixels. - </dl> - - #> Image[{{{1,1,0},{0,1,1}}, {{1,0,1},{1,1,0}}}] - = -Image- - - #> Image[{{{0,0,0,0.25},{0,0,0,0.5}}, {{0,0,0,0.5},{0,0,0,0.75}}}] - = -Image- - """ - - summary_text = "get internal representation of an image" - requires = _image_requires - - def eval_create(self, array, evaluation: Evaluation): - "Image[array_]" - pixels = _image_pixels(array.to_python()) - if pixels is not None: - shape = pixels.shape - is_rgb = len(shape) == 3 and shape[2] in (3, 4) - return Image(pixels.clip(0, 1), "RGB" if is_rgb else "Grayscale") - else: - return Expression(SymbolImage, array) - - # complex operations @@ -2370,7 +1319,7 @@ class TextRecognize(Builtin): options = {"Language": '"English"'} - requires = _image_requires + ("pyocr",) + requires = "pyocr" summary_text = "recognize text in an image" @@ -2465,7 +1414,7 @@ class WordCloud(Builtin): "MaxItems": "Automatic", } - requires = _image_requires + ("wordcloud",) + requires = ("wordcloud",) summary_text = "show a word cloud from a list of words" diff --git a/mathics/builtin/image/__init__.py b/mathics/builtin/image/__init__.py new file mode 100644 index 000000000..18996934a --- /dev/null +++ b/mathics/builtin/image/__init__.py @@ -0,0 +1,3 @@ +""" +Image Manipulation +""" diff --git a/mathics/builtin/image/base.py b/mathics/builtin/image/base.py new file mode 100644 index 000000000..f908bd1a1 --- /dev/null +++ b/mathics/builtin/image/base.py @@ -0,0 +1,311 @@ +import base64 +from copy import deepcopy +from io import BytesIO +from typing import Tuple + +from mathics.builtin.base import AtomBuiltin, String +from mathics.builtin.box.image import ImageBox +from mathics.builtin.colors.color_internals import convert_color +from mathics.core.atoms import Atom, Integer +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.systemsymbols import SymbolImage, SymbolRule +from mathics.eval.image import pixels_as_float, pixels_as_ubyte + +_skimage_requires = ("skimage", "scipy", "matplotlib", "networkx") + + +try: + import warnings + + import numpy + import PIL + import PIL.ImageEnhance + import PIL.ImageFilter + import PIL.ImageOps + +except ImportError: + pass + + +def _image_pixels(matrix): + try: + pixels = numpy.array(matrix, dtype="float64") + except ValueError: # irregular array, e.g. {{0, 1}, {0, 1, 1}} + return None + shape = pixels.shape + if len(shape) == 2 or (len(shape) == 3 and shape[2] in (1, 3, 4)): + return pixels + else: + return None + + +class _SkimageBuiltin: + """ + Image Builtins that require scikit-image. + """ + + requires = _skimage_requires + + +class Image(Atom): + class_head_name = "System`Image" + + # FIXME: pixels should be optional if pillow is provided. + def __init__(self, pixels, color_space, pillow=None, metadata={}, **kwargs): + super(Image, self).__init__(**kwargs) + + if pillow is not None: + self.pillow = pillow + + self.pixels = pixels + + if len(pixels.shape) == 2: + pixels = pixels.reshape(list(pixels.shape) + [1]) + + # FIXME: assigning pixels should be done lazily on demand. + # Turn pixels into a property? Include a setter? + + self.pixels = pixels + + self.color_space = color_space + self.metadata = metadata + + # Set a value for self.__hash__() once so that every time + # it is used this is fast. Note that in contrast to the + # cached object key, the hash key needs to be unique across all + # Python objects, so we include the class in the + # event that different objects have the same Python value + self.hash = hash( + ( + SymbolImage, + self.pixels.tobytes(), + self.color_space, + frozenset(self.metadata.items()), + ) + ) + + def atom_to_boxes(self, form, evaluation: Evaluation) -> ImageBox: + """ + Converts our internal Image object into a PNG base64-encoded. + """ + pixels = pixels_as_ubyte(self.color_convert("RGB", True).pixels) + shape = pixels.shape + + width = shape[1] + height = shape[0] + scaled_width = width + scaled_height = height + + # If the image was created from PIL, use that rather than + # reconstruct it from pixels which we can get wrong. + # In particular getting color-mapping info right can be + # tricky. + if hasattr(self, "pillow"): + pillow = deepcopy(self.pillow) + else: + pixels_format = "RGBA" if len(shape) >= 3 and shape[2] == 4 else "RGB" + pillow = PIL.Image.fromarray(pixels, pixels_format) + + # if the image is very small, scale it up using nearest neighbour. + min_size = 128 + if width < min_size and height < min_size: + scale = min_size / max(width, height) + scaled_width = int(scale * width) + scaled_height = int(scale * height) + pillow = pillow.resize( + (scaled_height, scaled_width), resample=PIL.Image.NEAREST + ) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + + stream = BytesIO() + pillow.save(stream, format="png") + stream.seek(0) + contents = stream.read() + stream.close() + + encoded = base64.b64encode(contents) + encoded = b"data:image/png;base64," + encoded + + return ImageBox( + String(encoded.decode("utf-8")), + Integer(scaled_width), + Integer(scaled_height), + ) + + # __hash__ is defined so that we can store Number-derived objects + # in a set or dictionary. + def __hash__(self): + return self.hash + + def __str__(self): + return "-Image-" + + def color_convert(self, to_color_space, preserve_alpha=True): + if to_color_space == self.color_space and preserve_alpha: + return self + else: + pixels = pixels_as_float(self.pixels) + converted = convert_color( + pixels, self.color_space, to_color_space, preserve_alpha + ) + if converted is None: + return None + return Image(converted, to_color_space) + + def channels(self): + return self.pixels.shape[2] + + def default_format(self, evaluation, form): + return "-Image-" + + def dimensions(self) -> Tuple[int, int]: + shape = self.pixels.shape + return shape[1], shape[0] + + def do_copy(self): + return Image(self.pixels, self.color_space, self.metadata) + + def filter(self, f): # apply PIL filters component-wise + pixels = self.pixels + n = pixels.shape[2] + channels = [ + f(PIL.Image.fromarray(c, "L")) for c in (pixels[:, :, i] for i in range(n)) + ] + return Image(numpy.dstack(channels), self.color_space) + + def get_sort_key(self, pattern_sort=False) -> tuple: + if pattern_sort: + # If pattern_sort=True, returns the sort key that matches to an Atom. + return super(Image, self).get_sort_key(True) + else: + # If pattern is False, return a sort_key for the expression `Image[]`, + # but with a `2` instead of `1` in the 5th position, + # and adding two extra fields: the length in the 5th position, + # and a hash in the 6th place. + return (1, 3, SymbolImage, len(self.pixels), tuple(), 2, hash(self)) + + def grayscale(self): + return self.color_convert("Grayscale") + + def pil(self): + + if hasattr(self, "pillow") and self.pillow is not None: + return self.pillow + + # see https://pillow.readthedocs.io/en/stable/handbook/concepts.html + + n = self.channels() + + if n == 1: + dtype = self.pixels.dtype + + if dtype in (numpy.float32, numpy.float64): + pixels = self.pixels.astype(numpy.float32) + mode = "F" + elif dtype == numpy.uint32: + pixels = self.pixels + mode = "I" + else: + pixels = pixels_as_ubyte(self.pixels) + mode = "L" + + pixels = pixels.reshape(pixels.shape[:2]) + elif n == 3: + if self.color_space == "LAB": + mode = "LAB" + pixels = self.pixels + elif self.color_space == "HSB": + mode = "HSV" + pixels = self.pixels + elif self.color_space == "RGB": + mode = "RGB" + pixels = self.pixels + else: + mode = "RGB" + pixels = self.color_convert("RGB").pixels + + pixels = pixels_as_ubyte(pixels) + elif n == 4: + if self.color_space == "CMYK": + mode = "CMYK" + pixels = self.pixels + elif self.color_space == "RGB": + mode = "RGBA" + pixels = self.pixels + else: + mode = "RGBA" + pixels = self.color_convert("RGB").pixels + + pixels = pixels_as_ubyte(pixels) + else: + raise NotImplementedError + + return PIL.Image.fromarray(pixels, mode) + + def options(self): + return ListExpression( + Expression(SymbolRule, String("ColorSpace"), String(self.color_space)), + Expression(SymbolRule, String("MetaInformation"), self.metadata), + ) + + def sameQ(self, other) -> bool: + """Mathics SameQ""" + if not isinstance(other, Image): + return False + if self.color_space != other.color_space or self.metadata != other.metadata: + return False + return numpy.array_equal(self.pixels, other.pixels) + + def storage_type(self): + dtype = self.pixels.dtype + if dtype in (numpy.float32, numpy.float64): + return "Real" + elif dtype == numpy.uint32: + return "Bit32" + elif dtype == numpy.uint16: + return "Bit16" + elif dtype == numpy.uint8: + return "Byte" + elif dtype == bool: + return "Bit" + else: + return str(dtype) + + def to_python(self, *args, **kwargs): + return self.pixels + + +class ImageAtom(AtomBuiltin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ImageAtom.html</url> + + <dl> + <dt>'Image[...]' + <dd> produces the internal representation of an image from an array \ + of values for the pixels. + </dl> + + #> Image[{{{1,1,0},{0,1,1}}, {{1,0,1},{1,1,0}}}] + = -Image- + + #> Image[{{{0,0,0,0.25},{0,0,0,0.5}}, {{0,0,0,0.5},{0,0,0,0.75}}}] + = -Image- + """ + + summary_text = "get internal representation of an image" + + def eval_create(self, array, evaluation: Evaluation): + "Image[array_]" + pixels = _image_pixels(array.to_python()) + if pixels is not None: + shape = pixels.shape + is_rgb = len(shape) == 3 and shape[2] in (3, 4) + return Image(pixels.clip(0, 1), "RGB" if is_rgb else "Grayscale") + else: + return Expression(SymbolImage, array) diff --git a/mathics/builtin/image/basic.py b/mathics/builtin/image/basic.py new file mode 100644 index 000000000..c1c0e89cf --- /dev/null +++ b/mathics/builtin/image/basic.py @@ -0,0 +1,204 @@ +""" +Basic Image Processing +""" + +import numpy +import PIL + +from mathics.builtin.base import Builtin +from mathics.builtin.image.base import Image +from mathics.core.atoms import Integer +from mathics.core.convert.python import from_python +from mathics.core.evaluation import Evaluation +from mathics.core.list import ListExpression +from mathics.eval.image import pixels_as_float + + +class Blur(Builtin): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Blur.html</url> + + <dl> + <dt>'Blur[$image$]' + <dd>gives a blurred version of $image$. + + <dt>'Blur[$image$, $r$]' + <dd>blurs $image$ with a kernel of size $r$. + </dl> + + >> lena = Import["ExampleData/lena.tif"]; + >> Blur[lena] + = -Image- + >> Blur[lena, 5] + = -Image- + """ + + summary_text = "blur an image" + rules = { + "Blur[image_Image]": "Blur[image, 2]", + "Blur[image_Image, r_?RealNumberQ]": "ImageConvolve[image, BoxMatrix[r] / Total[Flatten[BoxMatrix[r]]]]", + } + + +class ImageAdjust(Builtin): + """ + + <url>:WMA link: + https://reference.wolfram.com/language/ref/ImageAdjust.html</url> + + <dl> + <dt>'ImageAdjust[$image$]' + <dd>adjusts the levels in $image$. + + <dt>'ImageAdjust[$image$, $c$]' + <dd>adjusts the contrast in $image$ by $c$. + + <dt>'ImageAdjust[$image$, {$c$, $b$}]' + <dd>adjusts the contrast $c$, and brightness $b$ in $image$. + + <dt>'ImageAdjust[$image$, {$c$, $b$, $g$}]' + <dd>adjusts the contrast $c$, brightness $b$, and gamma $g$ in $image$. + </dl> + + >> lena = Import["ExampleData/lena.tif"]; + >> ImageAdjust[lena] + = -Image- + """ + + summary_text = "adjust levels, brightness, contrast, gamma, etc" + rules = { + "ImageAdjust[image_Image, c_?RealNumberQ]": "ImageAdjust[image, {c, 0, 1}]", + "ImageAdjust[image_Image, {c_?RealNumberQ, b_?RealNumberQ}]": "ImageAdjust[image, {c, b, 1}]", + } + + def eval_auto(self, image, evaluation: Evaluation): + "ImageAdjust[image_Image]" + pixels = pixels_as_float(image.pixels) + + # channel limits + axis = (0, 1) + cmaxs, cmins = pixels.max(axis=axis), pixels.min(axis=axis) + + # normalise channels + scales = cmaxs - cmins + if not scales.shape: + scales = numpy.array([scales]) + scales[scales == 0.0] = 1 + pixels -= cmins + pixels /= scales + return Image(pixels, image.color_space) + + def eval_contrast_brightness_gamma(self, image, c, b, g, evaluation: Evaluation): + "ImageAdjust[image_Image, {c_?RealNumberQ, b_?RealNumberQ, g_?RealNumberQ}]" + + im = image.pil() + + # gamma + g = g.round_to_float() + if g != 1: + im = PIL.ImageEnhance.Color(im).enhance(g) + + # brightness + b = b.round_to_float() + if b != 0: + im = PIL.ImageEnhance.Brightness(im).enhance(b + 1) + + # contrast + c = c.round_to_float() + if c != 0: + im = PIL.ImageEnhance.Contrast(im).enhance(c + 1) + + return Image(numpy.array(im), image.color_space) + + +class ImagePartition(Builtin): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ImagePartition.html</url> + + <dl> + <dt>'ImagePartition[$image$, $s$]' + <dd>Partitions an image into an array of $s$ x $s$ pixel subimages. + + <dt>'ImagePartition[$image$, {$w$, $h$}]' + <dd>Partitions an image into an array of $w$ x $h$ pixel subimages. + </dl> + + >> lena = Import["ExampleData/lena.tif"]; + >> ImageDimensions[lena] + = {512, 512} + >> ImagePartition[lena, 256] + = {{-Image-, -Image-}, {-Image-, -Image-}} + + >> ImagePartition[lena, {512, 128}] + = {{-Image-}, {-Image-}, {-Image-}, {-Image-}} + + #> ImagePartition[lena, 257] + = {{-Image-}} + #> ImagePartition[lena, 512] + = {{-Image-}} + #> ImagePartition[lena, 513] + = {} + #> ImagePartition[lena, {256, 300}] + = {{-Image-, -Image-}} + + #> ImagePartition[lena, {0, 300}] + : {0, 300} is not a valid size specification for image partitions. + = ImagePartition[-Image-, {0, 300}] + """ + + summary_text = "divide an image in an array of sub-images" + rules = {"ImagePartition[i_Image, s_Integer]": "ImagePartition[i, {s, s}]"} + + messages = {"arg2": "`1` is not a valid size specification for image partitions."} + + def eval(self, image, w: Integer, h: Integer, evaluation: Evaluation): + "ImagePartition[image_Image, {w_Integer, h_Integer}]" + py_w = w.value + py_h = h.value + if py_w <= 0 or py_h <= 0: + return evaluation.message("ImagePartition", "arg2", ListExpression(w, h)) + pixels = image.pixels + shape = pixels.shape + + # drop blocks less than w x h + parts = [] + for yi in range(shape[0] // py_h): + row = [] + for xi in range(shape[1] // py_w): + p = pixels[yi * py_h : (yi + 1) * py_h, xi * py_w : (xi + 1) * py_w] + row.append(Image(p, image.color_space)) + if row: + parts.append(row) + return from_python(parts) + + +class Sharpen(Builtin): + """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/Sharpen.html</url> + + <dl> + <dt>'Sharpen[$image$]' + <dd>gives a sharpened version of $image$. + + <dt>'Sharpen[$image$, $r$]' + <dd>sharpens $image$ with a kernel of size $r$. + </dl> + + >> lena = Import["ExampleData/lena.tif"]; + >> Sharpen[lena] + = -Image- + >> Sharpen[lena, 5] + = -Image- + """ + + summary_text = "sharpen version of an image" + rules = {"Sharpen[i_Image]": "Sharpen[i, 2]"} + + def eval(self, image, r, evaluation: Evaluation): + "Sharpen[image_Image, r_?RealNumberQ]" + f = PIL.ImageFilter.UnsharpMask(r.round_to_float()) + return image.filter(lambda im: im.filter(f)) + + +# Todo Darker, ImageClip, ImageEffect, ImageRestyle, Lighter, Threshold diff --git a/mathics/builtin/image/geometric.py b/mathics/builtin/image/geometric.py new file mode 100644 index 000000000..6f0041ef3 --- /dev/null +++ b/mathics/builtin/image/geometric.py @@ -0,0 +1,278 @@ +""" +Geometric Operations +""" + +import math + +import numpy +import PIL +import PIL.ImageEnhance +import PIL.ImageFilter +import PIL.ImageOps + +from mathics.builtin.base import Builtin +from mathics.builtin.image.base import Image +from mathics.core.convert.expression import to_mathics_list +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import SymbolRule +from mathics.eval.image import get_image_size_spec, resize_width_height + + +class ImageResize(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ImageResize.html</url> + + <dl> + <dt>'ImageResize[$image$, $width$]' + <dd> + + <dt>'ImageResize[$image$, {$width$, $height$}]' + <dd> + </dl> + + The Resampling option can be used to specify how to resample the image. Options are: + <ul> + <li>Automatic + <li>Bicubic + <li>Bilinear + <li>Box + <li>Hamming + <li>Lanczos + <li>Nearest + </ul> + + See <url> + :Pillow Filters: + https://pillow.readthedocs.io/en/stable/handbook/concepts.html#filters</url>\ + for a description of these. + + S> alice = Import["ExampleData/MadTeaParty.gif"] + = -Image- + + S> shape = ImageDimensions[alice] + = {640, 487} + + S> ImageResize[alice, shape / 2] + = -Image- + + The default sampling method is "Bicubic" which has pretty good upscaling \ + and downscaling quality. However "Box" is the fastest: + + + S> ImageResize[alice, shape / 2, Resampling -> "Box"] + = -Image- + """ + + messages = { + "imgrssz": "The size `1` is not a valid image size specification.", + "imgrsm": "Invalid resampling method `1`.", + "gaussaspect": "Gaussian resampling needs to maintain aspect ratio.", + "skimage": "Please install scikit-image to use Resampling -> Gaussian.", + } + + options = {"Resampling": "Automatic"} + summary_text = "resize an image" + + def eval_resize_width(self, image, s, evaluation, options): + "ImageResize[image_Image, s_, OptionsPattern[ImageResize]]" + old_w = image.pixels.shape[1] + if s.has_form("List", 1): + width = s.elements[0] + else: + width = s + w = get_image_size_spec(old_w, width) + if w is None: + return evaluation.message("ImageResize", "imgrssz", s) + if s.has_form("List", 1): + height = width + else: + height = Symbol("Automatic") + return self.eval_resize_width_height(image, width, height, evaluation, options) + + def eval_resize_width_height(self, image, width, height, evaluation, options): + "ImageResize[image_Image, {width_, height_}, OptionsPattern[ImageResize]]" + # resampling method + resampling = self.get_option(options, "Resampling", evaluation) + if ( + isinstance(resampling, Symbol) + and resampling.get_name() == "System`Automatic" + ): + resampling_name = "Bicubic" + else: + resampling_name = resampling.value + + # find new size + old_w, old_h = image.pixels.shape[1], image.pixels.shape[0] + w = get_image_size_spec(old_w, width) + h = get_image_size_spec(old_h, height) + if h is None or w is None: + return evaluation.message( + "ImageResize", "imgrssz", to_mathics_list(width, height) + ) + + # handle Automatic + old_aspect_ratio = old_w / old_h + if w == 0 and h == 0: + # if both width and height are Automatic then use old values + w, h = old_w, old_h + elif w == 0: + w = max(1, h * old_aspect_ratio) + elif h == 0: + h = max(1, w / old_aspect_ratio) + + if resampling_name != "Gaussian": + # Gaussian need to unrounded values to compute scaling ratios. + # round to closest pixel for other methods. + h, w = int(round(h)), int(round(w)) + + # perform the resize + return resize_width_height(image, w, h, resampling_name, evaluation) + + +class ImageReflect(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ImageReflect.html</url> + <dl> + <dt>'ImageReflect[$image$]' + <dd>Flips $image$ top to bottom. + + <dt>'ImageReflect[$image$, $side$]' + <dd>Flips $image$ so that $side$ is interchanged with its opposite. + + <dt>'ImageReflect[$image$, $side_1$ -> $side_2$]' + <dd>Flips $image$ so that $side_1$ is interchanged with $side_2$. + </dl> + + >> ein = Import["ExampleData/Einstein.jpg"]; + >> ImageReflect[ein] + = -Image- + >> ImageReflect[ein, Left] + = -Image- + >> ImageReflect[ein, Left -> Top] + = -Image- + + #> ein == ImageReflect[ein, Left -> Left] == ImageReflect[ein, Right -> Right] == ImageReflect[ein, Top -> Top] == ImageReflect[ein, Bottom -> Bottom] + = True + #> ImageReflect[ein, Left -> Right] == ImageReflect[ein, Right -> Left] == ImageReflect[ein, Left] == ImageReflect[ein, Right] + = True + #> ImageReflect[ein, Bottom -> Top] == ImageReflect[ein, Top -> Bottom] == ImageReflect[ein, Top] == ImageReflect[ein, Bottom] + = True + #> ImageReflect[ein, Left -> Top] == ImageReflect[ein, Right -> Bottom] (* Transpose *) + = True + #> ImageReflect[ein, Left -> Bottom] == ImageReflect[ein, Right -> Top] (* Anti-Transpose *) + = True + + #> ImageReflect[ein, x -> Top] + : x -> Top is not a valid 2D reflection specification. + = ImageReflect[-Image-, x -> Top] + """ + + summary_text = "reflect an image" + rules = { + "ImageReflect[image_Image]": "ImageReflect[image, Top -> Bottom]", + "ImageReflect[image_Image, Top|Bottom]": "ImageReflect[image, Top -> Bottom]", + "ImageReflect[image_Image, Left|Right]": "ImageReflect[image, Left -> Right]", + } + + messages = {"bdrfl2": "`1` is not a valid 2D reflection specification."} + + def eval(self, image, orig, dest, evaluation: Evaluation): + "ImageReflect[image_Image, Rule[orig_, dest_]]" + if isinstance(orig, Symbol) and isinstance(dest, Symbol): + specs = [orig.get_name(), dest.get_name()] + specs.sort() # `Top -> Bottom` is the same as `Bottom -> Top` + + def anti_transpose(i): + return numpy.flipud(numpy.transpose(numpy.flipud(i))) + + def no_op(i): + return i + + method = { + ("System`Bottom", "System`Top"): numpy.flipud, + ("System`Left", "System`Right"): numpy.fliplr, + ("System`Left", "System`Top"): numpy.transpose, + ("System`Right", "System`Top"): anti_transpose, + ("System`Bottom", "System`Left"): anti_transpose, + ("System`Bottom", "System`Right"): numpy.transpose, + ("System`Bottom", "System`Bottom"): no_op, + ("System`Top", "System`Top"): no_op, + ("System`Left", "System`Left"): no_op, + ("System`Right", "System`Right"): no_op, + }.get(tuple(specs), None) + + if method is None: + return evaluation.message( + "ImageReflect", "bdrfl2", Expression(SymbolRule, orig, dest) + ) + + return Image(method(image.pixels), image.color_space) + + +class ImageRotate(Builtin): + """ + + <url>:WMA link: + https://reference.wolfram.com/language/ref/ImageRotate.html</url> + + <dl> + <dt>'ImageRotate[$image$]' + <dd>Rotates $image$ 90 degrees counterclockwise. + <dt>'ImageRotate[$image$, $theta$]' + <dd>Rotates $image$ by a given angle $theta$ + </dl> + + >> ein = Import["ExampleData/Einstein.jpg"]; + + >> ImageRotate[ein] + = -Image- + + >> ImageRotate[ein, 45 Degree] + = -Image- + + >> ImageRotate[ein, Pi / 4] + = -Image- + + #> ImageRotate[ein, ein] + : Angle -Image- should be a real number, one of Top, Bottom, Left, Right, or a rule from one to another. + = ImageRotate[-Image-, -Image-] + """ + + messages = { + "imgang": "Angle `1` should be a real number, one of Top, Bottom, Left, Right, or a rule from one to another." + } + + rules = {"ImageRotate[i_Image]": "ImageRotate[i, 90 Degree]"} + + summary_text = "rotate an image" + + def eval(self, image, angle, evaluation: Evaluation): + "ImageRotate[image_Image, angle_]" + + # FIXME: this test I suppose is okay in that it checks more or less what is needed. + # However there might be a better test like for Real-valued-ness which could be used + # instead. + py_angle = ( + angle.round_to_float(evaluation) + if hasattr(angle, "round_to_float") + else None + ) + + if py_angle is None: + return evaluation.message("ImageRotate", "imgang", angle) + + def rotate(im): + return im.rotate( + 180 * py_angle / math.pi, resample=PIL.Image.BICUBIC, expand=True + ) + + return image.filter(rotate) + + +# TODO Thumbnail diff --git a/mathics/builtin/image/structure.py b/mathics/builtin/image/structure.py new file mode 100644 index 000000000..be29c83bc --- /dev/null +++ b/mathics/builtin/image/structure.py @@ -0,0 +1,242 @@ +""" +Structural Image Operations +""" +import numpy + +from mathics.builtin.base import Builtin +from mathics.builtin.image.base import Image +from mathics.core.atoms import Integer, MachineReal +from mathics.core.evaluation import Evaluation +from mathics.core.list import ListExpression +from mathics.eval.image import numpy_flip, pixels_as_float + + +class ImageTake(Builtin): + """ + Extract Image parts <url>:WMA link: + https://reference.wolfram.com/language/ref/ImageTake.html</url> + <dl> + <dt>'ImageTake[$image$, $n$]' + <dd>gives the first $n$ rows of $image$. + + <dt>'ImageTake[$image$, -$n$]' + <dd>gives the last $n$ rows of $image$. + + <dt>'ImageTake[$image$, {$r1$, $r2$}]' + <dd>gives rows $r1$, ..., $r2$ of $image$. + + <dt>'ImageTake[$image$, {$r1$, $r2$}, {$c1$, $c2$}]' + <dd>gives a cropped version of $image$. + </dl> + + Crop to the include only the upper half (244 rows) of an image: + >> alice = Import["ExampleData/MadTeaParty.gif"]; ImageTake[alice, 244] + = -Image- + + Now crop to the include the lower half of that image: + >> ImageTake[alice, -244] + = -Image- + + Just the text around the hat: + >> ImageTake[alice, {40, 150}, {500, 600}] + = -Image- + + """ + + summary_text = "extract image parts" + + # FIXME: this probably should be moved out since WMA docs + # suggest this kind of thing is done across many kinds of + # images. + def _image_slice(self, image, i1: Integer, i2: Integer, axis): + """ + Extracts a slice of an image and return a slice + indicting a slice, a function flip, that will + reverse the pixels in an image if necessary. + """ + n = image.pixels.shape[axis] + py_i1 = min(max(i1.value - 1, 0), n - 1) + py_i2 = min(max(i2.value - 1, 0), n - 1) + + def flip(pixels): + if py_i1 > py_i2: + return numpy_flip(pixels, axis) + else: + return pixels + + return slice(min(py_i1, py_i2), 1 + max(py_i1, py_i2)), flip + + # The reason it is hard to make a rules that turn Image[image, n], + # or Image[, {r1, r2} into the generic form Image[image, {r1, r2}, + # {c1, c2}] there can be negative numbers, e.g. -n. Also, that + # missing values, in particular r2 and c2, when filled out can be + # dependent on the size of the image. + + # FIXME: create common functions to process ranges. + # FIXME: fix up and use _image_slice. + + def eval_n(self, image, n: Integer, evaluation: Evaluation): + "ImageTake[image_Image, n_Integer]" + py_n = n.value + max_y, max_x = image.pixels.shape[:2] + if py_n >= 0: + adjusted_n = min(py_n, max_y) + pixels = image.pixels[:adjusted_n] + box_coords = (0, 0, max_x, adjusted_n) + elif py_n < 0: + adjusted_n = max(0, max_y + py_n) + pixels = image.pixels[adjusted_n:] + box_coords = (0, adjusted_n, max_x, max_y) + + if hasattr(image, "pillow"): + pillow = image.pillow.crop(box_coords) + pixels = numpy.asarray(pillow) + return Image(pixels, image.color_space, pillow=pillow) + + return Image(pixels, image.color_space, pillow=pillow) + + def eval_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): + "ImageTake[image_Image, {r1_Integer, r2_Integer}]" + + first_row = r1.value + last_row = r2.value + + max_row, max_col = image.pixels.shape[:2] + adjusted_first_row = ( + min(first_row, max_row) if first_row > 0 else max(0, max_row + first_row) + ) + adjusted_last_row = ( + min(last_row, max_row) if last_row > 0 else max(0, max_row + first_row) + ) + + # More complicated in that it reverses the data? + # if adjusted_first_row > adjusted_last_row: + # adjusted_first_row, adjusted_last_row = adjusted_last_row, adjusted_first_row + + pixels = image.pixels[adjusted_first_row:adjusted_last_row] + + if hasattr(image, "pillow"): + box_coords = (0, adjusted_first_row, max_col, adjusted_last_row) + pillow = image.pillow.crop(box_coords) + pixels = numpy.asarray(pillow) + return Image(pixels, image.color_space, pillow=pillow) + + pixels = image.pixels[adjusted_first_row:adjusted_last_row] + return Image(pixels, image.color_space, pillow=pillow) + + def eval_rows_cols( + self, image, r1: Integer, r2: Integer, c1: Integer, c2: Integer, evaluation + ): + "ImageTake[image_Image, {r1_Integer, r2_Integer}, {c1_Integer, c2_Integer}]" + + first_row = r1.value + last_row = r2.value + first_col = c1.value + last_col = c2.value + + max_row, max_col = image.pixels.shape[:2] + adjusted_first_row = ( + min(first_row, max_row) if first_row > 0 else max(0, max_row + first_row) + ) + adjusted_last_row = ( + min(last_row, max_row) if last_row > 0 else max(0, max_row + last_row) + ) + adjusted_first_col = ( + min(first_col, max_col) if first_col > 0 else max(0, max_col + first_col) + ) + adjusted_last_col = ( + min(last_col, max_col) if last_col > 0 else max(0, max_col + last_col) + ) + + # if adjusted_first_row > adjusted_last_row: + # adjusted_first_row, adjusted_last_row = adjusted_last_row, adjusted_first_row + + # if adjusted_first_col > adjusted_last_col: + # adjusted_first_col, adjusted_last_col = adjusted_last_col, adjusted_first_col + + pixels = image.pixels[ + adjusted_first_col:adjusted_last_col, adjusted_last_row:adjusted_last_row + ] + + if hasattr(image, "pillow"): + box_coords = ( + adjusted_first_col, + adjusted_first_row, + adjusted_last_col, + adjusted_last_row, + ) + pillow = image.pillow.crop(box_coords) + pixels = numpy.asarray(pillow) + return Image(pixels, image.color_space, pillow=pillow) + + pixels = image.pixels[adjusted_first_row:adjusted_last_row] + return Image(pixels, image.color_space, pillow=pillow) + + # Older code we can remove after we condence existing code that looks like this + # + # def eval_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): + # "ImageTake[image_Image, {r1_Integer, r2_Integer}]" + # s, f = self._slice(image, r1, r2, 0) + # return Image(f(image.pixels[s]), image.color_space) + + # def eval_rows_cols( + # self, image, r1: Integer, r2: Integer, c1: Integer, c2: Integer, evaluation + # ): + # "ImageTake[image_Image, {r1_Integer, r2_Integer}, {c1_Integer, c2_Integer}]" + # sr, fr = self._slice(image, r1, r2, 0) + # sc, fc = self._slice(image, c1, c2, 1) + # return Image(fc(fr(image.pixels[sr, sc])), image.color_space) + + +# FIXME: move to not-yet created Pixel Operations +class PixelValue(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/PixelValue.html</url> + + <dl> + <dt>'PixelValue[$image$, {$x$, $y$}]' + <dd>gives the value of the pixel at position {$x$, $y$} in $image$. + </dl> + + >> lena = Import["ExampleData/lena.tif"]; + >> PixelValue[lena, {1, 1}] + = {0.321569, 0.0862745, 0.223529} + #> {82 / 255, 22 / 255, 57 / 255} // N (* pixel byte values from bottom left corner *) + = {0.321569, 0.0862745, 0.223529} + + #> PixelValue[lena, {0, 1}]; + : Padding not implemented for PixelValue. + #> PixelValue[lena, {512, 1}] + = {0.72549, 0.290196, 0.317647} + #> PixelValue[lena, {513, 1}]; + : Padding not implemented for PixelValue. + #> PixelValue[lena, {1, 0}]; + : Padding not implemented for PixelValue. + #> PixelValue[lena, {1, 512}] + = {0.886275, 0.537255, 0.490196} + #> PixelValue[lena, {1, 513}]; + : Padding not implemented for PixelValue. + """ + + messages = {"nopad": "Padding not implemented for PixelValue."} + + summary_text = "get pixel value of image at a given position" + + def eval(self, image, x, y, evaluation: Evaluation): + "PixelValue[image_Image, {x_?RealNumberQ, y_?RealNumberQ}]" + x = int(x.round_to_float()) + y = int(y.round_to_float()) + height = image.pixels.shape[0] + width = image.pixels.shape[1] + if not (1 <= x <= width and 1 <= y <= height): + return evaluation.message("PixelValue", "nopad") + pixel = pixels_as_float(image.pixels)[height - y, x - 1] + if isinstance(pixel, (numpy.ndarray, numpy.generic, list)): + return ListExpression(*[MachineReal(float(x)) for x in list(pixel)]) + else: + return MachineReal(float(pixel)) + + +# TODO; ImageCrop, ImageTrip, ImagePad, BorderDimensions diff --git a/mathics/builtin/image/test.py b/mathics/builtin/image/test.py new file mode 100644 index 000000000..2be81cc7c --- /dev/null +++ b/mathics/builtin/image/test.py @@ -0,0 +1,69 @@ +""" +Image testing +""" +from mathics.builtin.base import Test +from mathics.builtin.image.base import Image, _skimage_requires + + +class _ImageTest(Test): + """ + Testing Image Builtins -- those function names ending with "Q" -- that require scikit-image. + """ + + requires = _skimage_requires + + +class BinaryImageQ(_ImageTest): + """ + <url>:WMA link: + https://reference.wolfram.com/language/ref/BinaryImageQ.html</url> + + <dl> + <dt>'BinaryImageQ[$image]' + <dd>returns True if the pixels of $image are binary bit values, and False otherwise. + </dl> + + S> img = Import["ExampleData/lena.tif"]; + S> BinaryImageQ[img] + = False + + S> BinaryImageQ[Binarize[img]] + = ... + : ... + """ + + summary_text = "test whether pixels in an image are binary bit values" + + def test(self, expr): + return isinstance(expr, Image) and expr.storage_type() == "Bit" + + +class ImageQ(_ImageTest): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ImageQ.html</url> + + <dl> + <dt>'ImageQ[Image[$pixels]]' + <dd>returns True if $pixels has dimensions from which an Image can be constructed, and False otherwise. + </dl> + + >> ImageQ[Image[{{0, 1}, {1, 0}}]] + = True + + >> ImageQ[Image[{{{0, 0, 0}, {0, 1, 0}}, {{0, 1, 0}, {0, 1, 1}}}]] + = True + + >> ImageQ[Image[{{{0, 0, 0}, {0, 1}}, {{0, 1, 0}, {0, 1, 1}}}]] + = False + + >> ImageQ[Image[{1, 0, 1}]] + = False + + >> ImageQ["abc"] + = False + """ + + summary_text = "test whether is a valid image" + + def test(self, expr): + return isinstance(expr, Image) diff --git a/mathics/builtin/options.py b/mathics/builtin/options.py index ce0127c0d..1db938368 100644 --- a/mathics/builtin/options.py +++ b/mathics/builtin/options.py @@ -12,7 +12,7 @@ """ from mathics.builtin.base import Builtin, Test, get_option -from mathics.builtin.drawing.image import Image +from mathics.builtin.image.base import Image from mathics.core.atoms import String from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression, SymbolDefault, get_default_value diff --git a/mathics/eval/image.py b/mathics/eval/image.py index 3f78371bd..293800577 100644 --- a/mathics/eval/image.py +++ b/mathics/eval/image.py @@ -234,7 +234,7 @@ def resize_width_height( """ workhorse part of ImageResize[] after mathic options have been processed. """ - from mathics.builtin.drawing.image import Image + from mathics.builtin.image.base import Image if resampling_name not in resampling_names2PIL.keys(): return evaluation.message("ImageResize", "imgrsm", resampling_name) diff --git a/setup.py b/setup.py index 5e963c25c..9be50f59b 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ sys, "pypy_version_info" ) -INSTALL_REQUIRES = ["Mathics-Scanner >= 1.3.0.dev0"] +INSTALL_REQUIRES = ["Mathics-Scanner >= 1.3.0.dev0", "pillow"] # Ensure user has the correct Python version # Address specific package dependencies based on Python version @@ -178,6 +178,7 @@ def subdirs(root, file="*.*", depth=10): "mathics.builtin.files_io", "mathics.builtin.forms", "mathics.builtin.functional", + "mathics.builtin.image", "mathics.builtin.intfns", "mathics.builtin.list", "mathics.builtin.matrices", From ebd3db78db5b7cb88c65ba8d67c30a3df1fd1cf6 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Thu, 29 Dec 2022 17:19:57 -0500 Subject: [PATCH 114/121] Split out Image Compositions --- mathics/builtin/drawing/image-misc.py | 147 +----------------------- mathics/builtin/image/composition.py | 158 ++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 146 deletions(-) create mode 100644 mathics/builtin/image/composition.py diff --git a/mathics/builtin/drawing/image-misc.py b/mathics/builtin/drawing/image-misc.py index 8b4097215..3add35c63 100644 --- a/mathics/builtin/drawing/image-misc.py +++ b/mathics/builtin/drawing/image-misc.py @@ -23,7 +23,7 @@ from mathics.builtin.base import Builtin, String from mathics.builtin.colors.color_internals import colorspaces as known_colorspaces from mathics.builtin.image.base import Image, _SkimageBuiltin -from mathics.core.atoms import Integer, Integer0, Integer1, MachineReal, Rational, Real +from mathics.core.atoms import Integer, Integer0, Integer1, MachineReal from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_python from mathics.core.evaluation import Evaluation @@ -124,151 +124,6 @@ def eval(self, path: String, evaluation: Evaluation): return ListExpression(*image_list_expression) -class _ImageArithmetic(Builtin): - messages = {"bddarg": "Expecting a number, image, or graphics instead of `1`."} - - @staticmethod - def convert_Image(image): - assert isinstance(image, Image) - return pixels_as_float(image.pixels) - - @staticmethod - def convert_args(*args): - images = [] - for arg in args: - if isinstance(arg, Image): - images.append(_ImageArithmetic.convert_Image(arg)) - elif isinstance(arg, (Integer, Rational, Real)): - images.append(float(arg.to_python())) - else: - return None, arg - return images, None - - @staticmethod - def _reduce(iterable, ufunc): - result = None - for i in iterable: - if result is None: - # ufunc is destructive so copy first - result = numpy.copy(i) - else: - # e.g. result *= i - ufunc(result, i, result) - return result - - def eval(self, image, args, evaluation: Evaluation): - "%(name)s[image_Image, args__]" - images, arg = self.convert_args(image, *args.get_sequence()) - if images is None: - return evaluation.message(self.get_name(), "bddarg", arg) - ufunc = getattr(numpy, self.get_name(True)[5:].lower()) - result = self._reduce(images, ufunc).clip(0, 1) - return Image(result, image.color_space) - - -class ImageAdd(_ImageArithmetic): - """ - <url>:WMA link: - https://reference.wolfram.com/language/ref/ImageAdd.html</url> - - <dl> - <dt>'ImageAdd[$image$, $expr_1$, $expr_2$, ...]' - <dd>adds all $expr_i$ to $image$ where each $expr_i$ must be an image \ - or a real number. - </dl> - - >> i = Image[{{0, 0.5, 0.2, 0.1, 0.9}, {1.0, 0.1, 0.3, 0.8, 0.6}}]; - - >> ImageAdd[i, 0.5] - = -Image- - - >> ImageAdd[i, i] - = -Image- - - #> ImageAdd[i, 0.2, i, 0.1] - = -Image- - - #> ImageAdd[i, x] - : Expecting a number, image, or graphics instead of x. - = ImageAdd[-Image-, x] - - >> ein = Import["ExampleData/Einstein.jpg"]; - >> noise = RandomImage[{-0.1, 0.1}, ImageDimensions[ein]]; - >> ImageAdd[noise, ein] - = -Image- - - >> lena = Import["ExampleData/lena.tif"]; - >> noise = RandomImage[{-0.2, 0.2}, ImageDimensions[lena], ColorSpace -> "RGB"]; - >> ImageAdd[noise, lena] - = -Image- - """ - - summary_text = "build an image adding pixel values of another image " - - -class ImageMultiply(_ImageArithmetic): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageMultiply.html</url> - - <dl> - <dt>'ImageMultiply[$image$, $expr_1$, $expr_2$, ...]' - <dd>multiplies all $expr_i$ with $image$ where each $expr_i$ must be an image or a real number. - </dl> - - >> i = Image[{{0, 0.5, 0.2, 0.1, 0.9}, {1.0, 0.1, 0.3, 0.8, 0.6}}]; - - >> ImageMultiply[i, 0.2] - = -Image- - - >> ImageMultiply[i, i] - = -Image- - - #> ImageMultiply[i, 0.2, i, 0.1] - = -Image- - - #> ImageMultiply[i, x] - : Expecting a number, image, or graphics instead of x. - = ImageMultiply[-Image-, x] - - S> ein = Import["ExampleData/Einstein.jpg"]; - S> noise = RandomImage[{0.7, 1.3}, ImageDimensions[ein]]; - S> ImageMultiply[noise, ein] - = -Image- - """ - - summary_text = "build an image multiplying the pixel values of another image " - - -class ImageSubtract(_ImageArithmetic): - """ - <url>:WMA link: - https://reference.wolfram.com/language/ref/ImageSubtract.html</url> - - <dl> - <dt>'ImageSubtract[$image$, $expr_1$, $expr_2$, ...]' - <dd>subtracts all $expr_i$ from $image$ where each $expr_i$ must be an \ - image or a real number. - </dl> - - >> i = Image[{{0, 0.5, 0.2, 0.1, 0.9}, {1.0, 0.1, 0.3, 0.8, 0.6}}]; - - >> ImageSubtract[i, 0.2] - = -Image- - - >> ImageSubtract[i, i] - = -Image- - - #> ImageSubtract[i, 0.2, i, 0.1] - = -Image- - - #> ImageSubtract[i, x] - : Expecting a number, image, or graphics instead of x. - = ImageSubtract[-Image-, x] - """ - - summary_text = "build an image substracting pixel values of another image " - - class RandomImage(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/RandomImage.html</url> diff --git a/mathics/builtin/image/composition.py b/mathics/builtin/image/composition.py new file mode 100644 index 000000000..2c360a8a7 --- /dev/null +++ b/mathics/builtin/image/composition.py @@ -0,0 +1,158 @@ +""" +Image Compositions +""" +import numpy + +from mathics.builtin.base import Builtin +from mathics.builtin.image.base import Image +from mathics.core.atoms import Integer, Rational, Real +from mathics.core.evaluation import Evaluation +from mathics.eval.image import pixels_as_float + + +class _ImageArithmetic(Builtin): + messages = {"bddarg": "Expecting a number, image, or graphics instead of `1`."} + + @staticmethod + def convert_Image(image): + assert isinstance(image, Image) + return pixels_as_float(image.pixels) + + @staticmethod + def convert_args(*args): + images = [] + for arg in args: + if isinstance(arg, Image): + images.append(_ImageArithmetic.convert_Image(arg)) + elif isinstance(arg, (Integer, Rational, Real)): + images.append(float(arg.to_python())) + else: + return None, arg + return images, None + + @staticmethod + def _reduce(iterable, ufunc): + result = None + for i in iterable: + if result is None: + # ufunc is destructive so copy first + result = numpy.copy(i) + else: + # e.g. result *= i + ufunc(result, i, result) + return result + + def eval(self, image, args, evaluation: Evaluation): + "%(name)s[image_Image, args__]" + images, arg = self.convert_args(image, *args.get_sequence()) + if images is None: + return evaluation.message(self.get_name(), "bddarg", arg) + ufunc = getattr(numpy, self.get_name(True)[5:].lower()) + result = self._reduce(images, ufunc).clip(0, 1) + return Image(result, image.color_space) + + +class ImageAdd(_ImageArithmetic): + """ + <url>:WMA link: + https://reference.wolfram.com/language/ref/ImageAdd.html</url> + + <dl> + <dt>'ImageAdd[$image$, $expr_1$, $expr_2$, ...]' + <dd>adds all $expr_i$ to $image$ where each $expr_i$ must be an image \ + or a real number. + </dl> + + >> i = Image[{{0, 0.5, 0.2, 0.1, 0.9}, {1.0, 0.1, 0.3, 0.8, 0.6}}]; + + >> ImageAdd[i, 0.5] + = -Image- + + >> ImageAdd[i, i] + = -Image- + + #> ImageAdd[i, 0.2, i, 0.1] + = -Image- + + #> ImageAdd[i, x] + : Expecting a number, image, or graphics instead of x. + = ImageAdd[-Image-, x] + + >> ein = Import["ExampleData/Einstein.jpg"]; + >> noise = RandomImage[{-0.1, 0.1}, ImageDimensions[ein]]; + >> ImageAdd[noise, ein] + = -Image- + + >> lena = Import["ExampleData/lena.tif"]; + >> noise = RandomImage[{-0.2, 0.2}, ImageDimensions[lena], ColorSpace -> "RGB"]; + >> ImageAdd[noise, lena] + = -Image- + """ + + summary_text = "build an image adding pixel values of another image " + + +class ImageMultiply(_ImageArithmetic): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ImageMultiply.html</url> + + <dl> + <dt>'ImageMultiply[$image$, $expr_1$, $expr_2$, ...]' + <dd>multiplies all $expr_i$ with $image$ where each $expr_i$ must be an image or a real number. + </dl> + + >> i = Image[{{0, 0.5, 0.2, 0.1, 0.9}, {1.0, 0.1, 0.3, 0.8, 0.6}}]; + + >> ImageMultiply[i, 0.2] + = -Image- + + >> ImageMultiply[i, i] + = -Image- + + #> ImageMultiply[i, 0.2, i, 0.1] + = -Image- + + #> ImageMultiply[i, x] + : Expecting a number, image, or graphics instead of x. + = ImageMultiply[-Image-, x] + + S> ein = Import["ExampleData/Einstein.jpg"]; + S> noise = RandomImage[{0.7, 1.3}, ImageDimensions[ein]]; + S> ImageMultiply[noise, ein] + = -Image- + """ + + summary_text = "build an image multiplying the pixel values of another image " + + +class ImageSubtract(_ImageArithmetic): + """ + <url>:WMA link: + https://reference.wolfram.com/language/ref/ImageSubtract.html</url> + + <dl> + <dt>'ImageSubtract[$image$, $expr_1$, $expr_2$, ...]' + <dd>subtracts all $expr_i$ from $image$ where each $expr_i$ must be an \ + image or a real number. + </dl> + + >> i = Image[{{0, 0.5, 0.2, 0.1, 0.9}, {1.0, 0.1, 0.3, 0.8, 0.6}}]; + + >> ImageSubtract[i, 0.2] + = -Image- + + >> ImageSubtract[i, i] + = -Image- + + #> ImageSubtract[i, 0.2, i, 0.1] + = -Image- + + #> ImageSubtract[i, x] + : Expecting a number, image, or graphics instead of x. + = ImageSubtract[-Image-, x] + """ + + summary_text = "build an image substracting pixel values of another image " + + +# TODO: ImageAssemble, ImageCollage, ImageCompose From ed9faf17aa749e18c3123a65802f5734b831752a Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Thu, 29 Dec 2022 19:49:19 -0500 Subject: [PATCH 115/121] Split out Image Filters --- mathics/builtin/drawing/image-misc.py | 218 +----------------- mathics/builtin/image/basic.py | 74 +++++- mathics/builtin/image/filters.py | 184 +++++++++++++++ .../test_summary_text.py | 1 - 4 files changed, 256 insertions(+), 221 deletions(-) create mode 100644 mathics/builtin/image/filters.py diff --git a/mathics/builtin/drawing/image-misc.py b/mathics/builtin/drawing/image-misc.py index 3add35c63..3a2acfa8a 100644 --- a/mathics/builtin/drawing/image-misc.py +++ b/mathics/builtin/drawing/image-misc.py @@ -23,7 +23,7 @@ from mathics.builtin.base import Builtin, String from mathics.builtin.colors.color_internals import colorspaces as known_colorspaces from mathics.builtin.image.base import Image, _SkimageBuiltin -from mathics.core.atoms import Integer, Integer0, Integer1, MachineReal +from mathics.core.atoms import Integer, Integer0, Integer1 from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_python from mathics.core.evaluation import Evaluation @@ -32,7 +32,6 @@ from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull, SymbolTrue from mathics.core.systemsymbols import SymbolRule from mathics.eval.image import ( - convolve, extract_exif, matrix_to_numpy, numpy_to_matrix, @@ -47,13 +46,6 @@ _skimage_requires = ("skimage", "scipy", "matplotlib", "networkx") -try: - import skimage.filters -except ImportError: - have_skimage_filters = False -else: - have_skimage_filters = True - # The following classes are used to allow inclusion of # Builtin Functions only when certain Python packages # are available. They do this by setting the `requires` class variable. @@ -196,124 +188,6 @@ def eval(self, minval, maxval, w, h, evaluation, options): return Image(data, cs) -class GaussianFilter(Builtin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/GaussianFilter.html</url> - - <dl> - <dt>'GaussianFilter[$image$, $r$]' - <dd>blurs $image$ using a Gaussian blur filter of radius $r$. - </dl> - - >> lena = Import["ExampleData/lena.tif"]; - >> GaussianFilter[lena, 2.5] - = -Image- - """ - - summary_text = "apply a gaussian filter to an image" - messages = {"only3": "GaussianFilter only supports up to three channels."} - - def eval_radius(self, image, radius, evaluation: Evaluation): - "GaussianFilter[image_Image, radius_?RealNumberQ]" - if len(image.pixels.shape) > 2 and image.pixels.shape[2] > 3: - return evaluation.message("GaussianFilter", "only3") - else: - f = PIL.ImageFilter.GaussianBlur(radius.round_to_float()) - return image.filter(lambda im: im.filter(f)) - - -# morphological image filters - - -class PillowImageFilter(Builtin): - """ - - ## <url>:PillowImageFilter:</url> - - <dl> - <dt>'PillowImageFilter[$image$, "filtername"]' - <dd> applies an image filter "filtername" from the pillow library. - </dl> - TODO: test cases? - """ - - summary_text = "apply a pillow filter to an image" - - def compute(self, image, f): - return image.filter(lambda im: im.filter(f)) - - -class MinFilter(PillowImageFilter): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/MinFilter.html</url> - - <dl> - <dt>'MinFilter[$image$, $r$]' - <dd>gives $image$ with a minimum filter of radius $r$ applied on it. This always - picks the smallest value in the filter's area. - </dl> - - >> lena = Import["ExampleData/lena.tif"]; - >> MinFilter[lena, 5] - = -Image- - """ - - summary_text = "replace every pixel value by the minimum in a neighbourhood" - - def eval(self, image, r: Integer, evaluation: Evaluation): - "MinFilter[image_Image, r_Integer]" - return self.compute(image, PIL.ImageFilter.MinFilter(1 + 2 * r.value)) - - -class MaxFilter(PillowImageFilter): - """ - - <url> - :WMA link: - https://reference.wolfram.com/language/ref/MaxFilter.html</url> - - <dl> - <dt>'MaxFilter[$image$, $r$]' - <dd>gives $image$ with a maximum filter of radius $r$ applied on it. This always \ - picks the largest value in the filter's area. - </dl> - - >> lena = Import["ExampleData/lena.tif"]; - >> MaxFilter[lena, 5] - = -Image- - """ - - summary_text = "replace every pixel value by the maximum in a neighbourhood" - - def eval(self, image, r: Integer, evaluation: Evaluation): - "MaxFilter[image_Image, r_Integer]" - return self.compute(image, PIL.ImageFilter.MaxFilter(1 + 2 * r.value)) - - -class MedianFilter(PillowImageFilter): - """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/MedianFilter.html</url> - - <dl> - <dt>'MedianFilter[$image$, $r$]' - <dd>gives $image$ with a median filter of radius $r$ applied on it. This always \ - picks the median value in the filter's area. - </dl> - - >> lena = Import["ExampleData/lena.tif"]; - >> MedianFilter[lena, 5] - = -Image- - """ - - summary_text = "replace every pixel value by the median in a neighbourhood" - - def eval(self, image, r: Integer, evaluation: Evaluation): - "MedianFilter[image_Image, r_Integer]" - return self.compute(image, PIL.ImageFilter.MedianFilter(1 + 2 * r.value)) - - class EdgeDetect(_SkimageBuiltin): """ @@ -450,37 +324,6 @@ def rows(): return _matrix(rows()) -class ImageConvolve(Builtin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ImageConvolve.html</url> - - <dl> - <dt>'ImageConvolve[$image$, $kernel$]' - <dd>Computes the convolution of $image$ using $kernel$. - </dl> - - >> img = Import["ExampleData/lena.tif"]; - >> ImageConvolve[img, DiamondMatrix[5] / 61] - = -Image- - >> ImageConvolve[img, DiskMatrix[5] / 97] - = -Image- - >> ImageConvolve[img, BoxMatrix[5] / 121] - = -Image- - """ - - summary_text = "give the convolution of image with kernel" - - def eval(self, image, kernel, evaluation: Evaluation): - "%(name)s[image_Image, kernel_?MatrixQ]" - numpy_kernel = matrix_to_numpy(kernel) - pixels = pixels_as_float(image.pixels) - shape = pixels.shape[:2] - channels = [] - for c in (pixels[:, :, i] for i in range(pixels.shape[2])): - channels.append(convolve(c.reshape(shape), numpy_kernel, fixed=True)) - return Image(numpy.dstack(channels), image.color_space) - - class _MorphologyFilter(_SkimageBuiltin): messages = { @@ -669,65 +512,6 @@ def eval(self, image, n: Integer, evaluation: Evaluation): return Image(numpy.array(im), "RGB") -class Threshold(Builtin): - """ - - <url>:WMA link:https://reference.wolfram.com/language/ref/Threshold.html</url> - - <dl> - <dt>'Threshold[$image$]' - <dd>gives a value suitable for binarizing $image$. - </dl> - - The option "Method" may be "Cluster" (use Otsu's threshold), "Median", or "Mean". - - >> img = Import["ExampleData/lena.tif"]; - >> Threshold[img] - = 0.456739 - X> Binarize[img, %] - = -Image- - X> Threshold[img, Method -> "Mean"] - = 0.486458 - X> Threshold[img, Method -> "Median"] - = 0.504726 - """ - - summary_text = "estimate a threshold value for binarize an image" - if have_skimage_filters: - options = {"Method": '"Cluster"'} - else: - options = {"Method": '"Median"'} - - messages = { - "illegalmethod": "Method `` is not supported.", - "skimage": "Please install scikit-image to use Method -> Cluster.", - } - - def eval(self, image, evaluation: Evaluation, options): - "Threshold[image_Image, OptionsPattern[Threshold]]" - pixels = image.grayscale().pixels - - method = self.get_option(options, "Method", evaluation) - method_name = ( - method.get_string_value() - if isinstance(method, String) - else method.to_python() - ) - if method_name == "Cluster": - if not have_skimage_filters: - evaluation.message("ImageResize", "skimage") - return - threshold = skimage.filters.threshold_otsu(pixels) - elif method_name == "Median": - threshold = numpy.median(pixels) - elif method_name == "Mean": - threshold = numpy.mean(pixels) - else: - return evaluation.message("Threshold", "illegalmethod", method) - - return MachineReal(float(threshold)) - - class Binarize(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/Binarize.html</url> diff --git a/mathics/builtin/image/basic.py b/mathics/builtin/image/basic.py index c1c0e89cf..1549c5c33 100644 --- a/mathics/builtin/image/basic.py +++ b/mathics/builtin/image/basic.py @@ -5,14 +5,21 @@ import numpy import PIL -from mathics.builtin.base import Builtin +from mathics.builtin.base import Builtin, String from mathics.builtin.image.base import Image -from mathics.core.atoms import Integer +from mathics.core.atoms import Integer, MachineReal from mathics.core.convert.python import from_python from mathics.core.evaluation import Evaluation from mathics.core.list import ListExpression from mathics.eval.image import pixels_as_float +try: + import skimage.filters +except ImportError: + have_skimage_filters = False +else: + have_skimage_filters = True + class Blur(Builtin): """ @@ -201,4 +208,65 @@ def eval(self, image, r, evaluation: Evaluation): return image.filter(lambda im: im.filter(f)) -# Todo Darker, ImageClip, ImageEffect, ImageRestyle, Lighter, Threshold +class Threshold(Builtin): + """ + + <url> + :WMA link: + https://reference.wolfram.com/language/ref/Threshold.html</url> + + <dl> + <dt>'Threshold[$image$]' + <dd>gives a value suitable for binarizing $image$. + </dl> + + The option "Method" may be "Cluster" (use Otsu's threshold), "Median", or "Mean". + + >> img = Import["ExampleData/lena.tif"]; + >> Threshold[img] + = 0.456739 + X> Binarize[img, %] + = -Image- + X> Threshold[img, Method -> "Mean"] + = 0.486458 + X> Threshold[img, Method -> "Median"] + = 0.504726 + """ + + summary_text = "estimate a threshold value for binarize an image" + if have_skimage_filters: + options = {"Method": '"Cluster"'} + else: + options = {"Method": '"Median"'} + + messages = { + "illegalmethod": "Method `` is not supported.", + "skimage": "Please install scikit-image to use Method -> Cluster.", + } + + def eval(self, image, evaluation: Evaluation, options): + "Threshold[image_Image, OptionsPattern[Threshold]]" + pixels = image.grayscale().pixels + + method = self.get_option(options, "Method", evaluation) + method_name = ( + method.get_string_value() + if isinstance(method, String) + else method.to_python() + ) + if method_name == "Cluster": + if not have_skimage_filters: + evaluation.message("ImageResize", "skimage") + return + threshold = skimage.filters.threshold_otsu(pixels) + elif method_name == "Median": + threshold = numpy.median(pixels) + elif method_name == "Mean": + threshold = numpy.mean(pixels) + else: + return evaluation.message("Threshold", "illegalmethod", method) + + return MachineReal(float(threshold)) + + +# Todo Darker, ImageClip, ImageEffect, ImageRestyle, Lighter diff --git a/mathics/builtin/image/filters.py b/mathics/builtin/image/filters.py new file mode 100644 index 000000000..930a90d6c --- /dev/null +++ b/mathics/builtin/image/filters.py @@ -0,0 +1,184 @@ +""" +Image Filters +""" + +import numpy +import PIL + +from mathics.builtin.base import Builtin +from mathics.builtin.image.base import Image +from mathics.core.atoms import Integer +from mathics.core.evaluation import Evaluation +from mathics.eval.image import convolve, matrix_to_numpy, pixels_as_float + + +class _PillowImageFilter(Builtin): + """ + Base class for various Image filters. + """ + + def compute(self, image, f): + return image.filter(lambda im: im.filter(f)) + + +class GaussianFilter(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/GaussianFilter.html</url> + + <dl> + <dt>'GaussianFilter[$image$, $r$]' + <dd>blurs $image$ using a Gaussian blur filter of radius $r$. + </dl> + + >> lena = Import["ExampleData/lena.tif"]; + >> GaussianFilter[lena, 2.5] + = -Image- + """ + + summary_text = "apply a gaussian filter to an image" + messages = {"only3": "GaussianFilter only supports up to three channels."} + + def eval_radius(self, image, radius, evaluation: Evaluation): + "GaussianFilter[image_Image, radius_?RealNumberQ]" + if len(image.pixels.shape) > 2 and image.pixels.shape[2] > 3: + return evaluation.message("GaussianFilter", "only3") + else: + f = PIL.ImageFilter.GaussianBlur(radius.round_to_float()) + return image.filter(lambda im: im.filter(f)) + + +class ImageConvolve(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ImageConvolve.html</url> + + <dl> + <dt>'ImageConvolve[$image$, $kernel$]' + <dd>Computes the convolution of $image$ using $kernel$. + </dl> + + >> img = Import["ExampleData/lena.tif"]; + >> ImageConvolve[img, DiamondMatrix[5] / 61] + = -Image- + >> ImageConvolve[img, DiskMatrix[5] / 97] + = -Image- + >> ImageConvolve[img, BoxMatrix[5] / 121] + = -Image- + """ + + summary_text = "give the convolution of image with kernel" + + def eval(self, image, kernel, evaluation: Evaluation): + "ImageConvolve[image_Image, kernel_?MatrixQ]" + numpy_kernel = matrix_to_numpy(kernel) + pixels = pixels_as_float(image.pixels) + shape = pixels.shape[:2] + channels = [] + for c in (pixels[:, :, i] for i in range(pixels.shape[2])): + channels.append(convolve(c.reshape(shape), numpy_kernel, fixed=True)) + return Image(numpy.dstack(channels), image.color_space) + + +class MaxFilter(_PillowImageFilter): + """ + + <url> + :WMA link: + https://reference.wolfram.com/language/ref/MaxFilter.html</url> + + <dl> + <dt>'MaxFilter[$image$, $r$]' + <dd>gives $image$ with a maximum filter of radius $r$ applied on it. This always \ + picks the largest value in the filter's area. + </dl> + + >> lena = Import["ExampleData/lena.tif"]; + >> MaxFilter[lena, 5] + = -Image- + """ + + summary_text = "replace every pixel value by the maximum in a neighborhood" + + def eval(self, image, r: Integer, evaluation: Evaluation): + "MaxFilter[image_Image, r_Integer]" + return self.compute(image, PIL.ImageFilter.MaxFilter(1 + 2 * r.value)) + + +class MedianFilter(_PillowImageFilter): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/MedianFilter.html</url> + + <dl> + <dt>'MedianFilter[$image$, $r$]' + <dd>gives $image$ with a median filter of radius $r$ applied on it. This always \ + picks the median value in the filter's area. + </dl> + + >> lena = Import["ExampleData/lena.tif"]; + >> MedianFilter[lena, 5] + = -Image- + """ + + summary_text = "replace every pixel value by the median in a neighborhood" + + def eval(self, image, r: Integer, evaluation: Evaluation): + "MedianFilter[image_Image, r_Integer]" + return self.compute(image, PIL.ImageFilter.MedianFilter(1 + 2 * r.value)) + + +class MinFilter(_PillowImageFilter): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/MinFilter.html</url> + + <dl> + <dt>'MinFilter[$image$, $r$]' + <dd>gives $image$ with a minimum filter of radius $r$ applied on it. This always \ + picks the smallest value in the filter's area. + </dl> + + >> lena = Import["ExampleData/lena.tif"]; + >> MinFilter[lena, 5] + = -Image- + """ + + summary_text = "replace every pixel value by the minimum in a neighborhood" + + def eval(self, image, r: Integer, evaluation: Evaluation): + "MinFilter[image_Image, r_Integer]" + return self.compute(image, PIL.ImageFilter.MinFilter(1 + 2 * r.value)) + + +# TODO: + +# BilateralFilter +# CommonestFilter +# CurvatureFlowFilter +# DerivativeFilter +# EntropyFilter +# GaborFilter, +# GeometricMeanFilter +# GradientFilter, +# GradientOrintationFilter, +# HarmonicMeanFilter +# ImageCorrelate, +# KuwaharaFilter +# LaplacianFilter, +# LaplacianGaussianFilter +# MeanFilter, +# MeanShiftFilter +# PeronMalikFilter +# RangeFilter +# RidgeFilter, +# StandardDevisationFilter +# WienerFilter, + +# ... and verything in: + +# Nonlocal Filters, Frequence-BasedFilters, Region-of-Interest Processing, General Neighborhood Processing diff --git a/test/consistency-and-style/test_summary_text.py b/test/consistency-and-style/test_summary_text.py index cea174582..8c0429233 100644 --- a/test/consistency-and-style/test_summary_text.py +++ b/test/consistency-and-style/test_summary_text.py @@ -10,7 +10,6 @@ from mathics.builtin import name_is_builtin_symbol from mathics.builtin.base import Builtin from mathics.doc.common_doc import skip_doc -from mathics.version import __version__ # noqa used in loading to check consistency. # Get file system path name for mathics.builtin mathics_path = osp.dirname(mathics_initfile_path) From e33a623f851ef8fe18dbc7711c85a38da2a8a9a7 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Thu, 29 Dec 2022 21:14:27 -0500 Subject: [PATCH 116/121] Split out Image Constructing builtins... Move remaining under Images Misc to be split out even more, later. --- mathics/builtin/image/__init__.py | 3 + .../{drawing/image-misc.py => image/misc.py} | 230 +----------------- mathics/builtin/matrices/constrmatrix.py | 104 +++++++- 3 files changed, 111 insertions(+), 226 deletions(-) rename mathics/builtin/{drawing/image-misc.py => image/misc.py} (81%) diff --git a/mathics/builtin/image/__init__.py b/mathics/builtin/image/__init__.py index 18996934a..63ccbaae0 100644 --- a/mathics/builtin/image/__init__.py +++ b/mathics/builtin/image/__init__.py @@ -1,3 +1,6 @@ """ Image Manipulation + + +For the full compliment of functions, you need to have scikit-image installed. """ diff --git a/mathics/builtin/drawing/image-misc.py b/mathics/builtin/image/misc.py similarity index 81% rename from mathics/builtin/drawing/image-misc.py rename to mathics/builtin/image/misc.py index 3a2acfa8a..7ae4423fa 100644 --- a/mathics/builtin/drawing/image-misc.py +++ b/mathics/builtin/image/misc.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- -# FIXME - move the rest into builtin.image +# FIXME - plit out rest: +# Color Manipulation, etc. """ -Image[] and image-related functions - -Note that you (currently) need scikit-image installed in order for this \ -module to work. +Miscellaneous image-related functions """ # This tells documentation how to sort this module @@ -13,7 +11,6 @@ sort_order = "mathics.builtin.image-and-image-related-functions" import functools -import math import os.path as osp from collections import defaultdict @@ -23,7 +20,7 @@ from mathics.builtin.base import Builtin, String from mathics.builtin.colors.color_internals import colorspaces as known_colorspaces from mathics.builtin.image.base import Image, _SkimageBuiltin -from mathics.core.atoms import Integer, Integer0, Integer1 +from mathics.core.atoms import Integer from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_python from mathics.core.evaluation import Evaluation @@ -191,7 +188,9 @@ def eval(self, minval, maxval, w, h, evaluation, options): class EdgeDetect(_SkimageBuiltin): """ - <url>:WMA link:https://reference.wolfram.com/language/ref/EdgeDetect.html</url> + <url> + :WMA link: + https://reference.wolfram.com/language/ref/EdgeDetect.html</url> <dl> <dt>'EdgeDetect[$image$]' @@ -229,221 +228,6 @@ def eval(self, image, r, t, evaluation: Evaluation): ) -def _matrix(rows): - return ListExpression(*[ListExpression(*r) for r in rows]) - - -class BoxMatrix(Builtin): - """ - - <url>:WMA link:https://reference.wolfram.com/language/ref/BoxMatrix.html</url> - - <dl> - <dt>'BoxMatrix[$s]' - <dd>Gives a box shaped kernel of size 2 $s$ + 1. - </dl> - - >> BoxMatrix[3] - = {{1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}} - """ - - summary_text = "create a matrix with all its entries set to 1" - - def eval(self, r, evaluation: Evaluation): - "BoxMatrix[r_?RealNumberQ]" - py_r = abs(r.round_to_float()) - s = int(math.floor(1 + 2 * py_r)) - return _matrix([[Integer1] * s] * s) - - -class DiskMatrix(Builtin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/DiskMatrix.html</url> - - <dl> - <dt>'DiskMatrix[$s]' - <dd>Gives a disk shaped kernel of size 2 $s$ + 1. - </dl> - - >> DiskMatrix[3] - = {{0, 0, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 1, 1, 0}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {0, 1, 1, 1, 1, 1, 0}, {0, 0, 1, 1, 1, 0, 0}} - """ - - summary_text = "create a matrix with 1 in a disk-shaped region, and 0 outside" - - def eval(self, r, evaluation: Evaluation): - "DiskMatrix[r_?RealNumberQ]" - py_r = abs(r.round_to_float()) - s = int(math.floor(0.5 + py_r)) - - m = (Integer0, Integer1) - r_sqr = (py_r + 0.5) * (py_r + 0.5) - - def rows(): - for y in range(-s, s + 1): - yield [m[int((x) * (x) + (y) * (y) <= r_sqr)] for x in range(-s, s + 1)] - - return _matrix(rows()) - - -class DiamondMatrix(Builtin): - """ - - <url>:WMA link:https://reference.wolfram.com/language/ref/DiamondMatrix.html</url> - - <dl> - <dt>'DiamondMatrix[$s]' - <dd>Gives a diamond shaped kernel of size 2 $s$ + 1. - </dl> - - >> DiamondMatrix[3] - = {{0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 1, 1, 0}, {1, 1, 1, 1, 1, 1, 1}, {0, 1, 1, 1, 1, 1, 0}, {0, 0, 1, 1, 1, 0, 0}, {0, 0, 0, 1, 0, 0, 0}} - """ - - summary_text = "create a matrix with 1 in a diamond-shaped region, and 0 outside" - - def eval(self, r, evaluation: Evaluation): - "DiamondMatrix[r_?RealNumberQ]" - py_r = abs(r.round_to_float()) - t = int(math.floor(0.5 + py_r)) - - zero = Integer0 - one = Integer1 - - def rows(): - for d in range(0, t): - p = [zero] * (t - d) - yield p + ([one] * (1 + d * 2)) + p - - yield [one] * (2 * t + 1) - - for d in reversed(range(0, t)): - p = [zero] * (t - d) - yield p + ([one] * (1 + d * 2)) + p - - return _matrix(rows()) - - -class _MorphologyFilter(_SkimageBuiltin): - - messages = { - "grayscale": "Your image has been converted to grayscale as color images are not supported yet." - } - - rules = {"%(name)s[i_Image, r_?RealNumberQ]": "%(name)s[i, BoxMatrix[r]]"} - - def eval(self, image, k, evaluation: Evaluation): - "%(name)s[image_Image, k_?MatrixQ]" - if image.color_space != "Grayscale": - image = image.grayscale() - evaluation.message(self.get_name(), "grayscale") - import skimage.morphology - - f = getattr(skimage.morphology, self.get_name(True).lower()) - shape = image.pixels.shape[:2] - img = f(image.pixels.reshape(shape), matrix_to_numpy(k)) - return Image(img, "Grayscale") - - -class Dilation(_MorphologyFilter): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Dilation.html</url> - - <dl> - <dt>'Dilation[$image$, $ker$]' - <dd>Gives the morphological dilation of $image$ with respect to structuring element $ker$. - </dl> - - >> ein = Import["ExampleData/Einstein.jpg"]; - >> Dilation[ein, 2.5] - = -Image- - """ - - summary_text = "give the dilation with respect to a range-r square" - - -class Erosion(_MorphologyFilter): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Erosion.html</url> - - <dl> - <dt>'Erosion[$image$, $ker$]' - <dd>Gives the morphological erosion of $image$ with respect to structuring element $ker$. - </dl> - - >> ein = Import["ExampleData/Einstein.jpg"]; - >> Erosion[ein, 2.5] - = -Image- - """ - - summary_text = "give the erotion with respect to a range-r square" - - -class Opening(_MorphologyFilter): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Opening.html</url> - - <dl> - <dt>'Opening[$image$, $ker$]' - <dd>Gives the morphological opening of $image$ with respect to structuring element $ker$. - </dl> - - >> ein = Import["ExampleData/Einstein.jpg"]; - >> Opening[ein, 2.5] - = -Image- - """ - - summary_text = "get morphological opening regarding a kernel" - - -class Closing(_MorphologyFilter): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Closing.html</url> - - <dl> - <dt>'Closing[$image$, $ker$]' - <dd>Gives the morphological closing of $image$ with respect to structuring element $ker$. - </dl> - - >> ein = Import["ExampleData/Einstein.jpg"]; - >> Closing[ein, 2.5] - = -Image- - """ - - summary_text = "morphological closing regarding a kernel" - - -class MorphologicalComponents(_SkimageBuiltin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/MorphologicalComponents.html</url> - - <dl> - <dt>'MorphologicalComponents[$image$]' - <dd> Builds a 2-D array in which each pixel of $image$ is replaced \ - by an integer index representing the connected foreground image \ - component in which the pixel lies. - - <dt>'MorphologicalComponents[$image$, $threshold$]' - <dd> consider any pixel with a value above $threshold$ as the foreground. - </dl> - """ - - summary_text = "tag connected regions of similar colors" - - rules = {"MorphologicalComponents[i_Image]": "MorphologicalComponents[i, 0]"} - - def eval(self, image, t, evaluation: Evaluation): - "MorphologicalComponents[image_Image, t_?RealNumberQ]" - pixels = pixels_as_ubyte( - pixels_as_float(image.grayscale().pixels) > t.round_to_float() - ) - import skimage.measure - - return from_python( - skimage.measure.label(pixels, background=0, connectivity=2).tolist() - ) - - # color space diff --git a/mathics/builtin/matrices/constrmatrix.py b/mathics/builtin/matrices/constrmatrix.py index e0ac29b12..8e41961e5 100644 --- a/mathics/builtin/matrices/constrmatrix.py +++ b/mathics/builtin/matrices/constrmatrix.py @@ -4,13 +4,43 @@ Methods for constructing Matrices. """ - +import math from mathics.builtin.base import Builtin -from mathics.core.atoms import Integer0 +from mathics.core.atoms import Integer0, Integer1 +from mathics.core.evaluation import Evaluation from mathics.core.list import ListExpression +def _matrix(rows): + return ListExpression(*[ListExpression(*r) for r in rows]) + + +class BoxMatrix(Builtin): + """ + + <url> + :WMA link: + https://reference.wolfram.com/language/ref/BoxMatrix.html</url> + + <dl> + <dt>'BoxMatrix[$s]' + <dd>Gives a box shaped kernel of size 2 $s$ + 1. + </dl> + + >> BoxMatrix[3] + = {{1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}} + """ + + summary_text = "create a matrix with all its entries set to 1" + + def eval(self, r, evaluation: Evaluation): + "BoxMatrix[r_?RealNumberQ]" + py_r = abs(r.round_to_float()) + s = int(math.floor(1 + 2 * py_r)) + return _matrix([[Integer1] * s] * s) + + class DiagonalMatrix(Builtin): """ <url>:WMA link: @@ -34,7 +64,7 @@ class DiagonalMatrix(Builtin): summary_text = "give a diagonal matrix with the elements of a given list" - def apply(self, list, evaluation): + def eval(self, list, evaluation): "DiagonalMatrix[list_List]" result = [] @@ -46,6 +76,74 @@ def apply(self, list, evaluation): return ListExpression(*result) +class DiamondMatrix(Builtin): + """ + + <url>:WMA link:https://reference.wolfram.com/language/ref/DiamondMatrix.html</url> + + <dl> + <dt>'DiamondMatrix[$s]' + <dd>Gives a diamond shaped kernel of size 2 $s$ + 1. + </dl> + + >> DiamondMatrix[3] + = {{0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 1, 1, 0}, {1, 1, 1, 1, 1, 1, 1}, {0, 1, 1, 1, 1, 1, 0}, {0, 0, 1, 1, 1, 0, 0}, {0, 0, 0, 1, 0, 0, 0}} + """ + + summary_text = "create a matrix with 1 in a diamond-shaped region, and 0 outside" + + def eval(self, r, evaluation: Evaluation): + "DiamondMatrix[r_?RealNumberQ]" + py_r = abs(r.round_to_float()) + t = int(math.floor(0.5 + py_r)) + + zero = Integer0 + one = Integer1 + + def rows(): + for d in range(0, t): + p = [zero] * (t - d) + yield p + ([one] * (1 + d * 2)) + p + + yield [one] * (2 * t + 1) + + for d in reversed(range(0, t)): + p = [zero] * (t - d) + yield p + ([one] * (1 + d * 2)) + p + + return _matrix(rows()) + + +class DiskMatrix(Builtin): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/DiskMatrix.html</url> + + <dl> + <dt>'DiskMatrix[$s]' + <dd>Gives a disk shaped kernel of size 2 $s$ + 1. + </dl> + + >> DiskMatrix[3] + = {{0, 0, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 1, 1, 0}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1}, {0, 1, 1, 1, 1, 1, 0}, {0, 0, 1, 1, 1, 0, 0}} + """ + + summary_text = "create a matrix with 1 in a disk-shaped region, and 0 outside" + + def eval(self, r, evaluation: Evaluation): + "DiskMatrix[r_?RealNumberQ]" + py_r = abs(r.round_to_float()) + s = int(math.floor(0.5 + py_r)) + + m = (Integer0, Integer1) + r_sqr = (py_r + 0.5) * (py_r + 0.5) + + def rows(): + for y in range(-s, s + 1): + yield [m[int((x) * (x) + (y) * (y) <= r_sqr)] for x in range(-s, s + 1)] + + return _matrix(rows()) + + class IdentityMatrix(Builtin): """ <url>:WMA link: From 7fb717bc6e78f5a58bab01e7e121ec8802437d34 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Fri, 30 Dec 2022 05:43:43 -0500 Subject: [PATCH 117/121] Split out Image Color built-ins --- mathics/builtin/drawing/__init__.py | 20 +- mathics/builtin/image/colors.py | 310 ++++++++++++++++++++++++++++ mathics/builtin/image/misc.py | 282 +------------------------ mathics/core/systemsymbols.py | 2 + 4 files changed, 327 insertions(+), 287 deletions(-) create mode 100644 mathics/builtin/image/colors.py diff --git a/mathics/builtin/drawing/__init__.py b/mathics/builtin/drawing/__init__.py index dec864979..a4f640974 100644 --- a/mathics/builtin/drawing/__init__.py +++ b/mathics/builtin/drawing/__init__.py @@ -1,14 +1,22 @@ """ -Graphics, Drawing, and Images +Graphics and Drawing -Showing something visually can be don in a number of ways: +Showing something visually can be done in a number of ways: <ul> - <li>Starting with complete images and modifiying them. The 'Image' function is in this category. - <li>Use pre-defined 2D or 3D objects like <url>:'Circle': /doc/reference-of-built-in-symbols/drawing-graphics/circle</url> and <url>:'Cuboid': /doc/reference-of-built-in-symbols/graphics-drawing-and-images/three-dimensional-graphics/cuboid/</url> and place them in a coordiate space. - <li>Compute the points of the space using a function. This is done using functions like <url>:'Plot': /doc/reference-of-built-in-symbols/graphics-drawing-and-images/plotting-data/plot</url> and <url>:'ListPlot': /doc/reference-of-built-in-symbols/graphics-drawing-and-images/plotting-data/listplot</url>. + <li>Starting with complete images and modifiying them using the 'Image' Built-in function. + <li>Use pre-defined 2D or 3D objects like <url> + :'Circle': + /doc/reference-of-built-in-symbols/drawing-graphics/circle</url> and <url> + :'Cuboid': /doc/reference-of-built-in-symbols/graphics-drawing-and-images/three-dimensional-graphics/cuboid/</url> \ + and place them in a coordiate space. + <li>Compute the points of the space using a function. This is done using functions like <url> + :'Plot': + /doc/reference-of-built-in-symbols/graphics-drawing-and-images/plotting-data/plot</url> \ + and <url> + :'ListPlot': /doc/reference-of-built-in-symbols/graphics-drawing-and-images/plotting-data/listplot</url>. </ul> """ # This tells documentation how to sort this module -sort_order = "mathics.builtin.graphing-drawing-and-images" +sort_order = "mathics.builtin.graphing-and-drawing" diff --git a/mathics/builtin/image/colors.py b/mathics/builtin/image/colors.py new file mode 100644 index 000000000..eaf2e7b39 --- /dev/null +++ b/mathics/builtin/image/colors.py @@ -0,0 +1,310 @@ +""" +Image Colors +""" + +import functools +from typing import Tuple + +import numpy +import PIL + +from mathics.builtin.base import Builtin, String +from mathics.builtin.colors.color_internals import colorspaces as known_colorspaces +from mathics.builtin.image.base import Image +from mathics.core.atoms import Integer +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.symbols import Symbol, SymbolTrue +from mathics.core.systemsymbols import SymbolMatrixQ, SymbolThreshold +from mathics.eval.image import matrix_to_numpy, pixels_as_ubyte + +SymbolColorQuantize = Symbol("ColorQuantize") + + +def _linearize_numpy_array(a: numpy.array) -> Tuple[numpy.array, int]: + """ + Transforms a numpy array numpy array and return the array and the number + of dimensions in the array + + A binary search is used. + """ + + orig_shape = a.shape + a = a.reshape((functools.reduce(lambda x, y: x * y, a.shape),)) # 1 dimension + + u = numpy.unique(a) + n = len(u) + + lower = numpy.ndarray(a.shape, dtype=int) + lower.fill(0) + upper = numpy.ndarray(a.shape, dtype=int) + upper.fill(n - 1) + + h = numpy.sort(u) + q = n # worst case partition size + + while q > 2: + m = numpy.right_shift(lower + upper, 1) + f = a <= h[m] + # (lower, m) vs (m + 1, upper) + lower = numpy.where(f, lower, m + 1) + upper = numpy.where(f, m, upper) + q = (q + 1) // 2 + + return numpy.where(a == h[lower], lower, upper).reshape(orig_shape), n + + +class Binarize(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/Binarize.html</url> + + <dl> + <dt>'Binarize[$image$]' + <dd>gives a binarized version of $image$, in which each pixel is either 0 or 1. + + <dt>'Binarize[$image$, $t$]' + <dd>map values $x$ > $t$ to 1, and values $x$ <= $t$ to 0. + + <dt>'Binarize[$image$, {$t1$, $t2$}]' + <dd>map $t1$ < $x$ < $t2$ to 1, and all other values to 0. + </dl> + + S> img = Import["ExampleData/lena.tif"]; + S> Binarize[img] + = -Image- + S> Binarize[img, 0.7] + = -Image- + S> Binarize[img, {0.2, 0.6}] + = -Image- + """ + + summary_text = "create a binarized image" + + def eval(self, image, evaluation: Evaluation): + "Binarize[image_Image]" + image = image.grayscale() + thresh = ( + Expression(SymbolThreshold, image).evaluate(evaluation).round_to_float() + ) + if thresh is not None: + return Image(image.pixels > thresh, "Grayscale") + + def eval_t(self, image, t, evaluation: Evaluation): + "Binarize[image_Image, t_?RealNumberQ]" + pixels = image.grayscale().pixels + return Image(pixels > t.round_to_float(), "Grayscale") + + def eval_t1_t2(self, image, t1, t2, evaluation: Evaluation): + "Binarize[image_Image, {t1_?RealNumberQ, t2_?RealNumberQ}]" + pixels = image.grayscale().pixels + mask1 = pixels > t1.round_to_float() + mask2 = pixels < t2.round_to_float() + return Image(mask1 * mask2, "Grayscale") + + +class ColorCombine(Builtin): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/ColorCombine.html</url> + + <dl> + <dt>'ColorCombine[$channels$, $colorspace$]' + <dd>Gives an image with $colorspace$ and the respective components described by the given channels. + </dl> + + >> ColorCombine[{{{1, 0}, {0, 0.75}}, {{0, 1}, {0, 0.25}}, {{0, 0}, {1, 0.5}}}, "RGB"] + = -Image- + """ + + summary_text = "combine color channels" + + def eval(self, channels, colorspace, evaluation: Evaluation): + "ColorCombine[channels_List, colorspace_String]" + + py_colorspace = colorspace.get_string_value() + if py_colorspace not in known_colorspaces: + return + + numpy_channels = [] + for channel in channels.elements: + if ( + not Expression(SymbolMatrixQ, channel).evaluate(evaluation) + is SymbolTrue + ): + return + numpy_channels.append(matrix_to_numpy(channel)) + + if not numpy_channels: + return + + if not all(x.shape == numpy_channels[0].shape for x in numpy_channels[1:]): + return + + return Image(numpy.dstack(numpy_channels), py_colorspace) + + +class ColorQuantize(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ColorQuantize.html</url> + + <dl> + <dt>'ColorQuantize[$image$, $n$]' + <dd>gives a version of $image$ using only $n$ colors. + </dl> + + >> img = Import["ExampleData/lena.tif"]; + >> ColorQuantize[img, 6] + = -Image- + + #> ColorQuantize[img, 0] + : Positive integer expected at position 2 in ColorQuantize[-Image-, 0]. + = ColorQuantize[-Image-, 0] + #> ColorQuantize[img, -1] + : Positive integer expected at position 2 in ColorQuantize[-Image-, -1]. + = ColorQuantize[-Image-, -1] + """ + + summary_text = "give an approximation to image that uses only n distinct colors" + messages = {"intp": "Positive integer expected at position `2` in `1`."} + + def eval(self, image, n: Integer, evaluation: Evaluation): + "ColorQuantize[image_Image, n_Integer]" + py_value = n.value + if py_value <= 0: + return evaluation.message( + "ColorQuantize", "intp", Expression(SymbolColorQuantize, image, n), 2 + ) + converted = image.color_convert("RGB") + if converted is None: + return + pixels = pixels_as_ubyte(converted.pixels) + im = PIL.Image.fromarray(pixels).quantize(py_value) + im = im.convert("RGB") + return Image(numpy.array(im), "RGB") + + +class ColorSeparate(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ColorSeparate.html</url> + + <dl> + <dt>'ColorSeparate[$image$]' + <dd>Gives each channel of $image$ as a separate grayscale image. + </dl> + + >> img = Import["ExampleData/lena.tif"]; + >> ColorSeparate[img] + = ... + + """ + + summary_text = "separate color channels" + + def eval(self, image: Image, evaluation: Evaluation): + "ColorSeparate[image_]" + images = [] + pixels = image.pixels + if len(pixels.shape) < 3: + images.append(pixels) + else: + for i in range(pixels.shape[2]): + images.append(Image(pixels[:, :, i], "Grayscale")) + return ListExpression(*images) + + +class Colorize(Builtin): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/Colorize.html</url> + + <dl> + <dt>'Colorize[$values$]' + <dd>returns an image where each number in the rectangular matrix \ + $values$ is a pixel and each occurence of the same number is \ + displayed in the same unique color, which is different from the \ + colors of all non-identical numbers. + + <dt>'Colorize[$image$]' + <dd>gives a colorized version of $image$. + </dl> + + >> Colorize[{{1.3, 2.1, 1.5}, {1.3, 1.3, 2.1}, {1.3, 2.1, 1.5}}] + = -Image- + + >> Colorize[{{1, 2}, {2, 2}, {2, 3}}, ColorFunction -> (Blend[{White, Blue}, #]&)] + = -Image- + """ + + summary_text = "create pseudocolor images" + options = {"ColorFunction": "Automatic"} + + messages = { + "cfun": "`1` is neither a gradient ColorData nor a pure function suitable as ColorFunction." + } + + def eval(self, values, evaluation, options): + "Colorize[values_, OptionsPattern[%(name)s]]" + + if isinstance(values, Image): + pixels = values.grayscale().pixels + matrix = pixels_as_ubyte(pixels.reshape(pixels.shape[:2])) + else: + if not Expression(SymbolMatrixQ, values).evaluate(evaluation) is SymbolTrue: + return + matrix = matrix_to_numpy(values) + + a, n = _linearize_numpy_array(matrix) + # the maximum value for n is the number of pixels in a, which is acceptable and never too large. + + color_function = self.get_option(options, "ColorFunction", evaluation) + if ( + isinstance(color_function, Symbol) + and color_function.get_name() == "System`Automatic" + ): + color_function = String("LakeColors") + + from mathics.builtin.drawing.plot import gradient_palette + + cmap = gradient_palette(color_function, n, evaluation) + if not cmap: + evaluation.message("Colorize", "cfun", color_function) + return + + s = (a.shape[0], a.shape[1], 1) + p = numpy.transpose(numpy.array([cmap[i] for i in range(n)])[:, 0:3]) + return Image( + numpy.concatenate([p[i][a].reshape(s) for i in range(3)], axis=2), + color_space="RGB", + ) + + +class ImageColorSpace(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ImageColorSpace.html</url> + + <dl> + <dt>'ImageColorSpace[$image$]' + <dd>gives $image$'s color space, e.g. "RGB" or "CMYK". + </dl> + + >> img = Import["ExampleData/MadTeaParty.gif"]; + >> ImageColorSpace[img] + = Grayscale + + >> img = Import["ExampleData/sunflowers.jpg"]; + >> ImageColorSpace[img] + = RGB + """ + + summary_text = "colorspace used in the image" + + def eval(self, image: Image, evaluation: Evaluation): + "ImageColorSpace[image_Image]" + return String(image.color_space) diff --git a/mathics/builtin/image/misc.py b/mathics/builtin/image/misc.py index 7ae4423fa..6bda91fa1 100644 --- a/mathics/builtin/image/misc.py +++ b/mathics/builtin/image/misc.py @@ -10,7 +10,6 @@ # the top level. sort_order = "mathics.builtin.image-and-image-related-functions" -import functools import os.path as osp from collections import defaultdict @@ -18,7 +17,6 @@ import PIL from mathics.builtin.base import Builtin, String -from mathics.builtin.colors.color_internals import colorspaces as known_colorspaces from mathics.builtin.image.base import Image, _SkimageBuiltin from mathics.core.atoms import Integer from mathics.core.convert.expression import to_mathics_list @@ -26,21 +24,16 @@ from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull, SymbolTrue +from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull from mathics.core.systemsymbols import SymbolRule from mathics.eval.image import ( extract_exif, - matrix_to_numpy, numpy_to_matrix, pixels_as_float, pixels_as_ubyte, pixels_as_uint, ) -SymbolColorQuantize = Symbol("ColorQuantize") -SymbolMatrixQ = Symbol("MatrixQ") -SymbolThreshold = Symbol("Threshold") - _skimage_requires = ("skimage", "scipy", "matplotlib", "networkx") # The following classes are used to allow inclusion of @@ -231,279 +224,6 @@ def eval(self, image, r, t, evaluation: Evaluation): # color space -class ImageColorSpace(Builtin): - """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/ImageColorSpace.html</url> - - <dl> - <dt>'ImageColorSpace[$image$]' - <dd>gives $image$'s color space, e.g. "RGB" or "CMYK". - </dl> - - >> img = Import["ExampleData/lena.tif"]; - >> ImageColorSpace[img] - = RGB - """ - - summary_text = "colorspace used in the image" - - def eval(self, image, evaluation: Evaluation): - "ImageColorSpace[image_Image]" - return String(image.color_space) - - -class ColorQuantize(Builtin): - """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/ColorQuantize.html</url> - - <dl> - <dt>'ColorQuantize[$image$, $n$]' - <dd>gives a version of $image$ using only $n$ colors. - </dl> - - >> img = Import["ExampleData/lena.tif"]; - >> ColorQuantize[img, 6] - = -Image- - - #> ColorQuantize[img, 0] - : Positive integer expected at position 2 in ColorQuantize[-Image-, 0]. - = ColorQuantize[-Image-, 0] - #> ColorQuantize[img, -1] - : Positive integer expected at position 2 in ColorQuantize[-Image-, -1]. - = ColorQuantize[-Image-, -1] - """ - - summary_text = "give an approximation to image that uses only n distinct colors" - messages = {"intp": "Positive integer expected at position `2` in `1`."} - - def eval(self, image, n: Integer, evaluation: Evaluation): - "ColorQuantize[image_Image, n_Integer]" - py_value = n.value - if py_value <= 0: - return evaluation.message( - "ColorQuantize", "intp", Expression(SymbolColorQuantize, image, n), 2 - ) - converted = image.color_convert("RGB") - if converted is None: - return - pixels = pixels_as_ubyte(converted.pixels) - im = PIL.Image.fromarray(pixels).quantize(py_value) - im = im.convert("RGB") - return Image(numpy.array(im), "RGB") - - -class Binarize(Builtin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Binarize.html</url> - - <dl> - <dt>'Binarize[$image$]' - <dd>gives a binarized version of $image$, in which each pixel is either 0 or 1. - - <dt>'Binarize[$image$, $t$]' - <dd>map values $x$ > $t$ to 1, and values $x$ <= $t$ to 0. - - <dt>'Binarize[$image$, {$t1$, $t2$}]' - <dd>map $t1$ < $x$ < $t2$ to 1, and all other values to 0. - </dl> - - S> img = Import["ExampleData/lena.tif"]; - S> Binarize[img] - = -Image- - S> Binarize[img, 0.7] - = -Image- - S> Binarize[img, {0.2, 0.6}] - = -Image- - """ - - summary_text = "create a binarized image" - - def eval(self, image, evaluation: Evaluation): - "Binarize[image_Image]" - image = image.grayscale() - thresh = ( - Expression(SymbolThreshold, image).evaluate(evaluation).round_to_float() - ) - if thresh is not None: - return Image(image.pixels > thresh, "Grayscale") - - def eval_t(self, image, t, evaluation: Evaluation): - "Binarize[image_Image, t_?RealNumberQ]" - pixels = image.grayscale().pixels - return Image(pixels > t.round_to_float(), "Grayscale") - - def eval_t1_t2(self, image, t1, t2, evaluation: Evaluation): - "Binarize[image_Image, {t1_?RealNumberQ, t2_?RealNumberQ}]" - pixels = image.grayscale().pixels - mask1 = pixels > t1.round_to_float() - mask2 = pixels < t2.round_to_float() - return Image(mask1 * mask2, "Grayscale") - - -class ColorSeparate(Builtin): - """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/ColorSeparate.html</url> - - <dl> - <dt>'ColorSeparate[$image$]' - <dd>Gives each channel of $image$ as a separate grayscale image. - </dl> - """ - - summary_text = "separate color channels" - - def eval(self, image, evaluation: Evaluation): - "ColorSeparate[image_Image]" - images = [] - pixels = image.pixels - if len(pixels.shape) < 3: - images.append(pixels) - else: - for i in range(pixels.shape[2]): - images.append(Image(pixels[:, :, i], "Grayscale")) - return ListExpression(*images) - - -class ColorCombine(Builtin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/ColorCombine.html</url> - - <dl> - <dt>'ColorCombine[$channels$, $colorspace$]' - <dd>Gives an image with $colorspace$ and the respective components described by the given channels. - </dl> - - >> ColorCombine[{{{1, 0}, {0, 0.75}}, {{0, 1}, {0, 0.25}}, {{0, 0}, {1, 0.5}}}, "RGB"] - = -Image- - """ - - summary_text = "combine color channels" - - def eval(self, channels, colorspace, evaluation: Evaluation): - "ColorCombine[channels_List, colorspace_String]" - - py_colorspace = colorspace.get_string_value() - if py_colorspace not in known_colorspaces: - return - - numpy_channels = [] - for channel in channels.elements: - if ( - not Expression(SymbolMatrixQ, channel).evaluate(evaluation) - is SymbolTrue - ): - return - numpy_channels.append(matrix_to_numpy(channel)) - - if not numpy_channels: - return - - if not all(x.shape == numpy_channels[0].shape for x in numpy_channels[1:]): - return - - return Image(numpy.dstack(numpy_channels), py_colorspace) - - -def _linearize(a): - # this uses a vectorized binary search to compute - # strictly sequential indices for all values in a. - - orig_shape = a.shape - a = a.reshape((functools.reduce(lambda x, y: x * y, a.shape),)) # 1 dimension - - u = numpy.unique(a) - n = len(u) - - lower = numpy.ndarray(a.shape, dtype=int) - lower.fill(0) - upper = numpy.ndarray(a.shape, dtype=int) - upper.fill(n - 1) - - h = numpy.sort(u) - q = n # worst case partition size - - while q > 2: - m = numpy.right_shift(lower + upper, 1) - f = a <= h[m] - # (lower, m) vs (m + 1, upper) - lower = numpy.where(f, lower, m + 1) - upper = numpy.where(f, m, upper) - q = (q + 1) // 2 - - return numpy.where(a == h[lower], lower, upper).reshape(orig_shape), n - - -class Colorize(Builtin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/Colorize.html</url> - - <dl> - <dt>'Colorize[$values$]' - <dd>returns an image where each number in the rectangular matrix \ - $values$ is a pixel and each occurence of the same number is \ - displayed in the same unique color, which is different from the \ - colors of all non-identical numbers. - - <dt>'Colorize[$image$]' - <dd>gives a colorized version of $image$. - </dl> - - >> Colorize[{{1.3, 2.1, 1.5}, {1.3, 1.3, 2.1}, {1.3, 2.1, 1.5}}] - = -Image- - - >> Colorize[{{1, 2}, {2, 2}, {2, 3}}, ColorFunction -> (Blend[{White, Blue}, #]&)] - = -Image- - """ - - summary_text = "create pseudocolor images" - options = {"ColorFunction": "Automatic"} - - messages = { - "cfun": "`1` is neither a gradient ColorData nor a pure function suitable as ColorFunction." - } - - def eval(self, values, evaluation, options): - "Colorize[values_, OptionsPattern[%(name)s]]" - - if isinstance(values, Image): - pixels = values.grayscale().pixels - matrix = pixels_as_ubyte(pixels.reshape(pixels.shape[:2])) - else: - if not Expression(SymbolMatrixQ, values).evaluate(evaluation) is SymbolTrue: - return - matrix = matrix_to_numpy(values) - - a, n = _linearize(matrix) - # the maximum value for n is the number of pixels in a, which is acceptable and never too large. - - color_function = self.get_option(options, "ColorFunction", evaluation) - if ( - isinstance(color_function, Symbol) - and color_function.get_name() == "System`Automatic" - ): - color_function = String("LakeColors") - - from mathics.builtin.drawing.plot import gradient_palette - - cmap = gradient_palette(color_function, n, evaluation) - if not cmap: - evaluation.message("Colorize", "cfun", color_function) - return - - s = (a.shape[0], a.shape[1], 1) - p = numpy.transpose(numpy.array([cmap[i] for i in range(n)])[:, 0:3]) - return Image( - numpy.concatenate([p[i][a].reshape(s) for i in range(3)], axis=2), - color_space="RGB", - ) - - # pixel access diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 978ee2f52..9b0aea436 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -119,6 +119,7 @@ SymbolMakeBoxes = Symbol("System`MakeBoxes") SymbolMap = Symbol("System`Map") SymbolMatchQ = Symbol("System`MatchQ") +SymbolMatrixQ = Symbol("System`MatrixQ") SymbolMathMLForm = Symbol("System`MathMLForm") SymbolMatrixPower = Symbol("System`MatrixPower") SymbolMax = Symbol("System`Max") @@ -204,6 +205,7 @@ SymbolTanh = Symbol("System`Tanh") SymbolTeXForm = Symbol("System`TeXForm") SymbolThrow = Symbol("System`Throw") +SymbolThreshold = Symbol("System`Threshold") SymbolToString = Symbol("System`ToString") SymbolTotal = Symbol("System`Total") SymbolTraditionalForm = Symbol("System`TraditionalForm") From e0fdedbc575e01029aab7f070be6c29dfd2eb390 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Fri, 30 Dec 2022 06:48:22 -0500 Subject: [PATCH 118/121] Split out Image Pixel operations Update CHANGES.rst for all of the splitting going on --- CHANGES.rst | 1 + mathics/builtin/image/misc.py | 116 +------------------- mathics/builtin/image/pixel.py | 169 +++++++++++++++++++++++++++++ mathics/builtin/image/structure.py | 92 +++------------- 4 files changed, 189 insertions(+), 189 deletions(-) create mode 100644 mathics/builtin/image/pixel.py diff --git a/CHANGES.rst b/CHANGES.rst index 0535b17bb..934fd0eb8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -51,6 +51,7 @@ Documentation #. "Forms of Input and Output" is its own section #. All Builtins have links to WMA pages. #. More url links to Wiki pages added; more internal cross links added. +#. Image has been split off from Graphics and Drawing. There are now subsections for Image Internals +++++++++ diff --git a/mathics/builtin/image/misc.py b/mathics/builtin/image/misc.py index 6bda91fa1..c84301944 100644 --- a/mathics/builtin/image/misc.py +++ b/mathics/builtin/image/misc.py @@ -26,13 +26,7 @@ from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull from mathics.core.systemsymbols import SymbolRule -from mathics.eval.image import ( - extract_exif, - numpy_to_matrix, - pixels_as_float, - pixels_as_ubyte, - pixels_as_uint, -) +from mathics.eval.image import extract_exif _skimage_requires = ("skimage", "scipy", "matplotlib", "networkx") @@ -221,114 +215,6 @@ def eval(self, image, r, t, evaluation: Evaluation): ) -# color space - - -# pixel access - - -class ImageData(Builtin): - """ - - <url>:WMA link: - https://reference.wolfram.com/language/ref/ImageData.html</url> - - <dl> - <dt>'ImageData[$image$]' - <dd>gives a list of all color values of $image$ as a matrix. - - <dt>'ImageData[$image$, $stype$]' - <dd>gives a list of color values in type $stype$. - </dl> - - >> img = Image[{{0.2, 0.4}, {0.9, 0.6}, {0.5, 0.8}}]; - >> ImageData[img] - = {{0.2, 0.4}, {0.9, 0.6}, {0.5, 0.8}} - - >> ImageData[img, "Byte"] - = {{51, 102}, {229, 153}, {127, 204}} - - >> ImageData[Image[{{0, 1}, {1, 0}, {1, 1}}], "Bit"] - = {{0, 1}, {1, 0}, {1, 1}} - - #> ImageData[img, "Bytf"] - : Unsupported pixel format "Bytf". - = ImageData[-Image-, Bytf] - """ - - messages = {"pixelfmt": 'Unsupported pixel format "``".'} - - rules = {"ImageData[image_Image]": 'ImageData[image, "Real"]'} - summary_text = "the array of pixel values from an image" - - def eval(self, image, stype: String, evaluation: Evaluation): - "ImageData[image_Image, stype_String]" - pixels = image.pixels - stype = stype.value - if stype == "Real": - pixels = pixels_as_float(pixels) - elif stype == "Byte": - pixels = pixels_as_ubyte(pixels) - elif stype == "Bit16": - pixels = pixels_as_uint(pixels) - elif stype == "Bit": - pixels = pixels.astype(int) - else: - return evaluation.message("ImageData", "pixelfmt", stype) - return from_python(numpy_to_matrix(pixels)) - - -class PixelValuePositions(Builtin): - """ - <url>:WMA link:https://reference.wolfram.com/language/ref/PixelValuePositions.html</url> - - <dl> - <dt>'PixelValuePositions[$image$, $val$]' - <dd>gives the positions of all pixels in $image$ that have value $val$. - </dl> - - >> PixelValuePositions[Image[{{0, 1}, {1, 0}, {1, 1}}], 1] - = {{1, 1}, {1, 2}, {2, 1}, {2, 3}} - - >> PixelValuePositions[Image[{{0.2, 0.4}, {0.9, 0.6}, {0.3, 0.8}}], 0.5, 0.15] - = {{2, 2}, {2, 3}} - - >> img = Import["ExampleData/lena.tif"]; - >> PixelValuePositions[img, 3 / 255, 0.5 / 255] - = {{180, 192, 2}, {181, 192, 2}, {181, 193, 2}, {188, 204, 2}, {265, 314, 2}, {364, 77, 2}, {365, 72, 2}, {365, 73, 2}, {365, 77, 2}, {366, 70, 2}, {367, 65, 2}} - >> PixelValue[img, {180, 192}] - = {0.25098, 0.0117647, 0.215686} - """ - - rules = { - "PixelValuePositions[image_Image, val_?RealNumberQ]": "PixelValuePositions[image, val, 0]" - } - - summary_text = "list the position of pixels with a given value" - - def eval(self, image, val, d, evaluation: Evaluation): - "PixelValuePositions[image_Image, val_?RealNumberQ, d_?RealNumberQ]" - val = val.round_to_float() - d = d.round_to_float() - - positions = numpy.argwhere( - numpy.isclose(pixels_as_float(image.pixels), val, atol=d, rtol=0) - ) - - # python indexes from 0 at top left -> indices from 1 starting at bottom left - # if single channel then ommit channel indices - height = image.pixels.shape[0] - if image.pixels.shape[2] == 1: - result = sorted((j + 1, height - i) for i, j, k in positions.tolist()) - else: - result = sorted( - (j + 1, height - i, k + 1) for i, j, k in positions.tolist() - ) - return ListExpression( - *(to_mathics_list(*arg, elements_conversion_fn=Integer) for arg in result) - ) - - # image attribute queries diff --git a/mathics/builtin/image/pixel.py b/mathics/builtin/image/pixel.py new file mode 100644 index 000000000..5d4fc22ac --- /dev/null +++ b/mathics/builtin/image/pixel.py @@ -0,0 +1,169 @@ +""" +Pixel Operations +""" +import numpy + +from mathics.builtin.base import Builtin, String +from mathics.core.atoms import Integer, MachineReal +from mathics.core.convert.expression import to_mathics_list +from mathics.core.convert.python import from_python +from mathics.core.evaluation import Evaluation +from mathics.core.list import ListExpression +from mathics.eval.image import ( + numpy_to_matrix, + pixels_as_float, + pixels_as_ubyte, + pixels_as_uint, +) + + +class PixelValue(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/PixelValue.html</url> + + <dl> + <dt>'PixelValue[$image$, {$x$, $y$}]' + <dd>gives the value of the pixel at position {$x$, $y$} in $image$. + </dl> + + >> lena = Import["ExampleData/lena.tif"]; + >> PixelValue[lena, {1, 1}] + = {0.321569, 0.0862745, 0.223529} + #> {82 / 255, 22 / 255, 57 / 255} // N (* pixel byte values from bottom left corner *) + = {0.321569, 0.0862745, 0.223529} + + #> PixelValue[lena, {0, 1}]; + : Padding not implemented for PixelValue. + #> PixelValue[lena, {512, 1}] + = {0.72549, 0.290196, 0.317647} + #> PixelValue[lena, {513, 1}]; + : Padding not implemented for PixelValue. + #> PixelValue[lena, {1, 0}]; + : Padding not implemented for PixelValue. + #> PixelValue[lena, {1, 512}] + = {0.886275, 0.537255, 0.490196} + #> PixelValue[lena, {1, 513}]; + : Padding not implemented for PixelValue. + """ + + messages = {"nopad": "Padding not implemented for PixelValue."} + + summary_text = "get pixel value of image at a given position" + + def eval(self, image, x, y, evaluation: Evaluation): + "PixelValue[image_Image, {x_?RealNumberQ, y_?RealNumberQ}]" + x = int(x.round_to_float()) + y = int(y.round_to_float()) + height = image.pixels.shape[0] + width = image.pixels.shape[1] + if not (1 <= x <= width and 1 <= y <= height): + return evaluation.message("PixelValue", "nopad") + pixel = pixels_as_float(image.pixels)[height - y, x - 1] + if isinstance(pixel, (numpy.ndarray, numpy.generic, list)): + return ListExpression(*[MachineReal(float(x)) for x in list(pixel)]) + else: + return MachineReal(float(pixel)) + + +class ImageData(Builtin): + """ + + <url>:WMA link: + https://reference.wolfram.com/language/ref/ImageData.html</url> + + <dl> + <dt>'ImageData[$image$]' + <dd>gives a list of all color values of $image$ as a matrix. + + <dt>'ImageData[$image$, $stype$]' + <dd>gives a list of color values in type $stype$. + </dl> + + >> img = Image[{{0.2, 0.4}, {0.9, 0.6}, {0.5, 0.8}}]; + >> ImageData[img] + = {{0.2, 0.4}, {0.9, 0.6}, {0.5, 0.8}} + + >> ImageData[img, "Byte"] + = {{51, 102}, {229, 153}, {127, 204}} + + >> ImageData[Image[{{0, 1}, {1, 0}, {1, 1}}], "Bit"] + = {{0, 1}, {1, 0}, {1, 1}} + + #> ImageData[img, "Bytf"] + : Unsupported pixel format "Bytf". + = ImageData[-Image-, Bytf] + """ + + messages = {"pixelfmt": 'Unsupported pixel format "``".'} + + rules = {"ImageData[image_Image]": 'ImageData[image, "Real"]'} + summary_text = "the array of pixel values from an image" + + def eval(self, image, stype: String, evaluation: Evaluation): + "ImageData[image_Image, stype_String]" + pixels = image.pixels + stype = stype.value + if stype == "Real": + pixels = pixels_as_float(pixels) + elif stype == "Byte": + pixels = pixels_as_ubyte(pixels) + elif stype == "Bit16": + pixels = pixels_as_uint(pixels) + elif stype == "Bit": + pixels = pixels.astype(int) + else: + return evaluation.message("ImageData", "pixelfmt", stype) + return from_python(numpy_to_matrix(pixels)) + + +class PixelValuePositions(Builtin): + """ + <url>:WMA link:https://reference.wolfram.com/language/ref/PixelValuePositions.html</url> + + <dl> + <dt>'PixelValuePositions[$image$, $val$]' + <dd>gives the positions of all pixels in $image$ that have value $val$. + </dl> + + >> PixelValuePositions[Image[{{0, 1}, {1, 0}, {1, 1}}], 1] + = {{1, 1}, {1, 2}, {2, 1}, {2, 3}} + + >> PixelValuePositions[Image[{{0.2, 0.4}, {0.9, 0.6}, {0.3, 0.8}}], 0.5, 0.15] + = {{2, 2}, {2, 3}} + + >> img = Import["ExampleData/lena.tif"]; + >> PixelValuePositions[img, 3 / 255, 0.5 / 255] + = {{180, 192, 2}, {181, 192, 2}, {181, 193, 2}, {188, 204, 2}, {265, 314, 2}, {364, 77, 2}, {365, 72, 2}, {365, 73, 2}, {365, 77, 2}, {366, 70, 2}, {367, 65, 2}} + >> PixelValue[img, {180, 192}] + = {0.25098, 0.0117647, 0.215686} + """ + + rules = { + "PixelValuePositions[image_Image, val_?RealNumberQ]": "PixelValuePositions[image, val, 0]" + } + + summary_text = "list the position of pixels with a given value" + + def eval(self, image, val, d, evaluation: Evaluation): + "PixelValuePositions[image_Image, val_?RealNumberQ, d_?RealNumberQ]" + val = val.round_to_float() + d = d.round_to_float() + + positions = numpy.argwhere( + numpy.isclose(pixels_as_float(image.pixels), val, atol=d, rtol=0) + ) + + # python indexes from 0 at top left -> indices from 1 starting at bottom left + # if single channel then ommit channel indices + height = image.pixels.shape[0] + if image.pixels.shape[2] == 1: + result = sorted((j + 1, height - i) for i, j, k in positions.tolist()) + else: + result = sorted( + (j + 1, height - i, k + 1) for i, j, k in positions.tolist() + ) + return ListExpression( + *(to_mathics_list(*arg, elements_conversion_fn=Integer) for arg in result) + ) diff --git a/mathics/builtin/image/structure.py b/mathics/builtin/image/structure.py index be29c83bc..145d6b5ea 100644 --- a/mathics/builtin/image/structure.py +++ b/mathics/builtin/image/structure.py @@ -5,10 +5,13 @@ from mathics.builtin.base import Builtin from mathics.builtin.image.base import Image -from mathics.core.atoms import Integer, MachineReal +from mathics.core.atoms import Integer from mathics.core.evaluation import Evaluation -from mathics.core.list import ListExpression -from mathics.eval.image import numpy_flip, pixels_as_float +from mathics.eval.image import numpy_flip + + +def clip_to(i: int, upper) -> int: + return min(i, upper) if i > 0 else max(0, upper + i) class ImageTake(Builtin): @@ -54,9 +57,13 @@ def _image_slice(self, image, i1: Integer, i2: Integer, axis): indicting a slice, a function flip, that will reverse the pixels in an image if necessary. """ + + def _clip_to(i: int, lower, upper) -> int: + return min(max(i, 0), upper) + n = image.pixels.shape[axis] - py_i1 = min(max(i1.value - 1, 0), n - 1) - py_i2 = min(max(i2.value - 1, 0), n - 1) + py_i1 = -clip_to(i1.value - 1, 0, n - 1) + py_i2 = _clip_to(i2.value - 1, 0, n - 1) def flip(pixels): if py_i1 > py_i2: @@ -102,12 +109,8 @@ def eval_rows(self, image, r1: Integer, r2: Integer, evaluation: Evaluation): last_row = r2.value max_row, max_col = image.pixels.shape[:2] - adjusted_first_row = ( - min(first_row, max_row) if first_row > 0 else max(0, max_row + first_row) - ) - adjusted_last_row = ( - min(last_row, max_row) if last_row > 0 else max(0, max_row + first_row) - ) + adjusted_first_row = clip_to(first_row, last_row) + adjusted_last_row = clip_to(last_row, max_row) # More complicated in that it reverses the data? # if adjusted_first_row > adjusted_last_row: @@ -135,18 +138,10 @@ def eval_rows_cols( last_col = c2.value max_row, max_col = image.pixels.shape[:2] - adjusted_first_row = ( - min(first_row, max_row) if first_row > 0 else max(0, max_row + first_row) - ) - adjusted_last_row = ( - min(last_row, max_row) if last_row > 0 else max(0, max_row + last_row) - ) - adjusted_first_col = ( - min(first_col, max_col) if first_col > 0 else max(0, max_col + first_col) - ) - adjusted_last_col = ( - min(last_col, max_col) if last_col > 0 else max(0, max_col + last_col) - ) + adjusted_first_row = clip_to(first_row, max_row) + adjusted_last_row = clip_to(last_row, max_row) + adjusted_first_col = clip_to(first_col, max_col) + adjusted_last_col = clip_to(last_col, max_col) # if adjusted_first_row > adjusted_last_row: # adjusted_first_row, adjusted_last_row = adjusted_last_row, adjusted_first_row @@ -188,55 +183,4 @@ def eval_rows_cols( # return Image(fc(fr(image.pixels[sr, sc])), image.color_space) -# FIXME: move to not-yet created Pixel Operations -class PixelValue(Builtin): - """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/PixelValue.html</url> - - <dl> - <dt>'PixelValue[$image$, {$x$, $y$}]' - <dd>gives the value of the pixel at position {$x$, $y$} in $image$. - </dl> - - >> lena = Import["ExampleData/lena.tif"]; - >> PixelValue[lena, {1, 1}] - = {0.321569, 0.0862745, 0.223529} - #> {82 / 255, 22 / 255, 57 / 255} // N (* pixel byte values from bottom left corner *) - = {0.321569, 0.0862745, 0.223529} - - #> PixelValue[lena, {0, 1}]; - : Padding not implemented for PixelValue. - #> PixelValue[lena, {512, 1}] - = {0.72549, 0.290196, 0.317647} - #> PixelValue[lena, {513, 1}]; - : Padding not implemented for PixelValue. - #> PixelValue[lena, {1, 0}]; - : Padding not implemented for PixelValue. - #> PixelValue[lena, {1, 512}] - = {0.886275, 0.537255, 0.490196} - #> PixelValue[lena, {1, 513}]; - : Padding not implemented for PixelValue. - """ - - messages = {"nopad": "Padding not implemented for PixelValue."} - - summary_text = "get pixel value of image at a given position" - - def eval(self, image, x, y, evaluation: Evaluation): - "PixelValue[image_Image, {x_?RealNumberQ, y_?RealNumberQ}]" - x = int(x.round_to_float()) - y = int(y.round_to_float()) - height = image.pixels.shape[0] - width = image.pixels.shape[1] - if not (1 <= x <= width and 1 <= y <= height): - return evaluation.message("PixelValue", "nopad") - pixel = pixels_as_float(image.pixels)[height - y, x - 1] - if isinstance(pixel, (numpy.ndarray, numpy.generic, list)): - return ListExpression(*[MachineReal(float(x)) for x in list(pixel)]) - else: - return MachineReal(float(pixel)) - - # TODO; ImageCrop, ImageTrip, ImagePad, BorderDimensions From db1849e5e3a8a1d454585ef158868ba128c8f168 Mon Sep 17 00:00:00 2001 From: rocky <rb@dustyfeet.com> Date: Fri, 30 Dec 2022 12:10:12 -0500 Subject: [PATCH 119/121] Add ImageProperties... correct some other classifications. Add a few type annotations --- mathics/builtin/image/composition.py | 174 +++++++++++++++- mathics/builtin/image/misc.py | 290 +-------------------------- mathics/builtin/image/pixel.py | 71 ++----- mathics/builtin/image/properties.py | 173 ++++++++++++++++ mathics/doc/common_doc.py | 4 +- 5 files changed, 364 insertions(+), 348 deletions(-) create mode 100644 mathics/builtin/image/properties.py diff --git a/mathics/builtin/image/composition.py b/mathics/builtin/image/composition.py index 2c360a8a7..b76873294 100644 --- a/mathics/builtin/image/composition.py +++ b/mathics/builtin/image/composition.py @@ -1,12 +1,16 @@ """ Image Compositions """ +import os.path as osp +from collections import defaultdict + import numpy -from mathics.builtin.base import Builtin +from mathics.builtin.base import Builtin, String from mathics.builtin.image.base import Image from mathics.core.atoms import Integer, Rational, Real from mathics.core.evaluation import Evaluation +from mathics.core.symbols import Symbol from mathics.eval.image import pixels_as_float @@ -155,4 +159,170 @@ class ImageSubtract(_ImageArithmetic): summary_text = "build an image substracting pixel values of another image " -# TODO: ImageAssemble, ImageCollage, ImageCompose +class WordCloud(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/WordCloud.html</url> + + <dl> + <dt>'WordCloud[{$word1$, $word2$, ...}]' + <dd>Gives a word cloud with the given list of words. + + <dt>'WordCloud[{$weight1$ -> $word1$, $weight2$ -> $word2$, ...}]' + <dd>Gives a word cloud with the words weighted using the given weights. + + <dt>'WordCloud[{$weight1$, $weight2$, ...} -> {$word1$, $word2$, ...}]' + <dd>Also gives a word cloud with the words weighted using the given weights. + + <dt>'WordCloud[{{$word1$, $weight1$}, {$word2$, $weight2$}, ...}]' + <dd>Gives a word cloud with the words weighted using the given weights. + </dl> + + >> WordCloud[StringSplit[Import["ExampleData/EinsteinSzilLetter.txt", CharacterEncoding->"UTF8"]]] + = -Image- + + >> WordCloud[Range[50] -> ToString /@ Range[50]] + = -Image- + """ + + # this is the palettable.colorbrewer.qualitative.Dark2_8 palette + default_colors = ( + (27, 158, 119), + (217, 95, 2), + (117, 112, 179), + (231, 41, 138), + (102, 166, 30), + (230, 171, 2), + (166, 118, 29), + (102, 102, 102), + ) + + options = { + "IgnoreCase": "True", + "ImageSize": "Automatic", + "MaxItems": "Automatic", + } + + requires = ("wordcloud",) + + summary_text = "show a word cloud from a list of words" + + def eval_words_weights(self, weights, words, evaluation, options): + "WordCloud[weights_List -> words_List, OptionsPattern[%(name)s]]" + if len(weights.elements) != len(words.elements): + return + + def weights_and_words(): + for weight, word in zip(weights.elements, words.elements): + yield weight.round_to_float(), word.get_string_value() + + return self._word_cloud(weights_and_words(), evaluation, options) + + def eval_words(self, words, evaluation, options): + "WordCloud[words_List, OptionsPattern[%(name)s]]" + + if not words: + return + elif isinstance(words.elements[0], String): + + def weights_and_words(): + for word in words.elements: + yield 1, word.get_string_value() + + else: + + def weights_and_words(): + for word in words.elements: + if len(word.elements) != 2: + raise ValueError + + head_name = word.get_head_name() + if head_name == "System`Rule": + weight, s = word.elements + elif head_name == "System`List": + s, weight = word.elements + else: + raise ValueError + + yield weight.round_to_float(), s.get_string_value() + + try: + return self._word_cloud(weights_and_words(), evaluation, options) + except ValueError: + return + + def _word_cloud(self, words, evaluation, options): + ignore_case = self.get_option(options, "IgnoreCase", evaluation) is Symbol( + "True" + ) + + freq = defaultdict(int) + for py_weight, py_word in words: + if py_word is None or py_weight is None: + return + key = py_word.lower() if ignore_case else py_word + freq[key] += py_weight + + max_items = self.get_option(options, "MaxItems", evaluation) + if isinstance(max_items, Integer): + py_max_items = max_items.get_int_value() + else: + py_max_items = 200 + + image_size = self.get_option(options, "ImageSize", evaluation) + if image_size is Symbol("Automatic"): + py_image_size = (800, 600) + elif ( + image_size.get_head_name() == "System`List" + and len(image_size.elements) == 2 + ): + py_image_size = [] + for element in image_size.elements: + if not isinstance(element, Integer): + return + py_image_size.append(element.get_int_value()) + elif isinstance(image_size, Integer): + size = image_size.get_int_value() + py_image_size = (size, size) + else: + return + + # inspired by http://minimaxir.com/2016/05/wordclouds/ + import random + + def color_func( + word, font_size, position, orientation, random_state=None, **kwargs + ): + return self.default_colors[random.randint(0, 7)] + + font_base_path = osp.join(osp.dirname(osp.abspath(__file__)), "..", "fonts") + + font_path = osp.realpath(font_base_path + "AmaticSC-Bold.ttf") + if not osp.exists(font_path): + font_path = None + + from wordcloud import WordCloud + + wc = WordCloud( + width=py_image_size[0], + height=py_image_size[1], + font_path=font_path, + max_font_size=300, + mode="RGB", + background_color="white", + max_words=py_max_items, + color_func=color_func, + random_state=42, + stopwords=set(), + ) + wc.generate_from_frequencies(freq) + + image = wc.to_image() + return Image(numpy.array(image), "RGB") + + +# TODO: +# ImageAssemble, +# Collage Creation other than WordCloud +# ImageCompose diff --git a/mathics/builtin/image/misc.py b/mathics/builtin/image/misc.py index c84301944..f60a26ecc 100644 --- a/mathics/builtin/image/misc.py +++ b/mathics/builtin/image/misc.py @@ -1,30 +1,20 @@ # -*- coding: utf-8 -*- -# FIXME - plit out rest: +# FIXME - split out rest: # Color Manipulation, etc. """ Miscellaneous image-related functions """ -# This tells documentation how to sort this module -# Here, we are also hiding "drawing" since this erroneously appears at -# the top level. -sort_order = "mathics.builtin.image-and-image-related-functions" - -import os.path as osp -from collections import defaultdict - import numpy import PIL from mathics.builtin.base import Builtin, String from mathics.builtin.image.base import Image, _SkimageBuiltin -from mathics.core.atoms import Integer -from mathics.core.convert.expression import to_mathics_list from mathics.core.convert.python import from_python from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.symbols import Symbol, SymbolDivide, SymbolNull +from mathics.core.symbols import Symbol, SymbolNull from mathics.core.systemsymbols import SymbolRule from mathics.eval.image import extract_exif @@ -215,118 +205,6 @@ def eval(self, image, r, t, evaluation: Evaluation): ) -# image attribute queries - - -class ImageDimensions(Builtin): - """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/ImageDimensions.html</url> - - <dl> - <dt>'ImageDimensions[$image$]' - <dd>Returns the dimensions {$width$, $height$} of $image$ in pixels. - </dl> - - >> lena = Import["ExampleData/lena.tif"]; - >> ImageDimensions[lena] - = {512, 512} - - >> ImageDimensions[RandomImage[1, {50, 70}]] - = {50, 70} - """ - - summary_text = "get the pixel dimensions of an image" - - def eval(self, image, evaluation: Evaluation): - "ImageDimensions[image_Image]" - return to_mathics_list(*image.dimensions(), elements_conversion_fn=Integer) - - -class ImageAspectRatio(Builtin): - """ - <url>:WMA link: - https://reference.wolfram.com/language/ref/ImageAspectRatio.html</url> - - <dl> - <dt>'ImageAspectRatio[$image$]' - <dd>gives the aspect ratio of $image$. - </dl> - - >> img = Import["ExampleData/lena.tif"]; - >> ImageAspectRatio[img] - = 1 - - >> ImageAspectRatio[Image[{{0, 1}, {1, 0}, {1, 1}}]] - = 3 / 2 - """ - - summary_text = "give the ratio of height to width of an image" - - def eval(self, image, evaluation: Evaluation): - "ImageAspectRatio[image_Image]" - dim = image.dimensions() - return Expression(SymbolDivide, Integer(dim[1]), Integer(dim[0])) - - -class ImageChannels(Builtin): - """ - <url>:WMA link: - https://reference.wolfram.com/language/ref/ImageChannels.html</url> - - <dl> - <dt>'ImageChannels[$image$]' - <dd>gives the number of channels in $image$. - </dl> - - >> ImageChannels[Image[{{0, 1}, {1, 0}}]] - = 1 - - >> img = Import["ExampleData/lena.tif"]; - >> ImageChannels[img] - = 3 - """ - - summary_text = "get number of channels present in the data for an image" - - def eval(self, image, evaluation: Evaluation): - "ImageChannels[image_Image]" - return Integer(image.channels()) - - -class ImageType(Builtin): - """ - <url> - :WMA link:https://reference.wolfram.com/language/ref/ImageType.html</url> - - <dl> - <dt>'ImageType[$image$]' - <dd>gives the interval storage type of $image$, e.g. "Real", "Bit32", or "Bit". - </dl> - - >> img = Import["ExampleData/lena.tif"]; - >> ImageType[img] - = Byte - - >> ImageType[Image[{{0, 1}, {1, 0}}]] - = Real - - X> ImageType[Binarize[img]] - = Bit - - """ - - summary_text = "type of values used for each pixel element in an image" - - def eval(self, image, evaluation: Evaluation): - "ImageType[image_Image]" - return String(image.storage_type()) - - -# complex operations - - class TextRecognize(Builtin): """ @@ -398,164 +276,6 @@ def eval(self, image, evaluation, options): return String(text) -class WordCloud(Builtin): - """ - <url> - :WMA link: - https://reference.wolfram.com/language/ref/WordCloud.html</url> - - <dl> - <dt>'WordCloud[{$word1$, $word2$, ...}]' - <dd>Gives a word cloud with the given list of words. - - <dt>'WordCloud[{$weight1$ -> $word1$, $weight2$ -> $word2$, ...}]' - <dd>Gives a word cloud with the words weighted using the given weights. - - <dt>'WordCloud[{$weight1$, $weight2$, ...} -> {$word1$, $word2$, ...}]' - <dd>Also gives a word cloud with the words weighted using the given weights. - - <dt>'WordCloud[{{$word1$, $weight1$}, {$word2$, $weight2$}, ...}]' - <dd>Gives a word cloud with the words weighted using the given weights. - </dl> - - >> WordCloud[StringSplit[Import["ExampleData/EinsteinSzilLetter.txt", CharacterEncoding->"UTF8"]]] - = -Image- - - >> WordCloud[Range[50] -> ToString /@ Range[50]] - = -Image- - """ - - # this is the palettable.colorbrewer.qualitative.Dark2_8 palette - default_colors = ( - (27, 158, 119), - (217, 95, 2), - (117, 112, 179), - (231, 41, 138), - (102, 166, 30), - (230, 171, 2), - (166, 118, 29), - (102, 102, 102), - ) - - options = { - "IgnoreCase": "True", - "ImageSize": "Automatic", - "MaxItems": "Automatic", - } - - requires = ("wordcloud",) - - summary_text = "show a word cloud from a list of words" - - def eval_words_weights(self, weights, words, evaluation, options): - "WordCloud[weights_List -> words_List, OptionsPattern[%(name)s]]" - if len(weights.elements) != len(words.elements): - return - - def weights_and_words(): - for weight, word in zip(weights.elements, words.elements): - yield weight.round_to_float(), word.get_string_value() - - return self._word_cloud(weights_and_words(), evaluation, options) - - def eval_words(self, words, evaluation, options): - "WordCloud[words_List, OptionsPattern[%(name)s]]" - - if not words: - return - elif isinstance(words.elements[0], String): - - def weights_and_words(): - for word in words.elements: - yield 1, word.get_string_value() - - else: - - def weights_and_words(): - for word in words.elements: - if len(word.elements) != 2: - raise ValueError - - head_name = word.get_head_name() - if head_name == "System`Rule": - weight, s = word.elements - elif head_name == "System`List": - s, weight = word.elements - else: - raise ValueError - - yield weight.round_to_float(), s.get_string_value() - - try: - return self._word_cloud(weights_and_words(), evaluation, options) - except ValueError: - return - - def _word_cloud(self, words, evaluation, options): - ignore_case = self.get_option(options, "IgnoreCase", evaluation) is Symbol( - "True" - ) - - freq = defaultdict(int) - for py_weight, py_word in words: - if py_word is None or py_weight is None: - return - key = py_word.lower() if ignore_case else py_word - freq[key] += py_weight - - max_items = self.get_option(options, "MaxItems", evaluation) - if isinstance(max_items, Integer): - py_max_items = max_items.get_int_value() - else: - py_max_items = 200 - - image_size = self.get_option(options, "ImageSize", evaluation) - if image_size is Symbol("Automatic"): - py_image_size = (800, 600) - elif ( - image_size.get_head_name() == "System`List" - and len(image_size.elements) == 2 - ): - py_image_size = [] - for element in image_size.elements: - if not isinstance(element, Integer): - return - py_image_size.append(element.get_int_value()) - elif isinstance(image_size, Integer): - size = image_size.get_int_value() - py_image_size = (size, size) - else: - return - - # inspired by http://minimaxir.com/2016/05/wordclouds/ - import random - - def color_func( - word, font_size, position, orientation, random_state=None, **kwargs - ): - return self.default_colors[random.randint(0, 7)] - - font_base_path = osp.join(osp.dirname(osp.abspath(__file__)), "..", "fonts") - - font_path = osp.realpath(font_base_path + "AmaticSC-Bold.ttf") - if not osp.exists(font_path): - font_path = None - - from wordcloud import WordCloud - - wc = WordCloud( - width=py_image_size[0], - height=py_image_size[1], - font_path=font_path, - max_font_size=300, - mode="RGB", - background_color="white", - max_words=py_max_items, - color_func=color_func, - random_state=42, - stopwords=set(), - ) - wc.generate_from_frequencies(freq) - - image = wc.to_image() - return Image(numpy.array(image), "RGB") +# TODO: +# Computer Vision outside TextRecognize +# Feature Detection routines other than EdgeDetect diff --git a/mathics/builtin/image/pixel.py b/mathics/builtin/image/pixel.py index 5d4fc22ac..68d1aa517 100644 --- a/mathics/builtin/image/pixel.py +++ b/mathics/builtin/image/pixel.py @@ -3,18 +3,12 @@ """ import numpy -from mathics.builtin.base import Builtin, String +from mathics.builtin.base import Builtin from mathics.core.atoms import Integer, MachineReal from mathics.core.convert.expression import to_mathics_list -from mathics.core.convert.python import from_python from mathics.core.evaluation import Evaluation from mathics.core.list import ListExpression -from mathics.eval.image import ( - numpy_to_matrix, - pixels_as_float, - pixels_as_ubyte, - pixels_as_uint, -) +from mathics.eval.image import pixels_as_float class PixelValue(Builtin): @@ -67,57 +61,6 @@ def eval(self, image, x, y, evaluation: Evaluation): return MachineReal(float(pixel)) -class ImageData(Builtin): - """ - - <url>:WMA link: - https://reference.wolfram.com/language/ref/ImageData.html</url> - - <dl> - <dt>'ImageData[$image$]' - <dd>gives a list of all color values of $image$ as a matrix. - - <dt>'ImageData[$image$, $stype$]' - <dd>gives a list of color values in type $stype$. - </dl> - - >> img = Image[{{0.2, 0.4}, {0.9, 0.6}, {0.5, 0.8}}]; - >> ImageData[img] - = {{0.2, 0.4}, {0.9, 0.6}, {0.5, 0.8}} - - >> ImageData[img, "Byte"] - = {{51, 102}, {229, 153}, {127, 204}} - - >> ImageData[Image[{{0, 1}, {1, 0}, {1, 1}}], "Bit"] - = {{0, 1}, {1, 0}, {1, 1}} - - #> ImageData[img, "Bytf"] - : Unsupported pixel format "Bytf". - = ImageData[-Image-, Bytf] - """ - - messages = {"pixelfmt": 'Unsupported pixel format "``".'} - - rules = {"ImageData[image_Image]": 'ImageData[image, "Real"]'} - summary_text = "the array of pixel values from an image" - - def eval(self, image, stype: String, evaluation: Evaluation): - "ImageData[image_Image, stype_String]" - pixels = image.pixels - stype = stype.value - if stype == "Real": - pixels = pixels_as_float(pixels) - elif stype == "Byte": - pixels = pixels_as_ubyte(pixels) - elif stype == "Bit16": - pixels = pixels_as_uint(pixels) - elif stype == "Bit": - pixels = pixels.astype(int) - else: - return evaluation.message("ImageData", "pixelfmt", stype) - return from_python(numpy_to_matrix(pixels)) - - class PixelValuePositions(Builtin): """ <url>:WMA link:https://reference.wolfram.com/language/ref/PixelValuePositions.html</url> @@ -167,3 +110,13 @@ def eval(self, image, val, d, evaluation: Evaluation): return ListExpression( *(to_mathics_list(*arg, elements_conversion_fn=Integer) for arg in result) ) + + +# TODO: +# ImageApply +# ImageApplyIndexed +# ImageScan +# ImageValue, +# ImageValuePositions +# ReplaceImageValue, +# ReplacePixelValue, diff --git a/mathics/builtin/image/properties.py b/mathics/builtin/image/properties.py new file mode 100644 index 000000000..bbb6b0b66 --- /dev/null +++ b/mathics/builtin/image/properties.py @@ -0,0 +1,173 @@ +""" +Image Properties +""" + +from mathics.builtin.base import Builtin, String +from mathics.core.atoms import Integer +from mathics.core.convert.expression import from_python, to_mathics_list +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.symbols import SymbolDivide +from mathics.eval.image import ( + numpy_to_matrix, + pixels_as_float, + pixels_as_ubyte, + pixels_as_uint, +) + + +class ImageAspectRatio(Builtin): + """ + <url>:WMA link: + https://reference.wolfram.com/language/ref/ImageAspectRatio.html</url> + + <dl> + <dt>'ImageAspectRatio[$image$]' + <dd>gives the aspect ratio of $image$. + </dl> + + >> img = Import["ExampleData/lena.tif"]; + >> ImageAspectRatio[img] + = 1 + + >> ImageAspectRatio[Image[{{0, 1}, {1, 0}, {1, 1}}]] + = 3 / 2 + """ + + summary_text = "give the ratio of height to width of an image" + + def eval(self, image, evaluation: Evaluation): + "ImageAspectRatio[image_Image]" + dim = image.dimensions() + return Expression(SymbolDivide, Integer(dim[1]), Integer(dim[0])) + + +class ImageChannels(Builtin): + """ + <url>:WMA link: + https://reference.wolfram.com/language/ref/ImageChannels.html</url> + + <dl> + <dt>'ImageChannels[$image$]' + <dd>gives the number of channels in $image$. + </dl> + + >> ImageChannels[Image[{{0, 1}, {1, 0}}]] + = 1 + + >> img = Import["ExampleData/lena.tif"]; + >> ImageChannels[img] + = 3 + """ + + summary_text = "get number of channels present in the data for an image" + + def eval(self, image, evaluation: Evaluation): + "ImageChannels[image_Image]" + return Integer(image.channels()) + + +class ImageData(Builtin): + """ + + <url>:WMA link: + https://reference.wolfram.com/language/ref/ImageData.html</url> + + <dl> + <dt>'ImageData[$image$]' + <dd>gives a list of all color values of $image$ as a matrix. + + <dt>'ImageData[$image$, $stype$]' + <dd>gives a list of color values in type $stype$. + </dl> + + >> img = Image[{{0.2, 0.4}, {0.9, 0.6}, {0.5, 0.8}}]; + >> ImageData[img] + = {{0.2, 0.4}, {0.9, 0.6}, {0.5, 0.8}} + + >> ImageData[img, "Byte"] + = {{51, 102}, {229, 153}, {127, 204}} + + >> ImageData[Image[{{0, 1}, {1, 0}, {1, 1}}], "Bit"] + = {{0, 1}, {1, 0}, {1, 1}} + + #> ImageData[img, "Bytf"] + : Unsupported pixel format "Bytf". + = ImageData[-Image-, Bytf] + """ + + messages = {"pixelfmt": 'Unsupported pixel format "``".'} + + rules = {"ImageData[image_Image]": 'ImageData[image, "Real"]'} + summary_text = "the array of pixel values from an image" + + def eval(self, image, stype: String, evaluation: Evaluation): + "ImageData[image_Image, stype_String]" + pixels = image.pixels + stype = stype.value + if stype == "Real": + pixels = pixels_as_float(pixels) + elif stype == "Byte": + pixels = pixels_as_ubyte(pixels) + elif stype == "Bit16": + pixels = pixels_as_uint(pixels) + elif stype == "Bit": + pixels = pixels.astype(int) + else: + return evaluation.message("ImageData", "pixelfmt", stype) + return from_python(numpy_to_matrix(pixels)) + + +class ImageDimensions(Builtin): + """ + <url> + :WMA link: + https://reference.wolfram.com/language/ref/ImageDimensions.html</url> + + <dl> + <dt>'ImageDimensions[$image$]' + <dd>Returns the dimensions {$width$, $height$} of $image$ in pixels. + </dl> + + >> lena = Import["ExampleData/lena.tif"]; + >> ImageDimensions[lena] + = {512, 512} + + >> ImageDimensions[RandomImage[1, {50, 70}]] + = {50, 70} + """ + + summary_text = "get the pixel dimensions of an image" + + def eval(self, image, evaluation: Evaluation): + "ImageDimensions[image_Image]" + return to_mathics_list(*image.dimensions(), elements_conversion_fn=Integer) + + +class ImageType(Builtin): + """ + <url> + :WMA link:https://reference.wolfram.com/language/ref/ImageType.html</url> + + <dl> + <dt>'ImageType[$image$]' + <dd>gives the interval storage type of $image$, e.g. "Real", "Bit32", or "Bit". + </dl> + + >> img = Import["ExampleData/lena.tif"]; + >> ImageType[img] + = Byte + + >> ImageType[Image[{{0, 1}, {1, 0}}]] + = Real + + X> ImageType[Binarize[img]] + = Bit + + """ + + summary_text = "type of values used for each pixel element in an image" + + def eval(self, image, evaluation: Evaluation): + "ImageType[image_Image]" + return String(image.storage_type()) diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py index 233060263..836687375 100644 --- a/mathics/doc/common_doc.py +++ b/mathics/doc/common_doc.py @@ -138,7 +138,7 @@ test_result_map = {} -def get_module_doc(module): +def get_module_doc(module: ModuleType): doc = module.__doc__ if doc is not None: doc = doc.strip() @@ -1138,7 +1138,7 @@ def __init__(self, module=None): part.is_appendix = True appendix.append(part) - # Builds the automatic documentation + # Builds the automatic Pymathics documentation builtin_part = DocPart(self, "Pymathics Modules", is_reference=True) title, text = get_module_doc(self.pymathicsmodule) chapter = DocChapter(builtin_part, title, XMLDoc(text, title)) From 6f0e6d4992f0b093deb5fa39ebeed749d24c5dbf Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Sat, 31 Dec 2022 11:06:24 -0300 Subject: [PATCH 120/121] fix extra quote in insets --- mathics/builtin/box/graphics.py | 8 ++++---- mathics/format/latex.py | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index d1d9a2eea..f36ea2620 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -993,10 +993,10 @@ def init( self.pos = pos self.opos = opos - if isinstance(self.content, String): - self.content = self.content.atom_to_boxes( - SymbolStandardForm, evaluation=self.graphics.evaluation - ) + # if isinstance(self.content, String): + # self.content = self.content.atom_to_boxes( + # SymbolStandardForm, evaluation=self.graphics.evaluation + # ) self.content_text = self.content.boxes_to_text( evaluation=self.graphics.evaluation ) diff --git a/mathics/format/latex.py b/mathics/format/latex.py index accb49d8c..46b3f9ea6 100644 --- a/mathics/format/latex.py +++ b/mathics/format/latex.py @@ -123,7 +123,6 @@ def render(format, string, in_text=False): # is required, to so get the standard WMA behaviour, # this option is set to False: # show_string_characters = False - if show_string_characters: return render(r"\text{``%s''}", text[1:-1], in_text=True) else: From 8062d4dea7765b723401cb69e443cd722b114831 Mon Sep 17 00:00:00 2001 From: mmatera <matera@fisica.unlp.edu.ar> Date: Sat, 31 Dec 2022 11:35:45 -0300 Subject: [PATCH 121/121] fix PIL.Image import --- mathics/builtin/image/base.py | 1 + mathics/eval/image.py | 1 + 2 files changed, 2 insertions(+) diff --git a/mathics/builtin/image/base.py b/mathics/builtin/image/base.py index f908bd1a1..3e4d68dc4 100644 --- a/mathics/builtin/image/base.py +++ b/mathics/builtin/image/base.py @@ -21,6 +21,7 @@ import numpy import PIL + import PIL.Image import PIL.ImageEnhance import PIL.ImageFilter import PIL.ImageOps diff --git a/mathics/eval/image.py b/mathics/eval/image.py index 293800577..3c3d48f3e 100644 --- a/mathics/eval/image.py +++ b/mathics/eval/image.py @@ -9,6 +9,7 @@ import numpy import PIL +import PIL.Image from mathics.builtin.base import String from mathics.core.atoms import Rational