Skip to content

Commit

Permalink
Refactor around RealNumberQ and {In,}ExactNumberQ...
Browse files Browse the repository at this point in the history
* Add slightly better test() annotation for the return type
* create BooleanType and use that
* Reduce use of RealNumberQ by removing it from Range[]
* Move {In,}ExactNumberQ from numbers to numerical_properties which is where
  other tests appear. (it is smaller than numbers).
* add is_number() to mathics.eval.numbers
  • Loading branch information
rocky committed Mar 22, 2023
1 parent b79f495 commit 687badc
Show file tree
Hide file tree
Showing 20 changed files with 182 additions and 107 deletions.
2 changes: 1 addition & 1 deletion mathics/builtin/arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1132,7 +1132,7 @@ class RealNumberQ(Test):

summary_text = "test whether an expression is a real number"

def test(self, expr):
def test(self, expr) -> bool:
return isinstance(expr, (Integer, Rational, Real))


Expand Down
2 changes: 1 addition & 1 deletion mathics/builtin/atomic/atomic.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class AtomQ(Test):

summary_text = "test whether an expression is an atom"

def test(self, expr):
def test(self, expr) -> bool:
return isinstance(expr, Atom)


Expand Down
62 changes: 2 additions & 60 deletions mathics/builtin/atomic/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ class Accuracy(Builtin):
See also <url>
:'Precision':
/doc/reference-of-built-in-symbols/atomic-elements-of-expressions/representation-of-numbers/precision/</url>.
/doc/reference-of-built-in-symbols/atomic-elements-of-expressions
/representation-of-numbers/precision/</url>.
"""

summary_text = "find the accuracy of a number"
Expand All @@ -220,37 +221,6 @@ def eval(self, z, evaluation):
return MachineReal(acc)


class ExactNumberQ(Test):
"""
<url>
:WMA link:
https://reference.wolfram.com/language/ref/ExactNumberQ.html</url>
<dl>
<dt>'ExactNumberQ[$expr$]'
<dd>returns 'True' if $expr$ is an exact number, and 'False' otherwise.
</dl>
>> ExactNumberQ[10]
= True
>> ExactNumberQ[4.0]
= False
>> ExactNumberQ[n]
= False
'ExactNumberQ' can be applied to complex numbers:
>> ExactNumberQ[1 + I]
= True
>> ExactNumberQ[1 + 1. I]
= False
"""

summary_text = "test if an expression is an exact real or complex number"

def test(self, expr):
return isinstance(expr, Number) and not expr.is_inexact()


class IntegerExponent(Builtin):
"""
<url>:WMA link:
Expand Down Expand Up @@ -410,34 +380,6 @@ def eval(self, n, b, evaluation):
return Integer(j)


class InexactNumberQ(Test):
"""
<url>:WMA link:
https://reference.wolfram.com/language/ref/InexactNumberQ.html</url>
<dl>
<dt>'InexactNumberQ[$expr$]'
<dd>returns 'True' if $expr$ is not an exact number, and 'False' otherwise.
</dl>
>> InexactNumberQ[a]
= False
>> InexactNumberQ[3.0]
= True
>> InexactNumberQ[2/3]
= False
'InexactNumberQ' can be applied to complex numbers:
>> InexactNumberQ[4.0+I]
= True
"""

summary_text = "the negation of ExactNumberQ"

def test(self, expr):
return isinstance(expr, Number) and expr.is_inexact()


