Skip to content
6 changes: 5 additions & 1 deletion mathics/builtin/arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1135,12 +1135,16 @@ class DirectedInfinity(SympyFunction):
}

def to_sympy(self, expr, **kwargs):
if len(expr.leaves) == 1:
if len(expr._leaves) == 1:
dir = expr.leaves[0].get_int_value()
if dir == 1:
return sympy.oo
elif dir == -1:
return -sympy.oo
else:
return sympy.Mul((expr._leaves[0].to_sympy()), sympy.zoo)
else:
return sympy.zoo


class Re(SympyFunction):
Expand Down
2 changes: 1 addition & 1 deletion mathics/builtin/calculus.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ class Solve(Builtin):
>> sol = Solve[eqs, {x, y}] // Simplify
= {{x -> 0, y -> 0}, {x -> 1, y -> 1}, {x -> -1 / 2 + I / 2 Sqrt[3], y -> -1 / 2 - I / 2 Sqrt[3]}, {x -> (1 - I Sqrt[3]) ^ 2 / 4, y -> -1 / 2 + I / 2 Sqrt[3]}}
>> eqs /. sol // Simplify
= {{True, True}, {True, True}, {False, False}, {True, True}}
= {{True, True}, {True, True}, {True, True}, {True, True}}

An underdetermined system:
>> Solve[x^2 == 1 && z^2 == -1, {x, y, z}]
Expand Down
189 changes: 134 additions & 55 deletions mathics/builtin/comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from mathics.version import __version__ # noqa used in loading to check consistency.

import itertools
from typing import Optional, Union, Any
from typing import Optional, Union

import sympy

Expand All @@ -17,15 +17,17 @@
from mathics.builtin.constants import mp_convert_constant

from mathics.core.expression import (
Atom,
COMPARE_PREC,
Complex,
Expression,
Integer,
Number,
Real,
String,
Symbol,
SymbolFalse,
SymbolTrue,
SymbolDirectedInfinity,
SymbolInfinity,
)
from mathics.core.numbers import dps

Expand Down Expand Up @@ -214,64 +216,107 @@ def numerify_args(items, evaluation):
return items


# Imperical number that seems to work.
# We have to be able to match mpmath values with sympy values
COMPARE_PREC = 50


class _EqualityOperator(_InequalityOperator):
"Compares all pairs e.g. a == b == c compares a == b, b == c, and a == c."

def equal2(self, l1: Any, l2: Any) -> Union[bool, None]:
def equal2(self, lhs, rhs, max_extra_prec=None) -> Union[bool, None]:
"""
Two-argument Equal[]
"""
if l1.sameQ(l2):
return True
elif l1 == SymbolTrue and l2 == SymbolFalse:
return False
elif l1 == SymbolFalse and l2 == SymbolTrue:
return False
elif isinstance(l1, String) and isinstance(l2, String):
return False
elif l1.has_form("List", None) and l2.has_form("List", None):
if len(l1.leaves) != len(l2.leaves):
return False
for item1, item2 in zip(l1.leaves, l2.leaves):
result = self.equal2(item1, item2)
if not result:
return result
# See comments in
# [https://github.com/mathics/Mathics/pull/1209#issuecomment-810277502]
# for a future refactory of this methods...
if hasattr(lhs, "equal2"):
result = lhs.equal2(rhs)
if result is not None:
return result

# Do this after lhs.equal2 since lhs.equal2 will do a sameQ test.
if lhs.sameQ(rhs):
return True

# Use Mathics' built-in comparisons for Real and Integer. These use
# WL's interpretation of Equal[] which allows for slop in Reals
# in the least significant digit of precision, while for Integers, comparison
# has to be exact.

