diff --git a/mathics/core/builtin.py b/mathics/core/builtin.py index cd48318b2..87ba6df2e 100644 --- a/mathics/core/builtin.py +++ b/mathics/core/builtin.py @@ -1209,7 +1209,14 @@ def eval_iter_nostep(self, expr, i, imin, imax, evaluation): def eval_iter(self, expr, i, imin, imax, di, evaluation): "%(name)s[expr_, {i_Symbol, imin_, imax_, di_}]" - if isinstance(self, SympyFunction) and di.int_value == 1: + imin = imin.evaluate(evaluation) + imax = imax.evaluate(evaluation) + # With integer bounds the terms are just evaluated and combined below, as + # in Table; SymPy is needed only for symbolic bounds. Going through SymPy + # (summand and result converted there and back, then Together) costs far + # more than the sum itself when the terms are large symbolic expressions. + integer_bounds = isinstance(imin, Integer) and isinstance(imax, Integer) + if isinstance(self, SympyFunction) and di.int_value == 1 and not integer_bounds: whole_expr = to_expression( self.get_name(), expr, ListExpression(i, imin, imax) ) @@ -1227,8 +1234,6 @@ def eval_iter(self, expr, i, imin, imax, di, evaluation): return index = Integer0 - imin = imin.evaluate(evaluation) - imax = imax.evaluate(evaluation) di = di.evaluate(evaluation) # (imax - imin) / di diff --git a/test/builtin/test_arithmetic.py b/test/builtin/test_arithmetic.py index aa4a0bd19..3b5a6f77d 100644 --- a/test/builtin/test_arithmetic.py +++ b/test/builtin/test_arithmetic.py @@ -3,7 +3,7 @@ Unit tests from mathics.builtin.arithmetic """ -from test.helper import check_arg_counts +from test.helper import check_arg_counts, check_evaluation import pytest @@ -60,3 +60,30 @@ def test_arithmetic_arg_errors(function_name, msg_fragment): """ """ check_arg_counts(function_name, msg_fragment) + + +@pytest.mark.parametrize( + ("str_expr", "str_expected", "fail_msg"), + [ + ("Sum[i, {i, 1, 10}]", "55", None), + ("Sum[f[i], {i, 0, 3}]", "f[0] + f[1] + f[2] + f[3]", None), + ("Sum[x^i, {i, 0, 3}]", "1 + x + x ^ 2 + x ^ 3", None), + ( + "Sum[1/(x + i), {i, 1, 2}]", + "1 / (1 + x) + 1 / (2 + x)", + "With integer bounds the terms are added, not put over a common denominator", + ), + ("Sum[i, {i, 1, 0}]", "0", "Empty range"), + ("Product[x + i, {i, 1, 3}]", "(1 + x) (2 + x) (3 + x)", None), + ("Product[i, {i, 1, 0}]", "1", "Empty range"), + ( + "Sum[k, {k, 1, n}]", + "n (1 + n) / 2", + "Symbolic bounds still go through SymPy", + ), + ("Sum[1/k^2, {k, 1, Infinity}]", "Pi ^ 2 / 6", None), + ], +) +def test_sum_product_bounds(str_expr, str_expected, fail_msg): + """Sum and Product with integer and with symbolic bounds""" + check_evaluation(str_expr, str_expected, failure_message=fail_msg)