Skip to content

Commit

Permalink
Improve infinity and Precision (#767)
Browse files Browse the repository at this point in the history
This would be the simplest part of #766, including tests and changes in
`Infinity` and `Precision`. Some of the new tests are commented / marked
as xfail because require the other changes.
  • Loading branch information
mmatera authored Jan 28, 2023
1 parent bb8d3e6 commit 1b3641d
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 31 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ Bugs
#. Some scikit image routines line ``EdgeDetect`` were getting omitted due to overly stringent PYPI requirements

#. Units and Quantities were sometimes failing. Also they were omitted from documentation.
#. Better handling of ``Infinite`` quantities.
#. Fix ``Precision`` compatibility with WMA.


PyPI Package requirements
+++++++++++++++++++++++++

Expand Down
2 changes: 1 addition & 1 deletion mathics/builtin/arithfns/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ def eval(self, items, evaluation):
)
elif item.get_head().sameQ(SymbolDirectedInfinity):
infinity_factor = True
if len(item.elements) > 1:
if len(item.elements) > 0:
direction = item.elements[0]
if isinstance(direction, Number):
numbers.append(direction)
Expand Down
24 changes: 18 additions & 6 deletions mathics/builtin/arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from mathics.core.convert.expression import to_expression
from mathics.core.convert.mpmath import from_mpmath
from mathics.core.convert.sympy import SympyExpression, from_sympy, sympy_symbol_prefix
from mathics.core.element import ElementsProperties
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.number import SpecialValueError, dps, min_prec
Expand Down Expand Up @@ -658,9 +659,9 @@ class DirectedInfinity(SympyFunction):
"DirectedInfinity[Indeterminate]": "Indeterminate",
"DirectedInfinity[args___] ^ -1": "0",
"0 * DirectedInfinity[args___]": "Message[Infinity::indet, Unevaluated[0 DirectedInfinity[args]]]; Indeterminate",
"DirectedInfinity[a_?NumericQ] /; N[Abs[a]] != 1": "DirectedInfinity[a / Abs[a]]",
"DirectedInfinity[a_] * DirectedInfinity[b_]": "DirectedInfinity[a*b]",
"DirectedInfinity[] * DirectedInfinity[args___]": "DirectedInfinity[]",
# "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.eval
# "z_?NumberQ * DirectedInfinity[]": "DirectedInfinity[]",
# "z_?NumberQ * DirectedInfinity[a_]": "DirectedInfinity[z * a]",
Expand All @@ -685,9 +686,7 @@ class DirectedInfinity(SympyFunction):
" Unevaluated[DirectedInfinity[0.]]];"
"Indeterminate"
),
"DirectedInfinity[ComplexInfinity]": "ComplexInfinity",
"DirectedInfinity[Infinity]": "Infinity",
"DirectedInfinity[-Infinity]": "-Infinity",
"DirectedInfinity[DirectedInfinity[x___]]": "DirectedInfinity[x]",
}

formats = {
Expand All @@ -698,6 +697,19 @@ class DirectedInfinity(SympyFunction):
"DirectedInfinity[z_?NumericQ]": "HoldForm[z Infinity]",
}

def eval(self, z, evaluation):
"""DirectedInfinity[z_]"""
if z in (Integer1, IntegerM1):
return None
if isinstance(z, Number) or isinstance(eval_N(z, evaluation), Number):
direction = (z / Expression(SymbolAbs, z)).evaluate(evaluation)
return Expression(
SymbolDirectedInfinity,
direction,
elements_properties=ElementsProperties(True, True, True),
)
return None

def to_sympy(self, expr, **kwargs):
if len(expr.elements) == 1:
dir = expr.elements[0].get_int_value()
Expand Down
17 changes: 5 additions & 12 deletions mathics/builtin/numbers/hyperbolic.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ class ArcCosh(_MPMathFunction):

rules = {
"ArcCosh[Undefined]": "Undefined",
"ArcCosh[I Infinity]": "Infinity",
"ArcCosh[-I Infinity]": "Infinity",
"ArcCosh[ComplexInfinity]": "Infinity",
"ArcCosh[DirectedInfinity[I]]": "Infinity",
"ArcCosh[DirectedInfinity[-I]]": "Infinity",
"ArcCosh[DirectedInfinity[]]": "Infinity",
"Derivative[1][ArcCosh]": "1/(Sqrt[#-1]*Sqrt[#+1])&",
}
summary_text = "inverse hyperbolic cosine function"
Expand Down Expand Up @@ -324,15 +324,8 @@ class Gudermannian(Builtin):
"Gudermannian[Undefined]": "Undefined",
"Gudermannian[0]": "0",
"Gudermannian[2*Pi*I]": "0",
# FIXME: handling DirectedInfinity[-I] leads to problems
# "Gudermannian[6/4 Pi I]": "DirectedInfinity[-I]",
# Handled already
# "Gudermannian[Infinity]": "Pi/2",
# FIXME: Pi/2 from Sympy seems to take precedence
"Gudermannian[-Infinity]": "-Pi/2",
# Below, we don't use instead of ComplexInfinity that gets
# substituted out for DirectedInfinity[] before we match on
# Gudermannian[...]
"Gudermannian[3 I / 2 Pi]": "DirectedInfinity[-I]",
"Gudermannian[DirectedInfinity[-1]]": "-Pi/2",
"Gudermannian[DirectedInfinity[]]": "Indeterminate",
"Gudermannian[z_]": "2 ArcTan[Tanh[z / 2]]",
# Commented out because := might not work properly
Expand Down
7 changes: 7 additions & 0 deletions mathics/core/convert/mpmath.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
@lru_cache(maxsize=1024)
def from_mpmath(value, prec=None, acc=None):
"Converts mpf or mpc to Number."
if mpmath.isnan(value):
return SymbolIndeterminate
if isinstance(value, mpmath.mpf):
if mpmath.isinf(value):
direction = Integer1 if value > 0 else IntegerM1
return Expression(SymbolDirectedInfinity, direction)
# if accuracy is given, override
# prec:
if acc is not None:
Expand All @@ -28,6 +33,8 @@ def from_mpmath(value, prec=None, acc=None):
# HACK: use str here to prevent loss of precision
return PrecisionReal(sympy.Float(str(value), prec))
elif isinstance(value, mpmath.mpc):
if mpmath.isinf(value):
return SymbolComplexInfinity
if value.imag == 0.0:
return from_mpmath(value.real, prec, acc)
real = from_mpmath(value.real, prec, acc)
Expand Down
10 changes: 7 additions & 3 deletions mathics/core/convert/sympy.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,14 @@ def from_sympy(expr):
elif expr is sympy.false:
return SymbolFalse

elif expr.is_number and all([x.is_Number for x in expr.as_real_imag()]):
if expr.is_number and all([x.is_Number for x in expr.as_real_imag()]):
# Hack to convert <Integer> * I to Complex[0, <Integer>]
return Complex(*[from_sympy(arg) for arg in expr.as_real_imag()])
elif expr.is_Add:
try:
return Complex(*[from_sympy(arg) for arg in expr.as_real_imag()])
except ValueError:
# The exception happens if one of the components is infinity
pass
if expr.is_Add:
return to_expression(
SymbolPlus, *sorted([from_sympy(arg) for arg in expr.args])
)
Expand Down
8 changes: 7 additions & 1 deletion mathics/core/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,13 @@ def get_sort_key(self, pattern_sort=False) -> tuple:
exps[name] = exps.get(name, 0) + 1
elif self.has_form("Power", 2):
var = self._elements[0].get_name()
exp = self._elements[1].round_to_float()
# TODO: Check if this is the expected behaviour.
# round_to_float is an attribute of Expression,
# but not for Atoms.
try:
exp = self._elements[1].round_to_float()
except AttributeError:
exp = None
if var and exp is not None:
exps[var] = exps.get(var, 0) + exp
if exps:
Expand Down
47 changes: 41 additions & 6 deletions test/builtin/arithmetic/test_abs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,45 @@
"""
from test.helper import check_evaluation

import pytest

def test_abs():
for str_expr, str_expected in [
("Abs[a - b]", "Abs[a - b]"),
("Abs[Sqrt[3]]", "Sqrt[3]"),
]:
check_evaluation(str_expr, str_expected)

@pytest.mark.parametrize(
("str_expr", "str_expected", "msg"),
[
("Abs[a - b]", "Abs[a - b]", None),
("Abs[Sqrt[3]]", "Sqrt[3]", None),
("Abs[Sqrt[3]/5]", "Sqrt[3]/5", None),
("Abs[-2/3]", "2/3", None),
("Abs[2+3 I]", "Sqrt[13]", None),
("Abs[2.+3 I]", "3.60555", None),
# TODO: Implement rules for these cases.
# ("Abs[4^(2 Pi)]", "4^(2 Pi)", None),
],
)
def test_abs(str_expr, str_expected, msg):
check_evaluation(str_expr, str_expected, failure_message=msg)


@pytest.mark.parametrize(
("str_expr", "str_expected", "msg"),
[
("Sign[a - b]", "Sign[a - b]", None),
("Sign[Sqrt[3]]", "1", None),
("Sign[0]", "0", None),
("Sign[0.]", "0", None),
("Sign[(1 + I)]", "(1/2 + I/2)Sqrt[2]", None),
("Sign[(1. + I)]", "(0.707107 + 0.707107 I)", None),
("Sign[(1 + I)/Sqrt[2]]", "(1 + I)/Sqrt[2]", None),
("Sign[(1 + I)/Sqrt[2.]]", "(0.707107 + 0.707107 I)", None),
("Sign[-2/3]", "-1", None),
("Sign[2+3 I]", "(2 + 3 I)/(13^(1/2))", None),
("Sign[2.+3 I]", "0.5547 + 0.83205 I", None),
("Sign[4^(2 Pi)]", "1", None),
# FixME: add rules to handle this kind of case
# ("Sign[I^(2 Pi)]", "I^(2 Pi)", None),
# ("Sign[4^(2 Pi I)]", "1", None),
],
)
def test_sign(str_expr, str_expected, msg):
check_evaluation(str_expr, str_expected, failure_message=msg)
Loading

0 comments on commit 1b3641d

Please sign in to comment.