Skip to content

Commit 1b3641d

Browse files
authored
Improve infinity and Precision (#767)
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.
1 parent bb8d3e6 commit 1b3641d

File tree

10 files changed

+286
-31
lines changed

10 files changed

+286
-31
lines changed

CHANGES.rst

+3
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ Bugs
9191
#. Some scikit image routines line ``EdgeDetect`` were getting omitted due to overly stringent PYPI requirements
9292

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

97+
9598
PyPI Package requirements
9699
+++++++++++++++++++++++++
97100

mathics/builtin/arithfns/basic.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,7 @@ def eval(self, items, evaluation):
979979
)
980980
elif item.get_head().sameQ(SymbolDirectedInfinity):
981981
infinity_factor = True
982-
if len(item.elements) > 1:
982+
if len(item.elements) > 0:
983983
direction = item.elements[0]
984984
if isinstance(direction, Number):
985985
numbers.append(direction)

mathics/builtin/arithmetic.py

+18-6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
from mathics.core.convert.expression import to_expression
5050
from mathics.core.convert.mpmath import from_mpmath
5151
from mathics.core.convert.sympy import SympyExpression, from_sympy, sympy_symbol_prefix
52+
from mathics.core.element import ElementsProperties
5253
from mathics.core.expression import Expression
5354
from mathics.core.list import ListExpression
5455
from mathics.core.number import SpecialValueError, dps, min_prec
@@ -658,9 +659,9 @@ class DirectedInfinity(SympyFunction):
658659
"DirectedInfinity[Indeterminate]": "Indeterminate",
659660
"DirectedInfinity[args___] ^ -1": "0",
660661
"0 * DirectedInfinity[args___]": "Message[Infinity::indet, Unevaluated[0 DirectedInfinity[args]]]; Indeterminate",
661-
"DirectedInfinity[a_?NumericQ] /; N[Abs[a]] != 1": "DirectedInfinity[a / Abs[a]]",
662-
"DirectedInfinity[a_] * DirectedInfinity[b_]": "DirectedInfinity[a*b]",
663-
"DirectedInfinity[] * DirectedInfinity[args___]": "DirectedInfinity[]",
662+
# "DirectedInfinity[a_?NumericQ] /; N[Abs[a]] != 1": "DirectedInfinity[a / Abs[a]]",
663+
# "DirectedInfinity[a_] * DirectedInfinity[b_]": "DirectedInfinity[a*b]",
664+
# "DirectedInfinity[] * DirectedInfinity[args___]": "DirectedInfinity[]",
664665
# Rules already implemented in Times.eval
665666
# "z_?NumberQ * DirectedInfinity[]": "DirectedInfinity[]",
666667
# "z_?NumberQ * DirectedInfinity[a_]": "DirectedInfinity[z * a]",
@@ -685,9 +686,7 @@ class DirectedInfinity(SympyFunction):
685686
" Unevaluated[DirectedInfinity[0.]]];"
686687
"Indeterminate"
687688
),
688-
"DirectedInfinity[ComplexInfinity]": "ComplexInfinity",
689-
"DirectedInfinity[Infinity]": "Infinity",
690-
"DirectedInfinity[-Infinity]": "-Infinity",
689+
"DirectedInfinity[DirectedInfinity[x___]]": "DirectedInfinity[x]",
691690
}
692691

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

700+
def eval(self, z, evaluation):
701+
"""DirectedInfinity[z_]"""
702+
if z in (Integer1, IntegerM1):
703+
return None
704+
if isinstance(z, Number) or isinstance(eval_N(z, evaluation), Number):
705+
direction = (z / Expression(SymbolAbs, z)).evaluate(evaluation)
706+
return Expression(
707+
SymbolDirectedInfinity,
708+
direction,
709+
elements_properties=ElementsProperties(True, True, True),
710+
)
711+
return None
712+
701713
def to_sympy(self, expr, **kwargs):
702714
if len(expr.elements) == 1:
703715
dir = expr.elements[0].get_int_value()

mathics/builtin/numbers/hyperbolic.py

+5-12
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ class ArcCosh(_MPMathFunction):
5858

5959
rules = {
6060
"ArcCosh[Undefined]": "Undefined",
61-
"ArcCosh[I Infinity]": "Infinity",
62-
"ArcCosh[-I Infinity]": "Infinity",
63-
"ArcCosh[ComplexInfinity]": "Infinity",
61+
"ArcCosh[DirectedInfinity[I]]": "Infinity",
62+
"ArcCosh[DirectedInfinity[-I]]": "Infinity",
63+
"ArcCosh[DirectedInfinity[]]": "Infinity",
6464
"Derivative[1][ArcCosh]": "1/(Sqrt[#-1]*Sqrt[#+1])&",
6565
}
6666
summary_text = "inverse hyperbolic cosine function"
@@ -324,15 +324,8 @@ class Gudermannian(Builtin):
324324
"Gudermannian[Undefined]": "Undefined",
325325
"Gudermannian[0]": "0",
326326
"Gudermannian[2*Pi*I]": "0",
327-
# FIXME: handling DirectedInfinity[-I] leads to problems
328-
# "Gudermannian[6/4 Pi I]": "DirectedInfinity[-I]",
329-
# Handled already
330-
# "Gudermannian[Infinity]": "Pi/2",
331-
# FIXME: Pi/2 from Sympy seems to take precedence
332-
"Gudermannian[-Infinity]": "-Pi/2",
333-
# Below, we don't use instead of ComplexInfinity that gets
334-
# substituted out for DirectedInfinity[] before we match on
335-
# Gudermannian[...]
327+
"Gudermannian[3 I / 2 Pi]": "DirectedInfinity[-I]",
328+
"Gudermannian[DirectedInfinity[-1]]": "-Pi/2",
336329
"Gudermannian[DirectedInfinity[]]": "Indeterminate",
337330
"Gudermannian[z_]": "2 ArcTan[Tanh[z / 2]]",
338331
# Commented out because := might not work properly

