Skip to content

Commit 0894d8e

Browse files
authored
Merge pull request #1205 from mathics/fixtimesinfinity
Fixtimesinfinity
2 parents 7fb2fa1 + 6ba95a8 commit 0894d8e

5 files changed

Lines changed: 91 additions & 23 deletions

File tree

CHANGES.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Enhancements
2626
``Compile[] and CompiledFunction[]`` every expression can have a compiled form,
2727
as a Python function.
2828
* ``Equal[]`` now compares complex against other numbers properly.
29-
29+
* Improvements in handling products with infinite factors: ``0 Infinity``-> ``Indeterminate``, and ``expr Infinity``-> ``DirectedInfinite[expr]``
3030

3131
Bug fixes
3232
+++++++++

mathics/builtin/arithmetic.py

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
SymbolFalse,
3535
SymbolNull,
3636
SymbolTrue,
37+
SymbolList,
38+
SymbolInfinity,
39+
SymbolDirectedInfinity,
40+
SymbolComplexInfinity,
3741
from_python,
3842
from_mpmath,
3943
)
@@ -42,6 +46,7 @@
4246
from mathics.builtin.lists import _IterationFunction
4347
from mathics.core.convert import from_sympy, SympyExpression
4448

49+
4550
@lru_cache(maxsize=1024)
4651
def call_mpmath(mpmath_function, mpmath_args):
4752
try:
@@ -642,10 +647,10 @@ def format_outputform(self, items, evaluation):
642647

643648
def apply(self, items, evaluation):
644649
"Times[items___]"
645-
646650
items = items.numerify(evaluation).get_sequence()
647651
leaves = []
648652
numbers = []
653+
infinity_factor = False
649654

650655
prec = min_prec(*items)
651656
is_machine_precision = any(item.is_machine_precision() for item in items)
@@ -681,6 +686,16 @@ def apply(self, items, evaluation):
681686
leaves[-1] = Expression(
682687
"Power", item, Expression("Plus", Integer(1), leaves[-1].leaves[1])
683688
)
689+
elif item.get_head().same(SymbolDirectedInfinity):
690+
infinity_factor = True
691+
if len(item.leaves)>1:
692+
direction = item.leaves[0]
693+
if isinstance(direction, Number):
694+
numbers.append(direction)
695+
else:
696+
leaves.append(direction)
697+
elif item.same(SymbolInfinity) or item.same(SymbolComplexInfinity):
698+
infinity_factor = True
684699
else:
685700
leaves.append(item)
686701

@@ -704,6 +719,8 @@ def apply(self, items, evaluation):
704719
if number.same(Integer(1)):
705720
number = None
706721
elif number.is_zero:
722+
if infinity_factor:
723+
return Symbol("Indeterminate")
707724
return number
708725
elif number.same(Integer(-1)) and leaves and leaves[0].has_form("Plus", None):
709726
leaves[0] = Expression(
@@ -719,11 +736,18 @@ def apply(self, items, evaluation):
719736
leaves.insert(0, number)
720737

721738
if not leaves:
739+
if infinity_factor:
740+
return SymbolComplexInfinity
722741
return Integer(1)
723-
elif len(leaves) == 1:
724-
return leaves[0]
742+
743+
if len(leaves) == 1:
744+
ret = leaves[0]
725745
else:
726-
return Expression("Times", *leaves)
746+
ret = Expression("Times", *leaves)
747+
if infinity_factor:
748+
return Expression(SymbolDirectedInfinity, ret)
749+
else:
750+
return ret
727751

728752

729753
class Divide(BinaryOperator):
@@ -857,7 +881,7 @@ class Power(BinaryOperator, _MPMathFunction):
857881
#> (3/2+1/2I)^2
858882
= 2 + 3 I / 2
859883
#> I ^ I
860-
= I ^ I
884+
= -1 ^ (I / 2)
861885
862886
#> 2 ^ 2.0
863887
= 4.
@@ -905,6 +929,9 @@ class Power(BinaryOperator, _MPMathFunction):
905929
("", "x_ ^ y_?Negative"): (
906930
"HoldForm[Divide[1, #]]&[If[y==-1, HoldForm[x], HoldForm[x]^-y]]"
907931
),
932+
("", "x_?Negative ^ y_"): (
933+
'Infix[{HoldForm[(x)], HoldForm[y]},"^", 590, Right]'
934+
),
908935
}
909936

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