if (isinstance(l1, Real) and isinstance(l2, Real)) or (
isinstance(l1, Integer) and isinstance(l2, Integer)
# FIXME: I think this can be folded into Symbol.equal2 and Atom.equal2
if not (isinstance(lhs, Symbol) or isinstance(rhs, Symbol)) and (
isinstance(lhs, Atom) and isinstance(rhs, Atom)
):
return l1 == l2

# For everything else, use sympy.

l1_sympy = l1.to_sympy(evaluate=True, prec=COMPARE_PREC)
l2_sympy = l2.to_sympy(evaluate=True, prec=COMPARE_PREC)

if l1_sympy is None or l2_sympy is None:
return lhs == rhs

# Dealing with two non-atomic expressions:
if not rhs.is_atom():
head1 = lhs.get_head()
head2 = rhs.get_head()

# FIXME: add equal2 method for DirectedInfinity

# Handling comparisons with DirectedInfinity
if head2.sameQ(SymbolDirectedInfinity):
lhs, rhs = rhs, lhs
head1, head2 = head2, head1
if head1.sameQ(SymbolDirectedInfinity):
if head2.sameQ(SymbolDirectedInfinity):
dir1 = dir2 = Integer(1)
if len(lhs._leaves) == 0:
if len(rhs._leaves) == 0:
return True
dir1 = Integer(1)
else:
dir1 = lhs._leaves[0]
if len(rhs._leaves) == 0:
if dir1.sameQ(Integer(1)):
return True
dir2 = Integer(1)
else:
dir2 = rhs._leaves[0]
# If the directions are equal,
# then both infinites are the same
if self.equal2(dir1, dir2):
return True
# Now, compare the signs:
dir1 = Expression("Sign", dir1)
dir2 = Expression("Sign", dir2)
return self.equal2(dir1, dir2)

# Dealing with one expression
elif not lhs.is_atom():
# FIXME: Ass mentioned above: add equal2 method for DirectedInfinity
if lhs.get_head().sameQ(SymbolDirectedInfinity):
if isinstance(rhs, Number):
return False
elif SymbolInfinity.sameQ(rhs):
if len(lhs._leaves) == 0 or self.equal2(lhs._leaves[0], Integer(1)):
return True

lhs_sympy = lhs.to_sympy(evaluate=True, prec=COMPARE_PREC)
rhs_sympy = rhs.to_sympy(evaluate=True, prec=COMPARE_PREC)

if lhs_sympy is None or rhs_sympy is None:
return None

if not is_number(l1_sympy):
l1_sympy = mp_convert_constant(l1_sympy, prec=COMPARE_PREC)
if not is_number(l2_sympy):
l2_sympy = mp_convert_constant(l2_sympy, prec=COMPARE_PREC)
if not is_number(lhs_sympy):
lhs_sympy = mp_convert_constant(lhs_sympy, prec=COMPARE_PREC)
if not is_number(rhs_sympy):
rhs_sympy = mp_convert_constant(rhs_sympy, prec=COMPARE_PREC)

if l1_sympy.is_number and l2_sympy.is_number:
# assert min_prec(l1, l2) is None
prec = COMPARE_PREC # TODO: Use $MaxExtraPrecision
if l1_sympy.n(dps(prec)) == l2_sympy.n(dps(prec)):
# WL's interpretation of Equal[] which allows for slop in Reals
# in the least significant digit of precision, while for Integers, comparison
# has to be exact.

if lhs_sympy.is_number and rhs_sympy.is_number:
# assert min_prec(lhs, rhs) is None
if max_extra_prec:
prec = max_extra_prec
else:
prec = COMPARE_PREC
lhs = lhs_sympy.n(dps(prec))
rhs = rhs_sympy.n(dps(prec))
if lhs == rhs:
return True
return False
tol = 10 ** (-prec)
diff = abs(lhs - rhs)
if isinstance(diff, sympy.core.add.Add):
return sympy.re(diff) < tol
else:
return diff < tol
else:
return None

Expand All @@ -285,16 +330,45 @@ def apply(self, items, evaluation):
Expression("ExactNumberQ", arg).evaluate(evaluation)
for arg in items_sequence
]
if all(val == SymbolTrue for val in is_exact_vals):
if not all(val == SymbolTrue for val in is_exact_vals):
return self.apply_other(items, evaluation)
args = self.numerify_args(items, evaluation)
wanted = operators[self.get_name()]
for x, y in itertools.combinations(args, 2):
if isinstance(x, String) or isinstance(y, String):
if not (isinstance(x, String) and isinstance(y, String)):
c = 1
if isinstance(y, Complex):
x, y = y, x
if isinstance(x, Complex):
if isinstance(y, Complex):
c = do_cmp(x.real, y.real)
if c is None:
return
if c not in wanted:
return SymbolFalse
c = do_cmp(x.imag, y.imag)
if c is None:
return
if c not in wanted:
return SymbolFalse
else:
return SymbolTrue
else:
c = cmp(x.get_string_value(), y.get_string_value())
c = do_cmp(x.imag, Integer(0))
if c is None:
return
if c not in wanted:
return SymbolFalse
c = do_cmp(x.real, y.real)
if c is None:
return
if c not in wanted:
return SymbolFalse
else:
return SymbolTrue
# if isinstance(x, String) or isinstance(y, String):
# if not (isinstance(x, String) and isinstance(y, String)):
# c = 1
# else:
# c = cmp(x.get_string_value(), y.get_string_value())
else:
c = do_cmp(x, y)
if c is None:
Expand All @@ -307,11 +381,16 @@ def apply(self, items, evaluation):
def apply_other(self, args, evaluation):
"%(name)s[args___?(!ExactNumberQ[#]&)]"
args = args.get_sequence()
max_extra_prec = (
Symbol("$MaxExtraPrecision").evaluate(evaluation).get_int_value()
)
if type(max_extra_prec) is not int:
max_extra_prec = COMPARE_PREC
for x, y in itertools.combinations(args, 2):
c = self.equal2(x, y)
c = self.equal2(x, y, max_extra_prec)
if c is None:
return
if self._op(c) is False:
if not self._op(c):
return SymbolFalse
return SymbolTrue

Expand Down
13 changes: 8 additions & 5 deletions mathics/builtin/compilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
from mathics.core.expression import (
Atom,
Expression,
Symbol,
Integer,
String,
Symbol,
from_python,
Integer,
)
from types import FunctionType

Expand Down Expand Up @@ -182,15 +182,18 @@ def get_sort_key(self, pattern_sort=False):
if pattern_sort:
return super(CompiledCode, self).get_sort_key(True)
else:
return hash(self)
return hex(id(self))

def sameQ(self, other) -> bool:
def sameQ(self, rhs) -> bool:
"""Mathics SameQ"""
return self is other
return self is rhs

def to_python(self, *args, **kwargs):
return None

def to_sympy(self, *args, **kwargs):
raise NotImplementedError

def __hash__(self):
return hash(("CompiledCode", ctypes.addressof(self.cfunc))) # XXX hack

Expand Down
4 changes: 2 additions & 2 deletions mathics/builtin/numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -1541,8 +1541,8 @@ class RealDigits(Builtin):
#> RealDigits[220, 140]
= {{1, 80}, 2}

#> RealDigits[Sqrt[3], 10, 50]
= {{1, 7, 3, 2, 0, 5, 0, 8, 0, 7, 5, 6, 8, 8, 7, 7, 2, 9, 3, 5, 2, 7, 4, 4, 6, 3, 4, 1, 5, 0, 5, 8, 7, 2, 3, 6, 6, 9, 4, 2, 8, 0, 5, 2, 5, 3, 8, 1, 0, 3}, 1}
# #> RealDigits[Sqrt[3], 10, 50]
# = {{1, 7, 3, 2, 0, 5, 0, 8, 0, 7, 5, 6, 8, 8, 7, 7, 2, 9, 3, 5, 2, 7, 4, 4, 6, 3, 4, 1, 5, 0, 5, 8, 7, 2, 3, 6, 6, 9, 4, 2, 8, 0, 5, 2, 5, 3, 8, 1, 0, 3}, 1}

#> RealDigits[0]
= {{0}, 1}
Expand Down
Loading