class RealDigits(Builtin):
"""
<url>:WMA link:
Expand Down
2 changes: 1 addition & 1 deletion mathics/builtin/atomic/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ class StringQ(Test):

summary_text = "test whether an expression is a string"

def test(self, expr):
def test(self, expr) -> bool:
return isinstance(expr, String)


Expand Down
2 changes: 1 addition & 1 deletion mathics/builtin/atomic/symbols.py
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ class SymbolQ(Test):

summary_text = "test whether is a symbol"

def test(self, expr):
def test(self, expr) -> bool:
return isinstance(expr, Symbol)


Expand Down
12 changes: 10 additions & 2 deletions mathics/builtin/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from mathics.core.rules import BuiltinRule, Pattern, Rule
from mathics.core.symbols import (
BaseElement,
BooleanType,
Symbol,
SymbolFalse,
SymbolPlus,
Expand Down Expand Up @@ -843,11 +844,18 @@ def __init__(self, *args, **kwargs):


class Test(Builtin):
def eval(self, expr, evaluation) -> Optional[Symbol]:
"%(name)s[expr_]"
def eval(self, expr, evaluation) -> Optional[BooleanType]:
# Note: in the docstring below, we need to use %(name)s for
# subclasses like ExactNumberQ to work with function-application
# pattern matching.
"""%(name)s[expr_]"""
test_expr = self.test(expr)
return None if test_expr is None else from_bool(bool(test_expr))

def test(self, expr) -> bool:
"""Subclasses of test must implement a boolean test function"""
raise NotImplementedError


@lru_cache()
def run_sympy(sympy_fn: Callable, *sympy_args) -> Any:
Expand Down
4 changes: 2 additions & 2 deletions mathics/builtin/image/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class BinaryImageQ(Test):

summary_text = "test whether pixels in an image are binary bit values"

def test(self, expr):
def test(self, expr) -> bool:
return isinstance(expr, Image) and expr.storage_type() == "Bit"


Expand Down Expand Up @@ -64,5 +64,5 @@ class ImageQ(Test):

summary_text = "test whether is a valid image"

def test(self, expr):
def test(self, expr) -> bool:
return isinstance(expr, Image)
2 changes: 1 addition & 1 deletion mathics/builtin/list/associations.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ class AssociationQ(Test):

summary_text = "test if an expression is a valid association"

def test(self, expr):
def test(self, expr) -> bool:
def validate(elements):
for element in elements:
if element.has_form(("Rule", "RuleDelayed"), 2):
Expand Down
40 changes: 33 additions & 7 deletions mathics/builtin/list/constructing.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from mathics.core.symbols import Atom
from mathics.core.systemsymbols import SymbolNormal
from mathics.eval.lists import get_tuples, list_boxes
from mathics.eval.numbers import is_number


class Array(Builtin):
Expand Down Expand Up @@ -205,6 +206,11 @@ def eval_general(self, expr, evaluation: Evaluation):
)


range_list_elements_properties = ElementsProperties(
elements_fully_evaluated=True, is_flat=True, is_ordered=True
)


class Range(Builtin):
"""
<url>
Expand All @@ -221,33 +227,51 @@ class Range(Builtin):
>> Range[5]
= {1, 2, 3, 4, 5}
>> Range[-3, 2]
= {-3, -2, -1, 0, 1, 2}
>> Range[1.0, 2.3]
= {1., 2.}
>> Range[0, 2, 1/3]
= {0, 1 / 3, 2 / 3, 1, 4 / 3, 5 / 3, 2}
>> Range[1.0, 2.3, .5]
= {1., 1.5, 2.}
"""

attributes = A_LISTABLE | A_PROTECTED

messages = {
"range": "Range specification does not have appropriate bounds.",
}

rules = {
"Range[imax_?RealNumberQ]": "Range[1, imax, 1]",
"Range[imin_?RealNumberQ, imax_?RealNumberQ]": "Range[imin, imax, 1]",
"Range[imax_]": "Range[1, imax, 1]",
"Range[imin_, imax_]": "Range[imin, imax, 1]",
}

summary_text = "form a list from a range of numbers or other objects"

def eval(self, imin, imax, di, evaluation: Evaluation):
"Range[imin_?RealNumberQ, imax_?RealNumberQ, di_?RealNumberQ]"
"Range[imin_, imax_, di_]"

for arg in imin, imax, di:
if not is_number(arg):
evaluation.message(self.get_name(), "range")
return

if (
isinstance(imin, Integer)
and isinstance(imax, Integer)
and isinstance(di, Integer)
):
result = [Integer(i) for i in range(imin.value, imax.value + 1, di.value)]
# TODO: add ElementProperties in Expression interface refactor branch:
# fully_evaluated, flat, are True and is_ordered = di.value >= 0
return ListExpression(*result)
return ListExpression(
*result, elements_properties=range_list_elements_properties
)

imin = imin.to_sympy()
imax = imax.to_sympy()
Expand All @@ -258,7 +282,9 @@ def eval(self, imin, imax, di, evaluation: Evaluation):
evaluation.check_stopped()
result.append(from_sympy(index))
index += di
return ListExpression(*result)
return ListExpression(
*result, elements_properties=range_list_elements_properties
)


class Permutations(Builtin):
Expand Down
4 changes: 2 additions & 2 deletions mathics/builtin/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ class NotOptionQ(Test):

summary_text = "test whether an expression does not match the form of a valid option specification"

def test(self, expr):
def test(self, expr) -> bool:
if hasattr(expr, "flatten_with_respect_to_head"):
expr = expr.flatten_with_respect_to_head(SymbolList)
if not expr.has_form("List", None):
Expand Down Expand Up @@ -269,7 +269,7 @@ class OptionQ(Test):
"test whether an expression matches the form of a valid option specification"
)

def test(self, expr):
def test(self, expr) -> bool:
if hasattr(expr, "flatten_with_respect_to_head"):
expr = expr.flatten_with_respect_to_head(SymbolList)
if not expr.has_form("List", None):
Expand Down
4 changes: 2 additions & 2 deletions mathics/builtin/quantities.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class KnownUnitQ(Test):

summary_text = "tests whether its argument is a canonical unit."

def test(self, expr):
def test(self, expr) -> bool:
def validate(unit):
try:
Q_(1, unit)
Expand Down Expand Up @@ -292,7 +292,7 @@ class QuantityQ(Test):

summary_text = "tests whether its the argument is a quantity"

def test(self, expr):
def test(self, expr) -> bool:
def validate_unit(unit):
try:
Q_(1, unit)
Expand Down
6 changes: 3 additions & 3 deletions mathics/builtin/string/characters.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class LowerCaseQ(Test):

summary_text = "test wether all the characters are lower-case letters"

def test(self, s):
def test(self, s) -> bool:
return isinstance(s, String) and all(c.islower() for c in s.get_string_value())


Expand Down Expand Up @@ -240,7 +240,7 @@ class UpperCaseQ(Test):
= True
"""

summary_text = "test wether all the characters are upper-case letters"
summary_text = "test whether all the characters are upper-case letters"

def test(self, s):
def test(self, s) -> bool:
return isinstance(s, String) and all(c.isupper() for c in s.get_string_value())
2 changes: 1 addition & 1 deletion mathics/builtin/testing_expressions/expression_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ListQ(Test):

summary_text = "test if an expression is a list"

def test(self, expr):
def test(self, expr) -> bool:
return expr.get_head_name() == "System`List"


Expand Down
4 changes: 2 additions & 2 deletions mathics/builtin/testing_expressions/list_oriented.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class LevelQ(Test):

summary_text = "test whether is a valid level specification"

def test(self, ls):
def test(self, ls) -> bool:
try:
start, stop = python_levelspec(ls)
return True
Expand Down Expand Up @@ -270,7 +270,7 @@ class NotListQ(Test):

summary_text = "test if an expression is not a list"

def test(self, expr):
def test(self, expr) -> bool:
return expr.get_head_name() != "System`List"


Expand Down
Loading

0 comments on commit 687badc

Please sign in to comment.