935966
result = self.apply(Expression("Sequence", x, y), evaluation)
936967
if result is None or result != SymbolNull:
@@ -1049,6 +1080,10 @@ class DirectedInfinity(SympyFunction):
10491080
: Indeterminate expression -Infinity + Infinity encountered.
10501081
= Indeterminate
10511082
1083+
>> DirectedInfinity[0]
1084+
: Indeterminate expression 0 Infinity encountered.
1085+
= Indeterminate
1086+
10521087
#> DirectedInfinity[1+I]+DirectedInfinity[2+I]
10531088
= (2 / 5 + I / 5) Sqrt[5] Infinity + (1 / 2 + I / 2) Sqrt[2] Infinity
10541089
@@ -1057,14 +1092,15 @@ class DirectedInfinity(SympyFunction):
10571092
"""
10581093

10591094
rules = {
1095+
"DirectedInfinity[Indeterminate]":"Indeterminate",
10601096
"DirectedInfinity[args___] ^ -1": "0",
10611097
"0 * DirectedInfinity[args___]": "Message[Infinity::indet, Unevaluated[0 DirectedInfinity[args]]]; Indeterminate",
10621098
"DirectedInfinity[a_?NumericQ] /; N[Abs[a]] != 1": "DirectedInfinity[a / Abs[a]]",
10631099
"DirectedInfinity[a_] * DirectedInfinity[b_]": "DirectedInfinity[a*b]",
10641100
"DirectedInfinity[] * DirectedInfinity[args___]": "DirectedInfinity[]",
1065-
"DirectedInfinity[0]": "DirectedInfinity[]",
1066-
"z_?NumberQ * DirectedInfinity[]": "DirectedInfinity[]",
1067-
"z_?NumberQ * DirectedInfinity[a_]": "DirectedInfinity[z * a]",
1101+
# Rules already implemented in Times.apply
1102+
# "z_?NumberQ * DirectedInfinity[]": "DirectedInfinity[]",
1103+
# "z_?NumberQ * DirectedInfinity[a_]": "DirectedInfinity[z * a]",
10681104
"DirectedInfinity[a_] + DirectedInfinity[b_] /; b == -a": (
10691105
"Message[Infinity::indet,"
10701106
" Unevaluated[DirectedInfinity[a] + DirectedInfinity[b]]];"
@@ -1076,12 +1112,23 @@ class DirectedInfinity(SympyFunction):
10761112
"Indeterminate"
10771113
),
10781114
"DirectedInfinity[args___] + _?NumberQ": "DirectedInfinity[args]",
1115+
"DirectedInfinity[0]": (
1116+
"Message[Infinity::indet,"
1117+
" Unevaluated[DirectedInfinity[0]]];"
1118+
"Indeterminate"
1119+
),
1120+
"DirectedInfinity[0.]": (
1121+
"Message[Infinity::indet,"
1122+
" Unevaluated[DirectedInfinity[0.]]];"
1123+
"Indeterminate"
1124+
),
10791125
}
10801126

10811127
formats = {
10821128
"DirectedInfinity[1]": "HoldForm[Infinity]",
10831129
"DirectedInfinity[-1]": "HoldForm[-Infinity]",
10841130
"DirectedInfinity[]": "HoldForm[ComplexInfinity]",
1131+
"DirectedInfinity[DirectedInfinity[z_]]": "DirectedInfinity[z]",
10851132
"DirectedInfinity[z_?NumericQ]": "HoldForm[z Infinity]",
10861133
}
10871134

@@ -1365,7 +1412,7 @@ def apply(self, expr, evaluation):
13651412
sympy_expr = expr.to_sympy()
13661413
result = _iszero(sympy_expr)
13671414
if result is None:
1368-
# try expanding the expression
1415+
# try expanding the expression
13691416
exprexp = Expression("ExpandAll", expr).evaluate(evaluation)
13701417
exprexp = exprexp.to_sympy()
13711418
result = _iszero(exprexp)

mathics/builtin/base.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
PrecisionReal,
2424
String,
2525
Symbol,
26+
SymbolTrue,
27+
SymbolFalse,
2628
ensure_context,
2729
strip_context,
2830
)
@@ -445,6 +447,7 @@ def get_sympy_names(self) -> typing.List[str]:
445447
return [self.sympy_name]
446448
return []
447449

450+
448451
class UnaryOperator(Operator):
449452
def __init__(self, format_function, *args, **kwargs):
450453
super().__init__(*args, **kwargs)
@@ -526,13 +529,12 @@ def apply(self, expr, evaluation) -> Symbol:
526529
"%(name)s[expr_]"
527530

528531
if self.test(expr):
529-
return Symbol("True")
532+
return SymbolTrue
530533
else:
531-
return Symbol("False")
534+
return SymbolFalse
532535

533536

534537
class SympyFunction(SympyObject):
535-
536538
def apply(self, *args):
537539
"""
538540
Generic apply method that uses the class sympy_name.
@@ -583,8 +585,6 @@ def prepare_mathics(self, sympy_expr):
583585
return sympy_expr
584586

