diff --git a/mathics/builtin/numbers/constants.py b/mathics/builtin/numbers/constants.py index df596a728..28158a48a 100644 --- a/mathics/builtin/numbers/constants.py +++ b/mathics/builtin/numbers/constants.py @@ -6,9 +6,6 @@ Numeric, Arithmetic, or Symbolic constants like Pi, E, or Infinity. """ -# This tells documentation how to sort this module -sort_order = "mathics.builtin.mathematical-constants" - import math from typing import Dict, Optional @@ -25,6 +22,9 @@ from mathics.core.symbols import Atom, Symbol, strip_context from mathics.core.systemsymbols import SymbolIndeterminate +# This tells documentation how to sort this module +sort_order = "mathics.builtin.mathematical-constants" + def mp_constant(fn: str, d=None) -> mpmath.mpf: """ @@ -191,7 +191,7 @@ def __init__(self, *args, **kwargs): value_float = self.to_numpy(self.symbol) NUMERICAL_CONSTANTS[self.symbol] = MachineReal(value_float) - def to_numpy(self, args): + def to_numpy(self, _): return NUMERICAL_CONSTANTS[self.symbol] @@ -286,6 +286,10 @@ class ComplexInfinity(_SympyConstant): "ComplexInfinity": "DirectedInfinity[]", } + @property + def sympy(self) -> sympy.core.numbers.ComplexInfinity: + return zoo + class Degree(_MPMathConstant, _NumpyConstant, _SympyConstant): """ @@ -376,6 +380,10 @@ def eval_N(self, precision, evaluation): "N[E, precision_]" return self.get_constant(precision, evaluation) + @property + def sympy(self) -> sympy.core.numbers.Exp1: + return SymPyE + class EulerGamma(_MPMathConstant, _NumpyConstant, _SympyConstant): """ @@ -402,6 +410,10 @@ class EulerGamma(_MPMathConstant, _NumpyConstant, _SympyConstant): numpy_name = "euler_gamma" sympy_name = "EulerGamma" + @property + def sympy(self) -> sympy.core.numbers.EulerGamma: + return S.EulerGamma + class Glaisher(_MPMathConstant): """ diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index 30ab8d8ac..a5cadae53 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -8,6 +8,7 @@ import mpmath import sympy +from sympy.core import numbers as sympy_numbers from mathics.core.element import BoxElementMixin, ImmutableValueMixin from mathics.core.number import ( @@ -108,7 +109,7 @@ def is_numeric(self, evaluation=None) -> bool: def to_mpmath(self, precision: Optional[int] = None) -> mpmath.ctx_mp_python.mpf: """ - Convert self._value to an mpmath number with precision ``precision`` + Convert self.value to an mpmath number with precision ``precision`` If ``precision`` is None, use mpmath's default precision. A mpmath number is the default implementation for Number. @@ -139,8 +140,9 @@ def round(self, d: Optional[int] = None) -> "Number": @property def value(self) -> T: - """ - Equivalent value in Python's native datatype. + """Equivalent value in either SymPy's or Python's native + datatype if that exist. Note the SymPy value + and the Python value might be the same thing. """ return self._value @@ -195,6 +197,8 @@ class Integer(Number[int]): # dictionary's value is the corresponding Mathics Integer object. _integers: Dict[Any, "Integer"] = {} + _sympy: sympy_numbers.Integer + # 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 @@ -209,6 +213,7 @@ def __new__(cls, value) -> "Integer": # Cache object so we don't allocate again. self._integers[value] = self + self._sympy = sympy_numbers.Integer(value) # Set a value for self.__hash__() once so that every time # it is used this is fast. Note that in contrast to the @@ -318,12 +323,16 @@ def round(self, d: Optional[int] = None) -> Union["MachineReal", "PrecisionReal" d = MACHINE_PRECISION_VALUE return PrecisionReal(sympy.Float(self.value, d)) - def to_sympy(self, **_): - return sympy.Integer(self._value) + @property + def sympy(self) -> sympy_numbers.Integer: + return self._sympy - def sameQ(self, other) -> bool: + def to_sympy(self, **_) -> sympy_numbers.Integer: + return self.sympy + + def sameQ(self, rhs) -> bool: """Mathics SameQ""" - return isinstance(other, Integer) and self._value == other.value + return isinstance(rhs, Integer) and self._value == rhs.value def do_copy(self) -> "Integer": return Integer(self._value) @@ -492,25 +501,25 @@ def make_boxes(self, form): def is_zero(self) -> bool: return self.value == 0.0 - def sameQ(self, other) -> bool: + def sameQ(self, rhs) -> bool: """Mathics SameQ for MachineReal. - If the other comparison value is a MachineReal, the values - have to be equal. If the other value is a PrecisionReal though, then + If the rhs comparison value is a MachineReal, the values + have to be equal. If the rhs value is a PrecisionReal though, then the two values have to be within 1/2 ** (precision) of - other-value's precision. For any other type, sameQ is False. + rhs-value's precision. For any rhs type, sameQ is False. """ - if isinstance(other, MachineReal): - return self.value == other.value - if isinstance(other, PrecisionReal): - other_value = other.value + if isinstance(rhs, MachineReal): + return self.value == rhs.value + if isinstance(rhs, PrecisionReal): + rhs_value = rhs.value value = self.to_sympy() # If sympy fixes the issue, this comparison would be # enough - if (value - other_value).is_zero: + if (value - rhs_value).is_zero: return True # this handles the issue... - diff = abs(value - other_value) - prec = min(value._prec, other_value._prec) + diff = abs(value - rhs_value) + prec = min(value._prec, rhs_value._prec) return diff < 0.5 ** (prec) else: return False @@ -539,13 +548,14 @@ class PrecisionReal(Real[sympy.Float]): # The key is the PrecisionReal's sympy.Float, and the # dictionary's value is the corresponding Mathics PrecisionReal object. _precision_reals: Dict[Any, "PrecisionReal"] = {} + _sympy: Number def __new__(cls, value) -> "PrecisionReal": n = sympy.Float(value) self = cls._precision_reals.get(n) if self is None: self = Number.__new__(cls) - self._value = n + self._sympy = self._value = n # Cache object so we don't allocate again. self._precision_reals[n] = self @@ -593,12 +603,12 @@ def round(self, d: Optional[int] = None) -> Union[MachineReal, "PrecisionReal"]: _prec = min(prec(d), self.value._prec) return PrecisionReal(sympy.Float(self.value, precision=_prec)) - def sameQ(self, other) -> bool: + def sameQ(self, rhs) -> bool: """Mathics SameQ for PrecisionReal""" - if isinstance(other, PrecisionReal): - other_value = other.value - elif isinstance(other, MachineReal): - other_value = other.to_sympy() + if isinstance(rhs, PrecisionReal): + other_value = rhs.value + elif isinstance(rhs, MachineReal): + other_value = rhs.to_sympy() else: return False value = self.value @@ -679,11 +689,11 @@ def is_literal(self) -> bool: """ return True - def sameQ(self, other) -> bool: + def sameQ(self, rhs) -> bool: """Mathics SameQ""" # FIX: check - if isinstance(other, ByteArrayAtom): - return self.value == other.value + if isinstance(rhs, ByteArrayAtom): + return self.value == rhs.value return False def get_string_value(self) -> Optional[str]: @@ -822,12 +832,10 @@ def get_sort_key(self, pattern_sort=False) -> tuple: else: return (0, 0, self.real.get_sort_key()[2], self.imag.get_sort_key()[2], 1) - def sameQ(self, other) -> bool: + def sameQ(self, rhs) -> bool: """Mathics SameQ""" return ( - isinstance(other, Complex) - and self.real == other.real - and self.imag == other.imag + isinstance(rhs, Complex) and self.real == rhs.real and self.imag == rhs.imag ) def round(self, d=None) -> "Complex": @@ -951,9 +959,9 @@ def round(self, d=None) -> Union["MachineReal", "PrecisionReal"]: else: return PrecisionReal(self.value.n(d)) - def sameQ(self, other) -> bool: + def sameQ(self, rhs) -> bool: """Mathics SameQ""" - return isinstance(other, Rational) and self.value == other.value + return isinstance(rhs, Rational) and self.value == rhs.value def numerator(self) -> "Integer": return Integer(self.value.as_numer_denom()[0]) @@ -1059,9 +1067,9 @@ def is_literal(self) -> bool: """ return True - def sameQ(self, other) -> bool: + def sameQ(self, rhs) -> bool: """Mathics SameQ""" - return isinstance(other, String) and self.value == other.value + return isinstance(rhs, String) and self.value == rhs.value def to_expression(self): return self diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 27ca38124..fbe01b36a 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -412,7 +412,7 @@ def lookup_name(self, name: str) -> str: return ctx_name return with_context - def get_package_names(self) -> List[str]: + def get_package_names(self) -> List[Optional[str]]: """Return the list of names of the packages loaded in the system.""" try: packages = self.get_ownvalue("System`$Packages") @@ -420,7 +420,11 @@ def get_package_names(self) -> List[str]: return [] assert packages.has_form("System`List", None) - return [c.get_string_value() for c in packages.get_elements()] + return [ + c.get_string_value() + for c in packages.get_elements() + if c.get_string_value() is not None + ] # return sorted({name.split("`")[0] for name in self.get_names()}) def shorten_name(self, name_with_ctx: str) -> str: diff --git a/mathics/core/element.py b/mathics/core/element.py index e020e99bf..9054dc5c1 100644 --- a/mathics/core/element.py +++ b/mathics/core/element.py @@ -359,7 +359,7 @@ def get_sequence(self) -> Sequence["BaseElement"]: else: return tuple([self]) - def get_string_value(self): + def get_string_value(self) -> Optional[str]: return None @property diff --git a/mathics/core/expression.py b/mathics/core/expression.py index f52f1dbdd..97f6bc4aa 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -129,8 +129,6 @@ def eval_SameQ(self, other): # The next element in the tree. Maybe should be an iterator? def next_elem(): nonlocal len_elements - nonlocal parents - nonlocal pos while pos and pos[-1] == len_elements: pos.pop() @@ -1185,10 +1183,10 @@ def rewrite_apply_eval_step(self, evaluation) -> Tuple[BaseElement, bool]: self._build_elements_properties() assert self.elements_properties is not None + recompute_properties = False + # @timeit def eval_elements(): - nonlocal recompute_properties - # @timeit def eval_range(indices): nonlocal recompute_properties @@ -1237,7 +1235,6 @@ def rest_range(indices): # * evaluate elements, # * run to_python() on them in Expression construction, or # * convert Expression elements from a tuple to a list and back - recompute_properties = False elements: Sequence[BaseElement] if self.elements_properties.elements_fully_evaluated: elements = self._elements diff --git a/mathics/core/list.py b/mathics/core/list.py index 6f415e63e..f6f411bd6 100644 --- a/mathics/core/list.py +++ b/mathics/core/list.py @@ -4,7 +4,7 @@ """ import reprlib -from typing import Optional, Tuple +from typing import Any, Optional, Tuple from mathics.core.element import ElementsProperties from mathics.core.evaluation import Evaluation @@ -29,6 +29,7 @@ class ListExpression(Expression): """ _is_literal: bool + _sympy: Optional[Any] def __init__( self, @@ -39,6 +40,7 @@ def __init__( self.options = None self.pattern_sequence = False self._head = SymbolList + self._sympy = None # For debugging: diff --git a/test/builtin/test_forms.py b/test/builtin/test_forms.py index 894a9de18..1bc31a0c5 100644 --- a/test/builtin/test_forms.py +++ b/test/builtin/test_forms.py @@ -3,7 +3,7 @@ Unit tests from mathics.builtin.forms. """ -from test.helper import check_evaluation, session +from test.helper import check_evaluation import pytest