mathics/core/convert/mpmath.py

+7
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
@lru_cache(maxsize=1024)
1212
def from_mpmath(value, prec=None, acc=None):
1313
"Converts mpf or mpc to Number."
14+
if mpmath.isnan(value):
15+
return SymbolIndeterminate
1416
if isinstance(value, mpmath.mpf):
17+
if mpmath.isinf(value):
18+
direction = Integer1 if value > 0 else IntegerM1
19+
return Expression(SymbolDirectedInfinity, direction)
1520
# if accuracy is given, override
1621
# prec:
1722
if acc is not None:
@@ -28,6 +33,8 @@ def from_mpmath(value, prec=None, acc=None):
2833
# HACK: use str here to prevent loss of precision
2934
return PrecisionReal(sympy.Float(str(value), prec))
3035
elif isinstance(value, mpmath.mpc):
36+
if mpmath.isinf(value):
37+
return SymbolComplexInfinity
3138
if value.imag == 0.0:
3239
return from_mpmath(value.real, prec, acc)
3340
real = from_mpmath(value.real, prec, acc)

mathics/core/convert/sympy.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,14 @@ def from_sympy(expr):
252252
elif expr is sympy.false:
253253
return SymbolFalse
254254

255-
elif expr.is_number and all([x.is_Number for x in expr.as_real_imag()]):
255+
if expr.is_number and all([x.is_Number for x in expr.as_real_imag()]):
256256
# Hack to convert <Integer> * I to Complex[0, <Integer>]
257-
return Complex(*[from_sympy(arg) for arg in expr.as_real_imag()])
258-
elif expr.is_Add:
257+
try:
258+
return Complex(*[from_sympy(arg) for arg in expr.as_real_imag()])
259+
except ValueError:
260+
# The exception happens if one of the components is infinity
261+
pass
262+
if expr.is_Add:
259263
return to_expression(
260264
SymbolPlus, *sorted([from_sympy(arg) for arg in expr.args])
261265
)

mathics/core/expression.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,13 @@ def get_sort_key(self, pattern_sort=False) -> tuple:
862862
exps[name] = exps.get(name, 0) + 1
863863
elif self.has_form("Power", 2):
864864
var = self._elements[0].get_name()
865-
exp = self._elements[1].round_to_float()
865+
# TODO: Check if this is the expected behaviour.
866+
# round_to_float is an attribute of Expression,
867+
# but not for Atoms.
868+
try:
869+
exp = self._elements[1].round_to_float()
870+
except AttributeError:
871+
exp = None
866872
if var and exp is not None:
867873
exps[var] = exps.get(var, 0) + exp
868874
if exps:

test/builtin/arithmetic/test_abs.py

+41-6
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,45 @@
44
"""
55
from test.helper import check_evaluation
66

7+
import pytest
78

8-
def test_abs():
9-
for str_expr, str_expected in [
10-
("Abs[a - b]", "Abs[a - b]"),
11-
("Abs[Sqrt[3]]", "Sqrt[3]"),
12-
]:
13-
check_evaluation(str_expr, str_expected)
9+
10+
@pytest.mark.parametrize(
11+
("str_expr", "str_expected", "msg"),
12+
[
13+
("Abs[a - b]", "Abs[a - b]", None),
14+
("Abs[Sqrt[3]]", "Sqrt[3]", None),
15+
("Abs[Sqrt[3]/5]", "Sqrt[3]/5", None),
16+
("Abs[-2/3]", "2/3", None),
17+
("Abs[2+3 I]", "Sqrt[13]", None),
18+
("Abs[2.+3 I]", "3.60555", None),
19+
# TODO: Implement rules for these cases.
20+
# ("Abs[4^(2 Pi)]", "4^(2 Pi)", None),
21+
],
22+
)
23+
def test_abs(str_expr, str_expected, msg):
24+
check_evaluation(str_expr, str_expected, failure_message=msg)
25+
26+
27+
@pytest.mark.parametrize(
28+
("str_expr", "str_expected", "msg"),
29+
[
30+
("Sign[a - b]", "Sign[a - b]", None),
31+
("Sign[Sqrt[3]]", "1", None),
32+
("Sign[0]", "0", None),
33+
("Sign[0.]", "0", None),
34+
("Sign[(1 + I)]", "(1/2 + I/2)Sqrt[2]", None),
35+
("Sign[(1. + I)]", "(0.707107 + 0.707107 I)", None),
36+
("Sign[(1 + I)/Sqrt[2]]", "(1 + I)/Sqrt[2]", None),
37+
("Sign[(1 + I)/Sqrt[2.]]", "(0.707107 + 0.707107 I)", None),
38+
("Sign[-2/3]", "-1", None),
39+
("Sign[2+3 I]", "(2 + 3 I)/(13^(1/2))", None),
40+
("Sign[2.+3 I]", "0.5547 + 0.83205 I", None),
41+
("Sign[4^(2 Pi)]", "1", None),
42+
# FixME: add rules to handle this kind of case
43+
# ("Sign[I^(2 Pi)]", "I^(2 Pi)", None),
44+
# ("Sign[4^(2 Pi I)]", "1", None),
45+
],
46+
)
47+
def test_sign(str_expr, str_expected, msg):
48+
check_evaluation(str_expr, str_expected, failure_message=msg)

0 commit comments

Comments
 (0)