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
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Enhancements
``Compile[] and CompiledFunction[]`` every expression can have a compiled form,
as a Python function.
* ``Equal[]`` now compares complex against other numbers properly.

* Improvements in handling products with infinite factors: ``0 Infinity``-> ``Indeterminate``, and ``expr Infinity``-> ``DirectedInfinite[expr]``

Bug fixes
+++++++++
Expand Down
65 changes: 56 additions & 9 deletions mathics/builtin/arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
SymbolFalse,
SymbolNull,
SymbolTrue,
SymbolList,
SymbolInfinity,
SymbolDirectedInfinity,
SymbolComplexInfinity,
from_python,
from_mpmath,
)
Expand All @@ -42,6 +46,7 @@
from mathics.builtin.lists import _IterationFunction
from mathics.core.convert import from_sympy, SympyExpression


@lru_cache(maxsize=1024)
def call_mpmath(mpmath_function, mpmath_args):
try:
Expand Down Expand Up @@ -642,10 +647,10 @@ def format_outputform(self, items, evaluation):

def apply(self, items, evaluation):
"Times[items___]"

items = items.numerify(evaluation).get_sequence()
leaves = []
numbers = []
infinity_factor = False

prec = min_prec(*items)
is_machine_precision = any(item.is_machine_precision() for item in items)
Expand Down Expand Up @@ -681,6 +686,16 @@ def apply(self, items, evaluation):
leaves[-1] = Expression(
"Power", item, Expression("Plus", Integer(1), leaves[-1].leaves[1])
)
elif item.get_head().same(SymbolDirectedInfinity):
infinity_factor = True
if len(item.leaves)>1:
direction = item.leaves[0]
if isinstance(direction, Number):
numbers.append(direction)
else:
leaves.append(direction)
elif item.same(SymbolInfinity) or item.same(SymbolComplexInfinity):
infinity_factor = True
else:
leaves.append(item)

Expand All @@ -704,6 +719,8 @@ def apply(self, items, evaluation):
if number.same(Integer(1)):
number = None
elif number.is_zero:
if infinity_factor:
return Symbol("Indeterminate")
return number
elif number.same(Integer(-1)) and leaves and leaves[0].has_form("Plus", None):
leaves[0] = Expression(
Expand All @@ -719,11 +736,18 @@ def apply(self, items, evaluation):
leaves.insert(0, number)

if not leaves:
if infinity_factor:
return SymbolComplexInfinity
return Integer(1)
elif len(leaves) == 1:
return leaves[0]

if len(leaves) == 1:
ret = leaves[0]
else:
return Expression("Times", *leaves)
ret = Expression("Times", *leaves)
if infinity_factor:
return Expression(SymbolDirectedInfinity, ret)
else:
return ret


class Divide(BinaryOperator):
Expand Down Expand Up @@ -857,7 +881,7 @@ class Power(BinaryOperator, _MPMathFunction):
#> (3/2+1/2I)^2
= 2 + 3 I / 2
#> I ^ I
= I ^ I
= -1 ^ (I / 2)

#> 2 ^ 2.0
= 4.
Expand Down Expand Up @@ -905,6 +929,9 @@ class Power(BinaryOperator, _MPMathFunction):
("", "x_ ^ y_?Negative"): (
"HoldForm[Divide[1, #]]&[If[y==-1, HoldForm[x], HoldForm[x]^-y]]"
),
("", "x_?Negative ^ y_"): (
'Infix[{HoldForm[(x)], HoldForm[y]},"^", 590, Right]'
),
}

rules = {
Expand All @@ -931,6 +958,10 @@ def apply_check(self, x, y, evaluation):
elif py_y < 0:
evaluation.message("Power", "infy", Expression("Power", x, y_err))
return Symbol("ComplexInfinity")
if isinstance(x, Complex) and x.real.is_zero:
yhalf = Expression("Times", y, Rational(1, 2))
factor = self.apply(Expression("Sequence", x.imag, y), evaluation)
return Expression("Times", factor, Expression("Power", Integer(-1), yhalf))

result = self.apply(Expression("Sequence", x, y), evaluation)
if result is None or result != SymbolNull:
Expand Down Expand Up @@ -1049,6 +1080,10 @@ class DirectedInfinity(SympyFunction):
: Indeterminate expression -Infinity + Infinity encountered.
= Indeterminate

>> DirectedInfinity[0]
: Indeterminate expression 0 Infinity encountered.
= Indeterminate

#> DirectedInfinity[1+I]+DirectedInfinity[2+I]
= (2 / 5 + I / 5) Sqrt[5] Infinity + (1 / 2 + I / 2) Sqrt[2] Infinity

Expand All @@ -1057,14 +1092,15 @@ class DirectedInfinity(SympyFunction):
"""

rules = {
"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[0]": "DirectedInfinity[]",
"z_?NumberQ * DirectedInfinity[]": "DirectedInfinity[]",
"z_?NumberQ * DirectedInfinity[a_]": "DirectedInfinity[z * a]",
# Rules already implemented in Times.apply
# "z_?NumberQ * DirectedInfinity[]": "DirectedInfinity[]",
# "z_?NumberQ * DirectedInfinity[a_]": "DirectedInfinity[z * a]",
"DirectedInfinity[a_] + DirectedInfinity[b_] /; b == -a": (
"Message[Infinity::indet,"
" Unevaluated[DirectedInfinity[a] + DirectedInfinity[b]]];"
Expand All @@ -1076,12 +1112,23 @@ class DirectedInfinity(SympyFunction):
"Indeterminate"
),
"DirectedInfinity[args___] + _?NumberQ": "DirectedInfinity[args]",
"DirectedInfinity[0]": (
"Message[Infinity::indet,"
" Unevaluated[DirectedInfinity[0]]];"
"Indeterminate"
),
"DirectedInfinity[0.]": (
"Message[Infinity::indet,"
" Unevaluated[DirectedInfinity[0.]]];"
"Indeterminate"
),
}

formats = {
"DirectedInfinity[1]": "HoldForm[Infinity]",
"DirectedInfinity[-1]": "HoldForm[-Infinity]",
"DirectedInfinity[]": "HoldForm[ComplexInfinity]",
"DirectedInfinity[DirectedInfinity[z_]]": "DirectedInfinity[z]",
"DirectedInfinity[z_?NumericQ]": "HoldForm[z Infinity]",
}

Expand Down Expand Up @@ -1365,7 +1412,7 @@ def apply(self, expr, evaluation):
sympy_expr = expr.to_sympy()
result = _iszero(sympy_expr)
if result is None:
# try expanding the expression
# try expanding the expression
exprexp = Expression("ExpandAll", expr).evaluate(evaluation)
exprexp = exprexp.to_sympy()
result = _iszero(exprexp)
Expand Down
10 changes: 5 additions & 5 deletions mathics/builtin/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
PrecisionReal,
String,
Symbol,
SymbolTrue,
SymbolFalse,
ensure_context,
strip_context,
)
Expand Down Expand Up @@ -445,6 +447,7 @@ def get_sympy_names(self) -> typing.List[str]:
return [self.sympy_name]
return []


class UnaryOperator(Operator):
def __init__(self, format_function, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -526,13 +529,12 @@ def apply(self, expr, evaluation) -> Symbol:
"%(name)s[expr_]"

if self.test(expr):
return Symbol("True")
return SymbolTrue
else:
return Symbol("False")
return SymbolFalse


class SympyFunction(SympyObject):

def apply(self, *args):
"""
Generic apply method that uses the class sympy_name.
Expand Down Expand Up @@ -583,8 +585,6 @@ def prepare_mathics(self, sympy_expr):
return sympy_expr




class InvalidLevelspecError(Exception):
pass

Expand Down
4 changes: 4 additions & 0 deletions mathics/core/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -2014,6 +2014,10 @@ def __getnewargs__(self):
SymbolTrue = Symbol("True")
SymbolAborted = Symbol("$Aborted")
SymbolInfinity = Symbol("Infinity")
SymbolComplexInfinity = Symbol("ComplexInfinity")
SymbolDirectedInfinity = Symbol("DirectedInfinity")
SymbolList = Symbol("List")


@lru_cache(maxsize=1024)
def from_mpmath(value, prec=None):
Expand Down
33 changes: 25 additions & 8 deletions mathics/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

MAX_TESTS = 100000 # Number than the total number of tests

logfile = None

class TestOutput(Output):
def max_stored_size(self, settings):
Expand All @@ -36,6 +37,13 @@ def max_stored_size(self, settings):
documentation = None


def print_and_log(*args):
global logfile
string = "".join(args)
print(string)
if logfile:
logfile.write(string)

def compare(result, wanted):
if result == wanted:
return True
Expand Down Expand Up @@ -64,7 +72,7 @@ def test_case(test, tests, index=0, subindex=0, quiet=False, section=None):

def fail(why):
part, chapter, section = tests.part, tests.chapter, tests.section
print(
print_and_log(
"%sTest failed: %s in %s / %s\n%s\n%s\n"
% (sep, section, part, chapter, test, why)
)
Expand Down Expand Up @@ -198,9 +206,9 @@ def test_section(sections: set, quiet=False, stop_on_failure=False):

print()
if failed > 0:
print("%d test%s failed." % (failed, "s" if failed != 1 else ""))
print_and_log("%d test%s failed." % (failed, "s" if failed != 1 else ""))
else:
print("OK")
print_and_log("OK")


def open_ensure_dir(f, *args, **kwargs):
Expand Down Expand Up @@ -265,21 +273,21 @@ def test_all(
if failed > 0:
print(sep)
if count == MAX_TESTS:
print(
print_and_log(
"%d Tests for %d built-in symbols, %d passed, %d failed, %d skipped."
% (total, builtin_total, total - failed - skipped, failed, skipped)
)
else:
print(
print_and_log(
"%d Tests, %d passed, %d failed, %d skipped."
% (total, total - failed, failed, skipped)
)
if failed_symbols:
if stop_on_failure:
print("(not all tests are accounted for due to --stop-on-failure)")
print("Failed:")
print_and_log("(not all tests are accounted for due to --stop-on-failure)")
print_and_log("Failed:")
for part, chapter, section in sorted(failed_symbols):
print(" - %s in %s / %s" % (section, part, chapter))
print_and_log(" - %s in %s / %s" % (section, part, chapter))

if generate_output and (failed == 0 or doc_even_if_error):
print("Save XML")
Expand Down Expand Up @@ -341,6 +349,7 @@ def main():

global definitions
global documentation
global logfile
definitions = Definitions(add_builtin=True)
documentation = main_mathics_documentation

Expand All @@ -355,6 +364,9 @@ def main():
"--sections", "-s", dest="section", metavar="SECTION", help="only test SECTION(s). "
"You can list multiple sections by adding a comma (and no space) in between section names."
)
parser.add_argument(
"--logfile", "-f", dest="logfilename", metavar="LOGFILENAME", help="stores the output in [logfilename]. "
)
parser.add_argument(
"--pymathics",
"-l",
Expand Down Expand Up @@ -415,6 +427,9 @@ def main():
args = parser.parse_args()
# If a test for a specific section is called
# just test it
if args.logfilename:
logfile = open(args.logfilename,"wt")

if args.section:
sections = set(args.section.split(","))
if args.pymathics: # in case the section is in a pymathics module...
Expand Down Expand Up @@ -446,6 +461,8 @@ def main():
# If TeX output requested, try to build it:
if args.tex:
write_latex()
if logfile:
logfile.close()


if __name__ == "__main__":
Expand Down