585587

586-
587-
588588
class InvalidLevelspecError(Exception):
589589
pass
590590

mathics/core/expression.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2014,6 +2014,10 @@ def __getnewargs__(self):
20142014
SymbolTrue = Symbol("True")
20152015
SymbolAborted = Symbol("$Aborted")
20162016
SymbolInfinity = Symbol("Infinity")
2017+
SymbolComplexInfinity = Symbol("ComplexInfinity")
2018+
SymbolDirectedInfinity = Symbol("DirectedInfinity")
2019+
SymbolList = Symbol("List")
2020+
20172021

20182022
@lru_cache(maxsize=1024)
20192023
def from_mpmath(value, prec=None):

mathics/test.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
MAX_TESTS = 100000 # Number than the total number of tests
2525

26+
logfile = None
2627

2728
class TestOutput(Output):
2829
def max_stored_size(self, settings):
@@ -36,6 +37,13 @@ def max_stored_size(self, settings):
3637
documentation = None
3738

3839

40+
def print_and_log(*args):
41+
global logfile
42+
string = "".join(args)
43+
print(string)
44+
if logfile:
45+
logfile.write(string)
46+
3947
def compare(result, wanted):
4048
if result == wanted:
4149
return True
@@ -64,7 +72,7 @@ def test_case(test, tests, index=0, subindex=0, quiet=False, section=None):
6472

6573
def fail(why):
6674
part, chapter, section = tests.part, tests.chapter, tests.section
67-
print(
75+
print_and_log(
6876
"%sTest failed: %s in %s / %s\n%s\n%s\n"
6977
% (sep, section, part, chapter, test, why)
7078
)
@@ -198,9 +206,9 @@ def test_section(sections: set, quiet=False, stop_on_failure=False):
198206

199207
print()
200208
if failed > 0:
201-
print("%d test%s failed." % (failed, "s" if failed != 1 else ""))
209+
print_and_log("%d test%s failed." % (failed, "s" if failed != 1 else ""))
202210
else:
203-
print("OK")
211+
print_and_log("OK")
204212

205213

206214
def open_ensure_dir(f, *args, **kwargs):
@@ -265,21 +273,21 @@ def test_all(
265273
if failed > 0:
266274
print(sep)
267275
if count == MAX_TESTS:
268-
print(
276+
print_and_log(
269277
"%d Tests for %d built-in symbols, %d passed, %d failed, %d skipped."
270278
% (total, builtin_total, total - failed - skipped, failed, skipped)
271279
)
272280
else:
273-
print(
281+
print_and_log(
274282
"%d Tests, %d passed, %d failed, %d skipped."
275283
% (total, total - failed, failed, skipped)
276284
)
277285
if failed_symbols:
278286
if stop_on_failure:
279-
print("(not all tests are accounted for due to --stop-on-failure)")
280-
print("Failed:")
287+
print_and_log("(not all tests are accounted for due to --stop-on-failure)")
288+
print_and_log("Failed:")
281289
for part, chapter, section in sorted(failed_symbols):
282-
print(" - %s in %s / %s" % (section, part, chapter))
290+
print_and_log(" - %s in %s / %s" % (section, part, chapter))
283291

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

342350
global definitions
343351
global documentation
352+
global logfile
344353
definitions = Definitions(add_builtin=True)
345354
documentation = main_mathics_documentation
346355

@@ -355,6 +364,9 @@ def main():
355364
"--sections", "-s", dest="section", metavar="SECTION", help="only test SECTION(s). "
356365
"You can list multiple sections by adding a comma (and no space) in between section names."
357366
)
367+
parser.add_argument(
368+
"--logfile", "-f", dest="logfilename", metavar="LOGFILENAME", help="stores the output in [logfilename]. "
369+
)
358370
parser.add_argument(
359371
"--pymathics",
360372
"-l",
@@ -415,6 +427,9 @@ def main():
415427
args = parser.parse_args()
416428
# If a test for a specific section is called
417429
# just test it
430+
if args.logfilename:
431+
logfile = open(args.logfilename,"wt")
432+
418433
if args.section:
419434
sections = set(args.section.split(","))
420435
if args.pymathics: # in case the section is in a pymathics module...
@@ -446,6 +461,8 @@ def main():
446461
# If TeX output requested, try to build it:
447462
if args.tex:
448463
write_latex()
464+
if logfile:
465+
logfile.close()
449466

450467

451468
if __name__ == "__main__":

0 commit comments

Comments
 (0)