diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..bcaad605f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,87 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+
+## Description
+
+
+
+## How to Reproduce
+
+
+
+## Output Given
+
+
+
+
+## Expected behavior
+
+
+
+## Your Environment
+
+
+
+## Workarounds
+
+
+
+## Priority
+
+
+
+## Additional context
+
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..c51ecfbc0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,21 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+*Note: If the feature is about adding or filling out an existing deficiency in the Mathics3 language, please file this as an [issue](https://github.com/Mathics3/mathics-core/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=).*
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/SYMBOLS_MANIFEST.txt b/SYMBOLS_MANIFEST.txt
index 6a4797982..861113891 100644
--- a/SYMBOLS_MANIFEST.txt
+++ b/SYMBOLS_MANIFEST.txt
@@ -845,6 +845,7 @@ System`NumberQ
System`NumberString
System`Numerator
System`NumericFunction
+System`NumericArray
System`NumericQ
System`O
System`Octahedron
diff --git a/mathics/builtin/binary/bytearray.py b/mathics/builtin/binary/bytearray.py
index ae3a4e947..d0c3b5975 100644
--- a/mathics/builtin/binary/bytearray.py
+++ b/mathics/builtin/binary/bytearray.py
@@ -1,18 +1,16 @@
# -*- coding: utf-8 -*-
"""
-ByteArrays
+Byte Arrays
"""
-from typing import Optional
-
-from mathics.core.atoms import ByteArray, Integer, String
+from mathics.core.atoms import ByteArrayAtom, Integer, String
from mathics.core.builtin import Builtin
from mathics.core.convert.expression import to_mathics_list
-from mathics.core.evaluation import Evaluation
-from mathics.core.list import ListExpression
+from mathics.core.expression import Expression
+from mathics.core.systemsymbols import SymbolByteArray, SymbolFailed
-class ByteArray_(Builtin):
+class ByteArray(Builtin):
r"""
:WMA link:
https://reference.wolfram.com/language/ref/ByteArray.html
@@ -37,55 +35,44 @@ class ByteArray_(Builtin):
>> ByteArray["ARkD"]
= ByteArray[<3>]
>> B=ByteArray["asy"]
- : The argument at position 1 in ByteArray[asy] should be a vector of unsigned byte values or a Base64-encoded string.
- = ByteArray[asy]
-
- A 'ByteArray" is a kind of Atom:
-
- >> AtomQ[ByteArray[{4, 2}]]
- = True
+ : The first argument in Bytearray[asy] should be a B64 encoded string or a vector of integers.
+ = $Failed
"""
messages = {
- "batd": "Elements in `1` are not unsigned byte values.",
- "lend": (
- "The argument at position 1 in ByteArray[`1`] should "
- "be a vector of unsigned byte values or a Base64-encoded string."
- ),
+ "aotd": "Elements in `1` are inconsistent with type Byte",
+ "lend": "The first argument in Bytearray[`1`] should "
+ + "be a B64 encoded string or a vector of integers.",
}
-
- name = "ByteArray"
summary_text = "array of bytes"
- def eval_str(self, string, evaluation: Evaluation) -> Optional[ByteArray]:
+ def eval_str(self, string, evaluation):
"ByteArray[string_String]"
try:
- atom = ByteArray(string.value)
- except TypeError:
+ atom = ByteArrayAtom(string.value)
+ except Exception:
evaluation.message("ByteArray", "lend", string)
- return None
- return atom
+ return SymbolFailed
+ return Expression(SymbolByteArray, atom)
- def eval_to_str(self, baa, evaluation: Evaluation):
- "ToString[baa_ByteArray]"
+ def eval_to_str(self, baa, evaluation):
+ "ToString[ByteArray[baa_ByteArrayAtom]]"
return String(f"ByteArray[<{len(baa.value)}>]")
- def eval_normal(self, baa, evaluation: Evaluation):
- "System`Normal[baa_ByteArray]"
+ def eval_normal(self, baa, evaluation):
+ "System`Normal[ByteArray[baa_ByteArrayAtom]]"
return to_mathics_list(*baa.value, elements_conversion_fn=Integer)
- def eval_list(self, values, evaluation) -> Optional[ByteArray]:
- "ByteArray[values_]"
- if not isinstance(values, ListExpression):
- evaluation.message("ByteArray", "lend", values)
- return None
-
+ def eval_list(self, values, evaluation):
+ "ByteArray[values_List]"
+ if not values.has_form("List", None):
+ return
try:
- ba = ByteArray(bytearray([b.value for b in values.elements]))
+ ba = bytearray([b.get_int_value() for b in values.elements])
except Exception:
- evaluation.message("ByteArray", "batd", values)
- return None
- return ba
+ evaluation.message("ByteArray", "aotd", values)
+ return
+ return Expression(SymbolByteArray, ByteArrayAtom(ba))
# TODO: BaseEncode, BaseDecode, ByteArrayQ, ByteArrayToString, StringToByteArray, ImportByteArray, ExportByteArray
diff --git a/mathics/builtin/compilation.py b/mathics/builtin/compilation.py
index 09b56fa3e..d8af4368f 100644
--- a/mathics/builtin/compilation.py
+++ b/mathics/builtin/compilation.py
@@ -25,7 +25,7 @@
from mathics.core.element import ImmutableValueMixin
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
-from mathics.core.keycomparable import LITERAL_EXPRESSION_ELT_ORDER
+from mathics.core.keycomparable import LITERAL_EXPRESSION_SORT_KEY
from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue
from mathics.core.systemsymbols import SymbolCompiledFunction
@@ -146,7 +146,7 @@ def element_order(self) -> tuple:
lexicographically.
"""
- return (LITERAL_EXPRESSION_ELT_ORDER, hex(id(self)))
+ return (LITERAL_EXPRESSION_SORT_KEY, hex(id(self)))
@property
def pattern_precedence(self) -> tuple:
diff --git a/mathics/builtin/exp_structure/size_and_sig.py b/mathics/builtin/exp_structure/size_and_sig.py
index b06cede7f..0d6babb9f 100644
--- a/mathics/builtin/exp_structure/size_and_sig.py
+++ b/mathics/builtin/exp_structure/size_and_sig.py
@@ -5,7 +5,7 @@
import platform
import zlib
-from mathics.core.atoms import ByteArray, Integer, String
+from mathics.core.atoms import ByteArrayAtom, Integer, String
from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED
from mathics.core.builtin import Builtin
from mathics.core.evaluation import Evaluation
@@ -132,7 +132,7 @@ def compute(user_hash, py_hashtype, py_format):
if py_format == "DecimalString":
return String(str(res))
elif py_format == "ByteArray":
- return Expression(SymbolByteArray, ByteArray(res))
+ return Expression(SymbolByteArray, ByteArrayAtom(res))
return Integer(res)
def eval(self, expr, hashtype: String, outformat: String, evaluation: Evaluation):
diff --git a/mathics/builtin/files_io/importexport.py b/mathics/builtin/files_io/importexport.py
index 6db2700e7..9831814b7 100644
--- a/mathics/builtin/files_io/importexport.py
+++ b/mathics/builtin/files_io/importexport.py
@@ -26,7 +26,7 @@
from urllib.error import HTTPError, URLError
from mathics.builtin.pymimesniffer import magic
-from mathics.core.atoms import ByteArray
+from mathics.core.atoms import ByteArrayAtom
from mathics.core.attributes import A_NO_ATTRIBUTES, A_PROTECTED, A_READ_PROTECTED
from mathics.core.builtin import Builtin, Integer, Predefined, String, get_option
from mathics.core.convert.expression import to_mathics_list
@@ -2005,7 +2005,7 @@ def eval_elements(self, expr, elems, evaluation: Evaluation, **options):
evaluation.predetermined_out = current_predetermined_out
return SymbolFailed
if is_binary:
- res = Expression(SymbolByteArray, ByteArray(res))
+ res = Expression(SymbolByteArray, ByteArrayAtom(res))
else:
res = String(str(res))
elif function_channels == ListExpression(String("Streams")):
@@ -2030,7 +2030,9 @@ def eval_elements(self, expr, elems, evaluation: Evaluation, **options):
res = exporter_function.evaluate(evaluation)
if res is SymbolNull:
if is_binary:
- res = Expression(SymbolByteArray, ByteArray(pystream.getvalue()))
+ res = Expression(
+ SymbolByteArray, ByteArrayAtom(pystream.getvalue())
+ )
else:
res = String(str(pystream.getvalue()))
else:
diff --git a/mathics/builtin/image/base.py b/mathics/builtin/image/base.py
index 2d8ce7756..cca3c8a95 100644
--- a/mathics/builtin/image/base.py
+++ b/mathics/builtin/image/base.py
@@ -12,7 +12,7 @@
from mathics.core.builtin import AtomBuiltin, String
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
-from mathics.core.keycomparable import IMAGE_EXPRESSION_ELT_ORDER
+from mathics.core.keycomparable import IMAGE_EXPRESSION_SORT_KEY
from mathics.core.list import ListExpression
from mathics.core.systemsymbols import SymbolImage, SymbolRule
from mathics.eval.image import image_pixels, pixels_as_float, pixels_as_ubyte
@@ -122,7 +122,7 @@ def element_order(self) -> tuple:
# and adding two extra fields: the length in the 5th position,
# and a hash in the 6th place.
return (
- IMAGE_EXPRESSION_ELT_ORDER,
+ IMAGE_EXPRESSION_SORT_KEY,
SymbolImage,
len(self.pixels),
tuple(),
diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py
index c5b4eb6cc..f35c4c979 100644
--- a/mathics/builtin/layout.py
+++ b/mathics/builtin/layout.py
@@ -60,7 +60,7 @@ class Format(Builtin):
Raw objects cannot be formatted:
>> Format[3] = "three";
- : Tag Integer in 3 is Protected.
+ : Cannot assign to raw object 3.
Format types must be symbols:
>> Format[r, a + b] = "r";
diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py
index 73aa8fb4d..bef62e321 100644
--- a/mathics/builtin/list/constructing.py
+++ b/mathics/builtin/list/constructing.py
@@ -13,10 +13,11 @@
from typing import Optional, Tuple
from mathics.builtin.box.layout import RowBox
-from mathics.core.atoms import ByteArray, Integer, Integer1, is_integer_rational_or_real
+from mathics.core.atoms import Integer, Integer1, is_integer_rational_or_real, NumericArray
from mathics.core.attributes import A_HOLD_FIRST, A_LISTABLE, A_LOCKED, A_PROTECTED
from mathics.core.builtin import BasePattern, Builtin, IterationFunction
from mathics.core.convert.expression import to_expression
+from mathics.core.convert.python import from_python
from mathics.core.convert.sympy import from_sympy
from mathics.core.element import ElementsProperties
from mathics.core.evaluation import Evaluation
@@ -197,9 +198,9 @@ class Normal(Builtin):
def eval_general(self, expr: Expression, evaluation: Evaluation):
"Normal[expr_]"
+ if isinstance(expr, NumericArray):
+ return from_python(expr.to_python())
if isinstance(expr, Atom):
- if isinstance(expr, ByteArray):
- return ListExpression(*expr.items)
return expr
if expr.has_form("RootSum", 2):
return from_sympy(expr.to_sympy().doit(roots=True))
diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py
index 89c061144..ce355d0ab 100644
--- a/mathics/builtin/list/eol.py
+++ b/mathics/builtin/list/eol.py
@@ -10,16 +10,7 @@
from itertools import chain
from mathics.builtin.box.layout import RowBox
-from mathics.core.atoms import (
- ByteArray,
- Integer,
- Integer0,
- Integer1,
- Integer2,
- Integer3,
- Integer4,
- String,
-)
+from mathics.core.atoms import Integer, Integer0, Integer1, Integer3, Integer4, String
from mathics.core.attributes import (
A_HOLD_FIRST,
A_HOLD_REST,
@@ -49,10 +40,8 @@
SymbolByteArray,
SymbolDrop,
SymbolFailed,
- SymbolFirst,
SymbolInfinity,
SymbolKey,
- SymbolLast,
SymbolMakeBoxes,
SymbolMissing,
SymbolSelect,
@@ -686,6 +675,7 @@ class First(Builtin):
attributes = A_HOLD_REST | A_PROTECTED
messages = {
+ "argt": "First called with `1` arguments; 1 or 2 arguments are expected.",
"nofirst": "`1` has zero length and no first element.",
}
summary_text = "first element of a list or expression"
@@ -695,23 +685,14 @@ def eval(self, expr, evaluation: Evaluation, expression: Expression):
"expression: First[expr__]"
if isinstance(expr, Atom):
- if not hasattr(expr, "items"):
- evaluation.message("First", "normal", Integer1, expression)
- return
- expr_len = len(expr.items)
- else:
- expr_len = len(expr.elements)
+ evaluation.message("First", "normal", Integer1, expression)
+ return
+ expr_len = len(expr.elements)
if expr_len == 0:
evaluation.message("First", "nofirst", expr)
return
-
- if isinstance(expr, ByteArray):
- return expr.items[0]
-
if expr_len > 2 and expr.head is SymbolSequence:
- evaluation.message(
- "First", "argt", SymbolFirst, Integer(expr_len), Integer1, Integer2
- )
+ evaluation.message("First", "argt", expr_len)
return
first_elem = expr.elements[0]
@@ -968,6 +949,7 @@ class Last(Builtin):
attributes = A_HOLD_REST | A_PROTECTED
messages = {
+ "argt": "Last called with `1` arguments; 1 or 2 arguments are expected.",
"nolast": "`1` has zero length and no last element.",
}
summary_text = "last element of a list or expression"
@@ -977,24 +959,14 @@ def eval(self, expression: Expression, expr, evaluation: Evaluation):
"expression: Last[expr__]"
if isinstance(expr, Atom):
- if not hasattr(expr, "items"):
- evaluation.message("First", "normal", Integer1, expression)
- return
- expr_len = len(expr.items)
- else:
- expr_len = len(expr.elements)
+ evaluation.message("Last", "normal", Integer1, expression)
+ return
+ expr_len = len(expr.elements)
if expr_len == 0:
evaluation.message("Last", "nolast", expr)
return
-
- if isinstance(expr, ByteArray):
- # ByteArray or NumericArray, ...
- return expr.items[-1]
-
if expr_len > 2 and expr.head is SymbolSequence:
- evaluation.message(
- "Last", "argt", SymbolLast, Integer(expr_len), Integer1, Integer2
- )
+ evaluation.message("Last", "argt", expr_len)
return
return expr.elements[-1]
@@ -1188,6 +1160,7 @@ def eval(self, list, i, evaluation):
indices = i.get_sequence()
# How to deal with ByteArrays
if list.get_head() is SymbolByteArray:
+ list = list.evaluate(evaluation)
if len(indices) > 1:
print(
"Part::partd1: Depth of object ByteArray[<3>] "
@@ -1199,18 +1172,19 @@ def eval(self, list, i, evaluation):
idx = idx.value
if idx == 0:
return SymbolByteArray
- n = len(list.value)
+ data = list.elements[0].value
+ lendata = len(data)
if idx < 0:
- idx = n - idx
+ idx = data - idx
if idx < 0:
evaluation.message("Part", "partw", i, list)
return
else:
idx = idx - 1
- if idx > n:
+ if idx > lendata:
evaluation.message("Part", "partw", i, list)
return
- return Integer(list[idx])
+ return Integer(data[idx])
if idx is Symbol("System`All"):
return list
# TODO: handling ranges and lists...
diff --git a/mathics/builtin/numericarray.py b/mathics/builtin/numericarray.py
new file mode 100644
index 000000000..a2ff8bab5
--- /dev/null
+++ b/mathics/builtin/numericarray.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+"""Rules for working with NumericArray atoms."""
+
+from typing import Optional, Tuple
+
+try: # pragma: no cover - numpy is optional at runtime
+ import numpy
+except ImportError: # pragma: no cover - handled via requires attribute
+ numpy = None
+
+from mathics.core.atoms import NumericArray, NUMERIC_ARRAY_TYPE_MAP, String
+from mathics.core.builtin import Builtin
+from mathics.core.convert.python import from_python
+from mathics.core.symbols import Symbol, strip_context
+from mathics.core.systemsymbols import SymbolAutomatic, SymbolFailed, SymbolNumericArray
+
+
+# class name modeled on Complex_ to avoid collision with NumericArray atom
+class NumericArray_(Builtin):
+
+ summary_text = "construct NumericArray"
+ name = "NumericArray"
+ rules = {
+ "NumericArray[list_List]": "NumericArray[list, Automatic]"
+ }
+ messages = {
+ "type": "The type specification `1` is not supported in NumericArray.",
+ }
+
+ # rule to convert NumericArray[...nested list...] expression to NumericArray atom
+ def eval_list(self, data, typespec, evaluation):
+ "System`NumericArray[data_List, typespec_]"
+
+ # get a string key from the typespec
+ if isinstance(typespec, Symbol):
+ key = strip_context(typespec.get_name())
+ elif isinstance(typespec, String):
+ key = typespec.value
+ else:
+ evaluation.message("NumericArray", "type", typespec)
+ return SymbolFailed
+
+ # compute numpy dtype from key
+ if key == "Automatic":
+ dtype = None
+ else:
+ dtype = NUMERIC_ARRAY_TYPE_MAP.get(key, None)
+ if not dtype:
+ evaluation.message("NumericArray", "type", typespec)
+ return SymbolFailed
+
+ # compute array from data and dtype and wrap it in a NumericArray atom
+ python_value = data.to_python()
+ array = numpy.array(python_value, dtype=dtype)
+ atom = NumericArray(array, dtype)
+
+ return atom
+
+ # this doesn't work
+ # instead see Normal builtin in mathics/builtin/list/constructing.py
+ #def eval_normal(self, array, evaluation):
+ # "System`Normal[array_NumericArray]"
+ # return from_python(array.value.tolist())
diff --git a/mathics/builtin/statistics/orderstats.py b/mathics/builtin/statistics/orderstats.py
index ff75c6a0c..12b0314f7 100644
--- a/mathics/builtin/statistics/orderstats.py
+++ b/mathics/builtin/statistics/orderstats.py
@@ -300,32 +300,6 @@ class ReverseSort(Builtin):
}
-# FIXME: there might be a bug in sorting...
-#
-# Sort[{
-# "a","b", 1,
-# ByteArray[{1,2,4,1}],
-# 2, 1.2, I, 2I-3, A,
-# a+b, a*b, a+1, a*2, b^3, 2/3,
-# A[x], F[2], F[x], F[x_], F[x___], F[x,t], F[x__],
-# Condition[A,b>2], Pattern[expr, A]
-# }]
-#
-# should be:
-#
-# {-3 + 2*I, I, 2/3, 1, 1.2, 2,
-# "a", "b", 2*a,
-# 1 + a, A, a*b, b^3, a + b,
-# A[x], A /; b > 2,
-# F[2], F[x], F[x_], F[x___], F[x__], F[x, t],
-# ByteArray["AQIEAQ=="], expr:A}
-#
-# But this is too complicated a case to run as a test. It needs
-# to be isolated. Break this down to smaller pieces,
-# and also use Order[] to check smaller components.
-# The problem might also be in boxing-order output.
-
-
class Sort(Builtin):
"""
:WMA link:https://reference.wolfram.com/language/ref/Sort.html
@@ -342,7 +316,7 @@ class Sort(Builtin):
>> Sort[{4, 1.0, a, 3+I}]
= {1., 3 + I, 4, a}
- Sort uses 'Order' to determine ordering by default.
+ Sort uses 'OrderedQ' to determine ordering by default.
You can sort patterns according to their precedence using 'PatternsOrderedQ':
>> Sort[{items___, item_, OptionsPattern[], item_symbol, item_?test}, PatternsOrderedQ]
= {item_symbol, item_ ? test, item_, items___, OptionsPattern[]}
diff --git a/mathics/core/assignment.py b/mathics/core/assignment.py
index adbf8123b..ae12aa99d 100644
--- a/mathics/core/assignment.py
+++ b/mathics/core/assignment.py
@@ -102,10 +102,10 @@ def get_symbol_values(
A list of rules. None if `symbol` is not a Symbol.
"""
- if not isinstance(symbol, Symbol):
+ name = symbol.get_name()
+ if not name:
evaluation.message(func_name, "sym", symbol, 1)
return None
- name = symbol.get_name()
definitions = evaluation.definitions
try:
definition = (
diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py
index 17e5062ce..69cab6be2 100644
--- a/mathics/core/atoms.py
+++ b/mathics/core/atoms.py
@@ -10,11 +10,15 @@
import sympy
from sympy.core import numbers as sympy_numbers
+try: # pragma: no cover - optional dependency handled at runtime
+ import numpy
+except ImportError: # pragma: no cover - numpy is optional at import time
+ numpy = None
+
from mathics.core.element import BoxElementMixin, ImmutableValueMixin
from mathics.core.keycomparable import (
- BASIC_ATOM_BYTEARRAY_ELT_ORDER,
- BASIC_ATOM_NUMBER_ELT_ORDER,
- BASIC_ATOM_STRING_ELT_ORDER,
+ BASIC_ATOM_NUMBER_SORT_KEY,
+ BASIC_ATOM_STRING_OR_BYTEARRAY_SORT_KEY,
)
from mathics.core.number import (
FP_MANTISA_BINARY_DIGITS,
@@ -91,7 +95,7 @@ def element_order(self) -> tuple:
of an expression. The tuple is ultimately compared lexicographically.
"""
return (
- BASIC_ATOM_NUMBER_ELT_ORDER,
+ BASIC_ATOM_NUMBER_SORT_KEY,
self.value,
0,
1,
@@ -640,51 +644,30 @@ def to_sympy(self, *args, **kwargs):
return self.value
-class ByteArray(Atom, ImmutableValueMixin):
- _value: Union[bytes, bytearray]
-
- # Items is analogous to "elements" in Lists.
- # However the name is different because there is a concern
- # having these be distinct names may catch mistakes in coding
- # where an expanded or Normal[]'d value is used when it should
- # not be used.
- _items: Optional[tuple] = None
-
- class_head_name = "System`ByteArray"
- hash: int
+class ByteArrayAtom(Atom, ImmutableValueMixin):
+ value: Union[bytes, bytearray]
+ class_head_name = "System`ByteArrayAtom"
- # We use __new__ here to ensure that two ByteArray's that have the same value
+ # 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 isinstance(value, (bytes, bytearray)):
- self._value = value
- elif isinstance(value, list):
- self._value = bytearray(value)
- elif isinstance(value, str):
- try:
- self._value = base64.b64decode(value)
- except Exception as e:
- raise TypeError(f"base64 string decode failed: {e}")
+ if type(value) in (bytes, bytearray):
+ self.value = value
+ elif type(value) is list:
+ self.value = bytearray(list)
+ elif type(value) is str:
+ self.value = base64.b64decode(value)
else:
- raise TypeError("value does not belongs to a valid type")
+ raise Exception("value does not belongs to a valid type")
- self.hash = hash(("ByteArray", str(self.value)))
+ self.hash = hash(("ByteArrayAtom", str(self.value)))
return self
- def __getitem__(self, index: int) -> int:
- """
- Support List index lookup without having to expand the entire bytearray into a Mathics3 list.
- """
- return self.value[index]
-
- def __getnewargs__(self):
- return (self.value,)
-
- def __hash__(self) -> int:
+ def __hash__(self):
return self.hash
def __str__(self) -> str:
@@ -697,24 +680,16 @@ def __str__(self) -> str:
# 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":
- return String(f"ByteArray[<{len(self.value)}>]")
+ res = String(f"<{len(self.value)}>")
+ return res
- def do_copy(self) -> "ByteArray":
- return ByteArray(self.value)
+ def do_copy(self) -> "ByteArrayAtom":
+ return ByteArrayAtom(self.value)
def default_format(self, evaluation, form) -> str:
value = self.value
return '"' + value.__str__() + '"'
- @property
- def items(self) -> Tuple[Integer, ...]:
- """
- Return a tuple value of Mathics3 Integers for each element of the ByteArray.
- """
- if self._items is None:
- self._items = tuple([Integer(i) for i in self.value])
- return self._items
-
@property
def element_order(self) -> tuple:
"""
@@ -722,9 +697,8 @@ def element_order(self) -> tuple:
of an expression. The tuple is ultimately compared lexicographically.
"""
return (
- BASIC_ATOM_BYTEARRAY_ELT_ORDER,
+ BASIC_ATOM_STRING_OR_BYTEARRAY_SORT_KEY,
self.value,
- "utf-8",
0,
1,
)
@@ -739,16 +713,16 @@ def pattern_precedence(self) -> tuple:
@property
def is_literal(self) -> bool:
- """For a ByteArray, the value can't change and has a Python representation,
+ """For an ByteArrayAtom, 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 sameQ(self, rhs) -> bool:
- """Mathics3 SameQ"""
+ """Mathics SameQ"""
# FIX: check
- if isinstance(rhs, ByteArray):
+ if isinstance(rhs, ByteArrayAtom):
return self.value == rhs.value
return False
@@ -765,17 +739,12 @@ def to_python(self, *args, **kwargs) -> Union[bytes, bytearray]:
return self.value
def user_hash(self, update):
- """
- returned untampered hash value.
-
- hashing a String is the one case where the user gets the untampered
+ # hashing a String is the one case where the user gets the untampered
# hash value of the string's text. this corresponds to MMA behavior.
- """
update(self.value)
- @property
- def value(self) -> Union[bytes, bytearray]:
- return self._value
+ def __getnewargs__(self):
+ return (self.value,)
class Complex(Number[Tuple[Number[T], Number[T], Optional[int]]]):
@@ -887,7 +856,7 @@ def element_order(self) -> tuple:
of an expression. The tuple is ultimately compared lexicographically.
"""
return (
- BASIC_ATOM_NUMBER_ELT_ORDER,
+ BASIC_ATOM_NUMBER_SORT_KEY,
self.real.element_order[1],
self.imag.element_order[1],
1,
@@ -1049,7 +1018,7 @@ def element_order(self) -> tuple:
"""
# HACK: otherwise "Bus error" when comparing 1==1.
return (
- BASIC_ATOM_NUMBER_ELT_ORDER,
+ BASIC_ATOM_NUMBER_SORT_KEY,
sympy.Float(self.value),
0,
1,
@@ -1098,10 +1067,117 @@ def is_zero(self) -> bool:
}
+#
+# NumericArray
+#
+
+if numpy is not None:
+ NUMERIC_ARRAY_TYPE_MAP = {
+ "UnsignedInteger8": numpy.dtype("uint8"),
+ "UnsignedInteger16": numpy.dtype("uint16"),
+ "UnsignedInteger32": numpy.dtype("uint32"),
+ "UnsignedInteger64": numpy.dtype("uint64"),
+ "Integer8": numpy.dtype("int8"),
+ "Integer16": numpy.dtype("int16"),
+ "Integer32": numpy.dtype("int32"),
+ "Integer64": numpy.dtype("int64"),
+ "Real32": numpy.dtype("float32"),
+ "Real64": numpy.dtype("float64"),
+ "ComplexReal32": numpy.dtype("complex64"),
+ "ComplexReal64": numpy.dtype("complex128"),
+ }
+ NUMERIC_ARRAY_DTYPE_TO_NAME = {
+ dtype: name for name, dtype in NUMERIC_ARRAY_TYPE_MAP.items()
+ }
+else: # pragma: no cover - executed only when numpy is absent
+ NUMERIC_ARRAY_TYPE_MAP = {}
+ NUMERIC_ARRAY_DTYPE_TO_NAME = {}
+
+
+# TODO: would it be useful to follow the example of Complex and parameterize by type?
+# would that be array.dtype or the MMA type from the map above?
+class NumericArray(Atom, ImmutableValueMixin):
+ """
+ NumericArray provides compact storage and efficient access for machine-precision numeric arrays,
+ backed by NumPy arrays.
+ """
+
+ class_head_name = "NumericArray"
+
+ def __init__(self, value, dtype=None):
+ if numpy is None:
+ raise ImportError("numpy is required for NumericArray")
+
+ # compute value
+ if not isinstance(value, numpy.ndarray):
+ value = numpy.asarray(value, dtype=dtype)
+ elif dtype is not None:
+ value = value.astype(dtype)
+ self.value = value
+
+ # check type
+ self._type_name = NUMERIC_ARRAY_DTYPE_TO_NAME.get(self.value.dtype, None)
+ if not self._type_name:
+ allowed = ", ".join(str(dtype) for dtype in NUMERIC_ARRAY_TYPE_MAP.values())
+ message = f"Argument 'value' must be one of {allowed}; is {str(self.value.dtype)}."
+ raise ValueError(message)
+
+ # summary and hash
+ self._summary = (self._type_name, self.value.shape, self.value.tobytes())
+ shape_string = "×".join(str(dim) for dim in self.value.shape) or "0"
+ self._summary_string = f"{self._type_name}, {shape_string}"
+ self._hash = None
+
+ # TODO: this is potentially expensive - what if we left it unimplemented? is hashing a numpy array reasonable?
+ # TODO: or maybe make self._summary included only some of the bytes, since it's just a hash?
+ def __hash__(self):
+ if not self._hash:
+ print("HASHING NUMERICARRAY")
+ self._hash = hash(("NumericArray", self._summary))
+ return self._hash
+
+ def __str__(self) -> str:
+ return f"NumericArray[{self._summary_string}]"
+
+ def atom_to_boxes(self, f, evaluation):
+ return String(f"<{self._summary_string}>")
+
+ def do_copy(self) -> "NumericArray":
+ return NumericArray(self.value.copy())
+
+ def default_format(self, evaluation, form) -> str:
+ return f"NumericArray[<{self._summary_string}>]"
+
+ @property
+ def element_order(self) -> tuple:
+ return (BASIC_ATOM_STRING_OR_BYTEARRAY_SORT_KEY, *self._summary)
+
+ @property
+ def pattern_precedence(self) -> tuple:
+ return super().pattern_precedence
+
+ def sameQ(self, rhs) -> bool:
+ return isinstance(rhs, NumericArray) and numpy.array_equal(self.value, rhs.value)
+
+ def to_sympy(self, **kwargs):
+ return None
+
+ # TODO: note that this returns a simple python list (of lists),
+ # not the numpy array - ok?
+ def to_python(self, *args, **kwargs):
+ return self.value.tolist()
+
+ # TODO: what is this? is it right?
+ def user_hash(self, update):
+ update(self._summary[2])
+
+ def __getnewargs__(self):
+ return (self.value, self.value.dtype)
+
+
class String(Atom, BoxElementMixin):
value: str
class_head_name = "System`String"
- hash: int
def __new__(cls, value):
self = super().__new__(cls)
@@ -1111,7 +1187,7 @@ def __new__(cls, value):
self.hash = hash(("String", self.value))
return self
- def __hash__(self) -> int:
+ def __hash__(self):
return self.hash
def __str__(self) -> str:
@@ -1140,7 +1216,7 @@ def element_order(self) -> tuple:
of an expression. The tuple is ultimately compared lexicographically.
"""
return (
- BASIC_ATOM_STRING_ELT_ORDER,
+ BASIC_ATOM_STRING_OR_BYTEARRAY_SORT_KEY,
self.value,
0,
1,
diff --git a/mathics/core/builtin.py b/mathics/core/builtin.py
index 3822b1013..e91b50c21 100644
--- a/mathics/core/builtin.py
+++ b/mathics/core/builtin.py
@@ -444,7 +444,7 @@ def contextify_form_name(f):
# for Sqrt[a, b] (one argument expected) or Subtract[a] (two
# arguments expected) It assumes each builtin defines
# "expected_args" for the correct number of arguments to give.
- # See class mathics.builtin.arithfns.basic.Sqrt for how to set up.
+ # See class mathics.builtins.basic.Sqrt for how to set up.
def generic_argument_error(self, invalid, evaluation: Evaluation):
"%(name)s[invalid___]"
diff --git a/mathics/core/convert/python.py b/mathics/core/convert/python.py
index 70340f3a0..c61941edd 100644
--- a/mathics/core/convert/python.py
+++ b/mathics/core/convert/python.py
@@ -5,7 +5,12 @@
from typing import Any
-from mathics.core.atoms import Complex, Integer, Rational, Real, String
+try: # pragma: no cover - numpy is optional
+ import numpy
+except ImportError: # pragma: no cover - optional dependency missing
+ numpy = None
+
+from mathics.core.atoms import Complex, Integer, NumericArray, Rational, Real, String
from mathics.core.number import get_type
from mathics.core.symbols import (
BaseElement,
@@ -113,5 +118,7 @@ def from_python(arg: Any) -> BaseElement:
from mathics.builtin.binary.bytearray import ByteArray
return Expression(SymbolByteArray, ByteArray(arg))
+ elif numpy is not None and isinstance(arg, numpy.ndarray):
+ return NumericArray(arg)
else:
raise NotImplementedError
diff --git a/mathics/core/element.py b/mathics/core/element.py
index fb2ef7a2a..f087ad2e5 100644
--- a/mathics/core/element.py
+++ b/mathics/core/element.py
@@ -77,9 +77,6 @@ class ElementsProperties:
# this True elements are not sorted can cause evaluation differences.
is_ordered: bool = False
- # Uniform expressions have all their elements with the same Head.
- is_uniform: bool = False
-
class ImmutableValueMixin:
@property
diff --git a/mathics/core/expression.py b/mathics/core/expression.py
index 41883e650..2a6801532 100644
--- a/mathics/core/expression.py
+++ b/mathics/core/expression.py
@@ -40,10 +40,10 @@
from mathics.core.evaluation import Evaluation
from mathics.core.interrupt import ReturnInterrupt
from mathics.core.keycomparable import (
- BASIC_EXPRESSION_ELT_ORDER,
- BASIC_NUMERIC_EXPRESSION_ELT_ORDER,
- GENERAL_EXPRESSION_ELT_ORDER,
- GENERAL_NUMERIC_EXPRESSION_ELT_ORDER,
+ BASIC_EXPRESSION_SORT_KEY,
+ BASIC_NUMERIC_EXPRESSION_SORT_KEY,
+ GENERAL_EXPRESSION_SORT_KEY,
+ GENERAL_NUMERIC_EXPRESSION_SORT_KEY,
Monomial,
)
from mathics.core.structure import LinkedStructure
@@ -355,25 +355,15 @@ def _build_elements_properties(self):
"""
# All of the properties start out optimistic (True) and are reset when that proves wrong.
- self.elements_properties = ElementsProperties(True, True, True, True)
+ self.elements_properties = ElementsProperties(True, True, True)
last_element = None
values = []
- last_lookup_name = ""
- uniform = True
for element in self._elements:
# Test for the literalness, and the three properties mentioned above
if not element.is_literal:
self.elements_properties.elements_fully_evaluated = False
- if uniform:
- lookup_name = element.get_lookup_name()
- if last_lookup_name:
- if lookup_name != last_lookup_name:
- uniform = self.elements_properties.is_uniform = False
- else:
- last_lookup_name = lookup_name
-
if isinstance(element, Expression):
# "self" can't be flat.
self.elements_properties.is_flat = False
@@ -897,7 +887,7 @@ def element_order(self) -> tuple:
of an expression. The tuple is ultimately compared lexicographically.
"""
"""
- General element order key structure:
+ General sort key structure:
0: 1/2: Numeric / General Expression
1: 2/3 Special arithmetic (Times / Power) / General Expression
2: Element: Head
@@ -932,9 +922,9 @@ def element_order(self) -> tuple:
if exps:
return (
(
- BASIC_NUMERIC_EXPRESSION_ELT_ORDER
+ BASIC_NUMERIC_EXPRESSION_SORT_KEY
if self.is_numeric()
- else BASIC_EXPRESSION_ELT_ORDER
+ else BASIC_EXPRESSION_SORT_KEY
),
Monomial(exps),
1,
@@ -945,9 +935,9 @@ def element_order(self) -> tuple:
else:
return (
(
- GENERAL_NUMERIC_EXPRESSION_ELT_ORDER
+ GENERAL_NUMERIC_EXPRESSION_SORT_KEY
if self.is_numeric()
- else GENERAL_EXPRESSION_ELT_ORDER
+ else GENERAL_EXPRESSION_SORT_KEY
),
head,
len(self._elements),
@@ -1306,7 +1296,6 @@ def flatten_callback(new_elements, old):
else:
return threaded, True
- elements_properties = new.elements_properties
# Step 6:
# Look at the rules associated with:
# 1. the upvalues of each element
@@ -1346,17 +1335,15 @@ def flatten_callback(new_elements, old):
def rules():
rules_names = set()
if not A_HOLD_ALL_COMPLETE & attributes:
- sample_elements = (
- (elements[0],)
- if elements and elements_properties.is_uniform
- else elements
- )
- for element in sample_elements:
+ for element in elements:
+ if not isinstance(element, EvalMixin):
+ continue
name = element.get_lookup_name()
- if name and name not in rules_names:
- rules_names.add(name)
- for rule in evaluation.definitions.get_upvalues(name):
- yield rule
+ if len(name) > 0: # only lookup rules if this is a symbol
+ if name not in rules_names:
+ rules_names.add(name)
+ for rule in evaluation.definitions.get_upvalues(name):
+ yield rule
lookup_name = new.get_lookup_name()
if lookup_name == new.get_head_name():
for rule in evaluation.definitions.get_downvalues(lookup_name):
diff --git a/mathics/core/keycomparable.py b/mathics/core/keycomparable.py
index c8bd5a771..f07a4fce1 100644
--- a/mathics/core/keycomparable.py
+++ b/mathics/core/keycomparable.py
@@ -5,39 +5,26 @@
class KeyComparable:
- """Mathics3/WL defines a "canonical ordering" between elements,
- even where they would not otherwise be comparable either
- numerically, lexicographically, etc.
-
- For example, there is an ordering defined between a Function
- call with some number of arguments, and a ByteArray Atom.
-
- Symbols that have an "Orderless" attribute use this canonic
- ordering in arranging elements.
+ """
- Also, there are builtin-functions like "Order[]", "OrderQ[]", and
- "Ordering[]", that use this canonic ordering in their computation.
+ Some Mathics3/WL Symbols have an "OrderLess" attribute
+ which is used in the evaluation process to arrange items in a list.
- To support the WL-predefined canonic order elements types, we need
- a way to compare arbitrary elements. That is what this class is for.
+ To do that, we need a way to compare Symbols, and that is what
+ this class is for.
- This class adds the boilerplate Python comparison operators, like
- __lt__, __eq__, etc. that Python provides for comparing Python
- objects.
+ This class adds the boilerplate Python comparison operators that
+ you expect in Python programs for comparing Python objects.
- This class is not complete in of itself; it is intended to be
- mixed into other classes, and is used as a fallback when Python
- object's comparison does not apply due to type mismatch.
+ This class is not complete in of itself, it is intended to be
+ mixed into other classes.
Each class should provide a `element_order` property which
is the primitive from which all other comparisons are based on.
- The class also contains a `pattern_precedence` property that
- provides the sort key used to order a list of rules according to
- the precedence they have in the evaluation loop. Note that pattern
- precedence and element ordering are separate concepts, although
- they both have a similar feel.
-
+ The class also contains a `pattern_precedence` property that provides
+ the sort key used to order a list of rules according to the
+ precedence they have in the evaluation loop.
"""
@property
@@ -184,7 +171,7 @@ def __ne__(self, other) -> bool:
# finished with ``END_OF_LIST_PATTERN_SORT_KEY`` to ensure that the longest
# list of patterns always come first.
-# Let's start by defining the basic magic numbers:
+# Let' s start by defining the basic magic numbers:
# EXPRESSION BIT
PATTERN_SORT_KEY_IS_EXPRESSION = 0x00020000
@@ -272,17 +259,15 @@ def __ne__(self, other) -> bool:
) # Used as the last element in the third field.
-### _ELT_ORDER suffixes are used in element ordering and
-### Expression.element_order().
+### SORT_KEYS prefix for expression_order
-BASIC_ATOM_NUMBER_ELT_ORDER = 0x00
-BASIC_ATOM_STRING_ELT_ORDER = 0x01
-BASIC_ATOM_BYTEARRAY_ELT_ORDER = 0x02
-LITERAL_EXPRESSION_ELT_ORDER = 0x03
+BASIC_ATOM_NUMBER_SORT_KEY = 0x00
+BASIC_ATOM_STRING_OR_BYTEARRAY_SORT_KEY = 0x01
+LITERAL_EXPRESSION_SORT_KEY = 0x03
-BASIC_NUMERIC_EXPRESSION_ELT_ORDER = 0x12
-GENERAL_NUMERIC_EXPRESSION_ELT_ORDER = 0x13
-IMAGE_EXPRESSION_ELT_ORDER = 0x13
+BASIC_NUMERIC_EXPRESSION_SORT_KEY = 0x12
+GENERAL_NUMERIC_EXPRESSION_SORT_KEY = 0x13
+IMAGE_EXPRESSION_SORT_KEY = 0x13
-BASIC_EXPRESSION_ELT_ORDER = 0x22
-GENERAL_EXPRESSION_ELT_ORDER = 0x23
+BASIC_EXPRESSION_SORT_KEY = 0x22
+GENERAL_EXPRESSION_SORT_KEY = 0x23
diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py
index 1394d06e8..83fa837ce 100644
--- a/mathics/core/symbols.py
+++ b/mathics/core/symbols.py
@@ -16,8 +16,8 @@
from mathics.core.keycomparable import (
BASIC_ATOM_PATTERN_SORT_KEY,
- BASIC_EXPRESSION_ELT_ORDER,
- BASIC_NUMERIC_EXPRESSION_ELT_ORDER,
+ BASIC_EXPRESSION_SORT_KEY,
+ BASIC_NUMERIC_EXPRESSION_SORT_KEY,
Monomial,
)
from mathics.eval.tracing import trace_evaluate
@@ -216,13 +216,6 @@ def get_head_name(self) -> "str":
# 1/0
# return None if stop_on_error else {}
- def get_lookup_name(self) -> str:
- """
- By default, atoms that are not symbols
- have their class head_names as their lookup names.
- """
- return self.class_head_name
-
@property
def element_order(self) -> tuple:
"""
@@ -468,12 +461,6 @@ def get_head(self) -> "Symbol":
def get_head_name(self) -> str:
return "System`Symbol"
- def get_lookup_name(self) -> str:
- """
- The lookup name of a Symbol is its name.
- """
- return self.get_name()
-
def get_option_values(self, evaluation, allow_symbols=False, stop_on_error=True):
"""
Build a dictionary of options from an expression.
@@ -558,9 +545,9 @@ def element_order(self) -> tuple:
"""
return (
(
- BASIC_NUMERIC_EXPRESSION_ELT_ORDER
+ BASIC_NUMERIC_EXPRESSION_SORT_KEY
if self.is_numeric()
- else BASIC_EXPRESSION_ELT_ORDER
+ else BASIC_EXPRESSION_SORT_KEY
),
Monomial({self.name: 1}),
0,
diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py
index a6b014081..c46fc227e 100644
--- a/mathics/core/systemsymbols.py
+++ b/mathics/core/systemsymbols.py
@@ -100,7 +100,6 @@
SymbolFactorial = Symbol("System`Factorial")
SymbolFailed = Symbol("System`$Failed")
SymbolFindClusters = Symbol("System`FindClusters")
-SymbolFirst = Symbol("System`First")
SymbolFloor = Symbol("System`Floor")
SymbolFormat = Symbol("System`Format")
SymbolFractionBox = Symbol("System`FractionBox")
@@ -135,7 +134,6 @@
SymbolInputStream = Symbol("System`InputStream")
SymbolInteger = Symbol("System`Integer")
SymbolIntegrate = Symbol("System`Integrate")
-SymbolLast = Symbol("System`Last")
SymbolLeft = Symbol("System`Left")
SymbolLength = Symbol("System`Length")
SymbolLess = Symbol("System`Less")
@@ -179,6 +177,7 @@
SymbolNumberForm = Symbol("System`NumberForm")
SymbolNumberString = Symbol("System`NumberString")
SymbolNumberQ = Symbol("System`NumberQ")
+SymbolNumericArray = Symbol("System`NumericArray")
SymbolNumericQ = Symbol("System`NumericQ")
SymbolO = Symbol("System`O")
SymbolOpacity = Symbol("System`Opacity")
diff --git a/mathics/eval/assignments/assignment.py b/mathics/eval/assignments/assignment.py
index 752928f15..1c00b1d23 100644
--- a/mathics/eval/assignments/assignment.py
+++ b/mathics/eval/assignments/assignment.py
@@ -508,7 +508,7 @@ def eval_assign_format(
lhs_reference = get_reference_expression(lhs)
lhs_reference = (
lhs_reference.get_head()
- if not isinstance(lhs_reference, Symbol)
+ if isinstance(lhs_reference, Expression)
else lhs_reference
)
tags = process_tags_and_upset_dont_allow_custom(
diff --git a/mathics/eval/list/eol.py b/mathics/eval/list/eol.py
index a20326293..8387bdb47 100644
--- a/mathics/eval/list/eol.py
+++ b/mathics/eval/list/eol.py
@@ -128,7 +128,7 @@ def list_parts(exprs, selectors, evaluation):
picked = list(list_parts(selected, selectors[1:], evaluation))
- if unwrap is None and hasattr(expr, "restructure"):
+ if unwrap is None:
expr = expr.restructure(expr.head, picked, evaluation)
yield expr
else:
@@ -197,7 +197,7 @@ def parts_sequence_selector(pspec):
raise MessageException("Part", "pspec", pspec)
def select(inner):
- if not hasattr(inner, "elements"):
+ if isinstance(inner, Atom):
raise MessageException("Part", "partd")
elements = inner.elements
diff --git a/pyproject.toml b/pyproject.toml
index 81d96749d..254eacde4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,7 +21,7 @@ dependencies = [
# ExampleData image hedy.tif is in this format.
# Pillow 9.2 handles sunflowers.jpg
"pillow >= 9.2",
- "pint >=0.24", # Earlier pint has problems with numpy 2.2.6
+ "pint",
"python-dateutil",
# Pympler is used in ByteCount[] and MemoryInUse[].
"Pympler",
diff --git a/test/builtin/list/test_eol.py b/test/builtin/list/test_eol.py
index 7941903f9..0b7780ee4 100644
--- a/test/builtin/list/test_eol.py
+++ b/test/builtin/list/test_eol.py
@@ -87,6 +87,12 @@
"Drop[{1, 2, 3, 4, 5, 6}, {-5, -2, -2}]",
None,
),
+ (
+ "First[a, b, c]",
+ ("First called with 3 arguments; 1 or 2 arguments are expected.",),
+ "First[a, b, c]",
+ None,
+ ),
('FirstPosition[{1, 2, 3}, _?StringQ, "NoStrings"]', None, "NoStrings", None),
("FirstPosition[a, a]", None, "{}", None),
(
@@ -161,6 +167,12 @@
## Negative step
("{1,2,3,4,5}[[3;;1;;-1]]", None, "{3, 2, 1}", None),
("ClearAll[a]", None, "Null", None),
+ (
+ "Last[a, b, c]",
+ ("Last called with 3 arguments; 1 or 2 arguments are expected.",),
+ "Last[a, b, c]",
+ None,
+ ),
("Range[11][[-3 ;; 2 ;; -2]]", None, "{9, 7, 5, 3}", None),
("Range[11][[-3 ;; -7 ;; -3]]", None, "{9, 6}", None),
("Range[11][[7 ;; -7;; -2]]", None, "{7, 5}", None),
@@ -257,46 +269,9 @@
),
],
)
-def test_eol_edicates(str_expr, expected_messages, str_expected, assert_message):
- check_evaluation(
- str_expr,
- str_expected,
- failure_message=assert_message,
- expected_messages=expected_messages,
- hold_expected=True,
- )
-
-
-@pytest.mark.parametrize(
- ("str_expr", "expected_messages", "str_expected", "assert_message"),
- [
- (
- "First[a, b, c]",
- ("First called with 3 arguments; 1 or 2 arguments are expected.",),
- "First[a, b, c]",
- None,
- ),
- (
- "First[ByteArray[{5}]]",
- None,
- "5",
- None,
- ),
- (
- "Last[c, d, e]",
- ("Last called with 3 arguments; 1 or 2 arguments are expected.",),
- "Last[c, d, e]",
- None,
- ),
- (
- "Last[ByteArray[{6}]]",
- None,
- "6",
- None,
- ),
- ],
-)
-def test_First_and_Last(str_expr, expected_messages, str_expected, assert_message):
+def test_eol_edicates_private_doctests(
+ str_expr, expected_messages, str_expected, assert_message
+):
check_evaluation(
str_expr,
str_expected,
diff --git a/test/builtin/statistics/__init__.py b/test/builtin/statistics/__init__.py
deleted file mode 100644
index 40a96afc6..000000000
--- a/test/builtin/statistics/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-
diff --git a/test/builtin/statistics/test_orderstats.py b/test/builtin/statistics/test_orderstats.py
deleted file mode 100644
index 52cd46169..000000000
--- a/test/builtin/statistics/test_orderstats.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from test.helper import check_evaluation
-
-import pytest
-
-from mathics.core.builtin import check_requires_list
-
-
-def test_canonical_sort():
- check_evaluation(
- "Sort[{F[2], ByteArray[{2}]}]",
- "{ByteArray[<1>], F[2]}",
- hold_expected=True,
- )
- check_evaluation(
- r"Sort[Table[IntegerDigits[2^n], {n, 10}]]",
- r"{{2}, {4}, {8}, {1, 6}, {3, 2}, {6, 4}, {1, 2, 8}, {2, 5, 6}, {5, 1, 2}, {1, 0, 2, 4}}",
- )
- check_evaluation(
- r"SortBy[Table[IntegerDigits[2^n], {n, 10}], First]",
- r"{{1, 6}, {1, 2, 8}, {1, 0, 2, 4}, {2}, {2, 5, 6}, {3, 2}, {4}, {5, 1, 2}, {6, 4}, {8}}",
- )
-
-
-# FIXME: come up with an example that doesn't require skimage.
-@pytest.mark.skipif(
- not check_requires_list(["skimage"]),
- reason="Right now need scikit-image for this to work",
-)
-def test_canonical_sort_images():
- check_evaluation(
- r'Sort[{Import["ExampleData/Einstein.jpg"], 5}]',
- r'{5, Import["ExampleData/Einstein.jpg"]}',
- )
-
-
-@pytest.mark.parametrize(
- ("str_expr", "msgs", "str_expected", "fail_msg"),
- [
- ("Sort[{x_, y_}, PatternsOrderedQ]", None, "{x_, y_}", None),
- ],
-)
-def test_SortPatterns(str_expr, msgs, str_expected, fail_msg):
- """ """
-
- check_evaluation(
- str_expr,
- str_expected,
- to_string_expr=True,
- to_string_expected=True,
- hold_expected=True,
- failure_message=fail_msg,
- expected_messages=msgs,
- )
diff --git a/test/builtin/test_binary.py b/test/builtin/test_binary.py
index e39e55905..0791bb254 100644
--- a/test/builtin/test_binary.py
+++ b/test/builtin/test_binary.py
@@ -376,24 +376,6 @@ def test_private_doctests_io(str_expr, str_expected, fail_msg):
)
-@pytest.mark.parametrize(
- ("str_expr", "str_expected", "fail_msg"),
- [
- ("Head[ByteArray[{1}]]", "ByteArray", None),
- ],
-)
-def test_ByteArray(str_expr, str_expected, fail_msg):
- """ """
- check_evaluation(
- str_expr,
- str_expected,
- to_string_expr=True,
- to_string_expected=True,
- hold_expected=True,
- failure_message=fail_msg,
- )
-
-
@pytest.mark.parametrize(
("str_expr", "str_expected", "fail_msg"),
[
@@ -411,7 +393,7 @@ def test_ByteArray(str_expr, str_expected, fail_msg):
),
],
)
-def test_ByteOrdering(str_expr, str_expected, fail_msg):
+def test_private_doctests_system(str_expr, str_expected, fail_msg):
""" """
check_evaluation(
str_expr,
diff --git a/test/builtin/test_numericarray.py b/test/builtin/test_numericarray.py
new file mode 100644
index 000000000..b539aae7a
--- /dev/null
+++ b/test/builtin/test_numericarray.py
@@ -0,0 +1,54 @@
+from mathics.core.atoms import Integer, NumericArray, String
+from mathics.core.convert.python import from_python
+from test.helper import check_evaluation, evaluate
+
+import numpy as np
+import pytest
+
+#
+# Python API tests
+#
+
+def test_numericarray_atom_preserves_array_reference():
+ array = np.array([1, 2, 3], dtype=np.int64)
+ atom = NumericArray(array)
+ assert atom.value is array
+
+def test_numericarray_atom_preserves_equality():
+ array = np.array([1, 2, 3], dtype=np.int64)
+ atom = NumericArray(array, dtype=np.float64)
+ np.testing.assert_array_equal(atom.value, array)
+
+
+def test_numericarray_expression_from_python_array():
+ array = np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32)
+ atom = from_python(array)
+ assert isinstance(atom, NumericArray)
+ assert atom.value is array
+
+
+#
+# WL tests
+#
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected"),
+ [
+ ("NumericArray[{{1,2},{3,4}}]", ""),
+ ("ToString[NumericArray[{{1,2},{3,4}}]]", ""),
+ ("Head[NumericArray[{1,2}]]", "NumericArray"),
+ ("AtomQ[NumericArray[{1,2}]]", "True"),
+ ("Normal[NumericArray[{{1,2}, {3,4}}]]", "{{1, 2}, {3, 4}}"),
+ ]
+)
+def test_basics(str_expr, str_expected):
+ check_evaluation(str_expr, str_expected, hold_expected=True)
+
+def test_type_conversion():
+ expr = evaluate("NumericArray[{1,2}]")
+ assert isinstance(expr, NumericArray)
+ assert expr.value.dtype == np.int64
+ expr = evaluate('NumericArray[{1,2}, "ComplexReal32"]')
+ assert expr.value.dtype == np.complex64
+
+
diff --git a/test/builtin/test_sort.py b/test/builtin/test_sort.py
index 4c851ed95..78daf0dc0 100644
--- a/test/builtin/test_sort.py
+++ b/test/builtin/test_sort.py
@@ -1,11 +1,62 @@
# -*- coding: utf-8 -*-
-from test.helper import check_evaluation
+from test.helper import check_evaluation, evaluate_value
+import pytest
+
+from mathics.core.builtin import check_requires_list
from mathics.core.expression import Expression
from mathics.core.symbols import Symbol, SymbolPlus, SymbolTimes
+def test_canonical_sort():
+ check_evaluation(
+ """
+ Sort[{
+ "a","b", 1,
+ ByteArray[{1,2,4,1}],
+ 2, 1.2, I, 2I-3, A,
+ a+b, a*b, a+1, a*2, b^3, 2/3,
+ A[x], F[2], F[x], F[x_], F[x___], F[x,t], F[x__],
+ Condition[A,b>2], Pattern[expr, A]
+ }]
+ """,
+ """{ -3 + 2*I, I, 2 / 3, 1, 1.2, 2,
+ "a", "b", A, 2*a, a*b, b^3,
+ A[x], F[2], F[x], F[x_], F[x___], F[x__], F[x, t],
+ ByteArray["AQIEAQ=="], A /; b > 2,
+ expr:A, 1 + a, a + b}""",
+ )
+ # The right canonical order should be, according to WMA:
+ # -3 + 2*I, I, 2/3, 1, 1.2, 2,
+ # "a", "b", 2*a,
+ # 1 + a, A, a*b, b^3, a + b,
+ # A[x], A /; b > 2,
+ # F[2], F[x], F[x_], F[x___], F[x__], F[x, t],
+ # ByteArray["AQIEAQ=="], expr:A
+
+ check_evaluation(
+ r"Sort[Table[IntegerDigits[2^n], {n, 10}]]",
+ r"{{2}, {4}, {8}, {1, 6}, {3, 2}, {6, 4}, {1, 2, 8}, {2, 5, 6}, {5, 1, 2}, {1, 0, 2, 4}}",
+ )
+ check_evaluation(
+ r"SortBy[Table[IntegerDigits[2^n], {n, 10}], First]",
+ r"{{1, 6}, {1, 2, 8}, {1, 0, 2, 4}, {2}, {2, 5, 6}, {3, 2}, {4}, {5, 1, 2}, {6, 4}, {8}}",
+ )
+
+
+# FIXME: come up with an example that doesn't require skimage.
+@pytest.mark.skipif(
+ not check_requires_list(["skimage"]),
+ reason="Right now need scikit-image for this to work",
+)
+def test_canonical_sort_images():
+ check_evaluation(
+ r'Sort[{Import["ExampleData/Einstein.jpg"], 5}]',
+ r'{5, Import["ExampleData/Einstein.jpg"]}',
+ )
+
+
def test_Expression_sameQ():
"""
Test Expression.SameQ
diff --git a/test/builtin/test_statistics.py b/test/builtin/test_statistics.py
new file mode 100644
index 000000000..ff92e5725
--- /dev/null
+++ b/test/builtin/test_statistics.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.statistics.
+"""
+
+import sys
+import time
+from test.helper import check_evaluation, evaluate
+
+import pytest
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "msgs", "str_expected", "fail_msg"),
+ [
+ ("Sort[{x_, y_}, PatternsOrderedQ]", None, "{x_, y_}", None),
+ ],
+)
+def test_private_doctests_statistics_orderstatistics(
+ str_expr, msgs, str_expected, fail_msg
+):
+ """ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=fail_msg,
+ expected_messages=msgs,
+ )
diff --git a/test/builtin/test_testing_expressions.py b/test/builtin/test_testing_expressions.py
index a1ced6d6b..8efe40db5 100644
--- a/test/builtin/test_testing_expressions.py
+++ b/test/builtin/test_testing_expressions.py
@@ -3,7 +3,9 @@
Unit tests for mathics.builtin.testing_expressions
"""
-from test.helper import check_evaluation
+import sys
+import time
+from test.helper import check_evaluation, evaluate
import pytest
@@ -23,7 +25,7 @@
("Xor[a, b]", None, "a \\[Xor] b", None),
],
)
-def test_logic(str_expr, msgs, str_expected, fail_msg):
+def test_private_doctests_logic(str_expr, msgs, str_expected, fail_msg):
"""text_expressions.logic"""
check_evaluation(
str_expr,
@@ -68,7 +70,7 @@ def test_logic(str_expr, msgs, str_expected, fail_msg):
("SubsetQ[f[a, b, c], f[a]]", None, "True", None),
],
)
-def test_list_oriented(str_expr, msgs, str_expected, fail_msg):
+def test_private_doctests_list_oriented(str_expr, msgs, str_expected, fail_msg):
"""text_expressions.logic"""
check_evaluation(
str_expr,
@@ -95,7 +97,7 @@ def test_list_oriented(str_expr, msgs, str_expected, fail_msg):
("a != b != a", None, "a != b != a", "Reproduce strange MMA behaviour"),
],
)
-def test_equality_inequality(str_expr, msgs, str_expected, fail_msg):
+def test_private_doctests_equality_inequality(str_expr, msgs, str_expected, fail_msg):
"""text_expressions.logic"""
check_evaluation(
str_expr,
@@ -122,7 +124,7 @@ def test_equality_inequality(str_expr, msgs, str_expected, fail_msg):
("PrimeQ[2 ^ 255 - 1]", None, "False", None),
],
)
-def test_numerical_properties(str_expr, msgs, str_expected, fail_msg):
+def test_private_doctests_numerical_properties(str_expr, msgs, str_expected, fail_msg):
"""text_expressions.numerical_properties"""
check_evaluation(
str_expr,
@@ -175,52 +177,3 @@ def test_matchq(str_expr, msgs, str_expected, fail_msg):
failure_message=fail_msg,
expected_messages=msgs,
)
-
-
-@pytest.mark.parametrize(
- ("str_expr", "str_expected", "assert_fail_msg"),
- [
- # Clean the definitions, because
- # a previous definition of `A` or `F` could make
- # the test to fail.
- (None, None, None),
- ('Order["c", "d"]', "1", "Alphabetic order: 'c' comes before 'd'"),
- ('Order["d", "c"]', "-1", "Alphabetic order: 'd' comes after 'c'"),
- ('Order["c", ByteArray[{99}]]', "1", "String comes before ByteArray"),
- ('Order[ByteArray[{1, 99}], "ZZZZZ"]', "-1", "ByteArray comes after String"),
- ('Order["xyzzy", "xyzzy"]', "0", "Equal strings"),
- (
- "Order[ByteArray[{1, 99}], ByteArray[{2, 0}]]",
- "1",
- "Numeric ordering within a ByteArray",
- ),
- ('Order["a", 1000]', "-1", "String comes after Integer"),
- ("Order[0.9, 1]", "1", "Numeric less-than comparison between Real and Integer"),
- (
- "Order[1.2, 1]",
- "-1",
- "Numeric greater than comparison between Real and Integer",
- ),
- ("Order[F[2], A[2]]", "-1", "Function ordering in function name"),
- (
- "Order[F[2], F[3]]",
- "1",
- "Function ordering in function with a single parameter",
- ),
- (
- "Order[F[2, 3], F[2]]",
- "-1",
- "Function ordering in function with mixed-length parameters",
- ),
- ],
-)
-def test_order(str_expr: str, str_expected: str, assert_fail_msg: str):
- """text_expressions.matchq"""
- check_evaluation(
- str_expr,
- str_expected,
- to_string_expr=False,
- to_string_expected=False,
- hold_expected=False,
- failure_message=assert_fail_msg,
- )
diff --git a/test/test_evaluation.py b/test/test_evaluation.py
index d21b7ac07..14a6c4e10 100644
--- a/test/test_evaluation.py
+++ b/test/test_evaluation.py
@@ -289,20 +289,6 @@ def test_system_specific_long_integer():
# check_evaluation_with_err(str_expr, str_expected, message)
-def test_eval_atom_upvalues():
- """Check that upvalues of atoms are taken into account in evaluations"""
- # Clear definitions
- check_evaluation(None, None, None)
- check_evaluation(
- "Unprotect[Real]; Real/:F[x_Real]:=x; DownValues[F]",
- "{}",
- "F does not have downvalues",
- )
- check_evaluation("F[3.]", "3.", "Upvalue of Real is taken into account.")
- # Clear definitions again.
- check_evaluation(None, None, None)
-
-
def test_exit():
global session
try: