diff --git a/CHANGES.rst b/CHANGES.rst index 511ad2aaf7..d3916ed404 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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 +++++++++ diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py index f52d69fcdb..cd8f1ec834 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -34,6 +34,10 @@ SymbolFalse, SymbolNull, SymbolTrue, + SymbolList, + SymbolInfinity, + SymbolDirectedInfinity, + SymbolComplexInfinity, from_python, from_mpmath, ) @@ -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: @@ -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) @@ -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) @@ -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( @@ -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): @@ -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. @@ -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 = { @@ -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: @@ -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 @@ -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]]];" @@ -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]", } @@ -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) diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index 009a985130..a4cc55063f 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -23,6 +23,8 @@ PrecisionReal, String, Symbol, + SymbolTrue, + SymbolFalse, ensure_context, strip_context, ) @@ -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) @@ -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. @@ -583,8 +585,6 @@ def prepare_mathics(self, sympy_expr): return sympy_expr - - class InvalidLevelspecError(Exception): pass diff --git a/mathics/core/expression.py b/mathics/core/expression.py index c31c2d6ba1..60458de108 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -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): diff --git a/mathics/test.py b/mathics/test.py index 6201f3337f..e8c31efd34 100644 --- a/mathics/test.py +++ b/mathics/test.py @@ -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): @@ -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 @@ -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) ) @@ -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): @@ -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") @@ -341,6 +349,7 @@ def main(): global definitions global documentation + global logfile definitions = Definitions(add_builtin=True) documentation = main_mathics_documentation @@ -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", @@ -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... @@ -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__":