Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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