diff --git a/CHANGES.rst b/CHANGES.rst index 34320f9a7e..5a1134a04b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,8 @@ Enhancements * ``ToString`` accepts an optional *form* parameter. * ``ToExpression`` handles multi-line string input * ``FileNames`` returns a sorted list (#1250). +* ``ReplaceRepeated`` and ``FixedPoint`` now supports the ``MaxIteration`` option (#1260). +* ``FixedPoint`` now supports the ``SameTest`` option. Bug fixes +++++++++ diff --git a/mathics/builtin/control.py b/mathics/builtin/control.py index 483e3a04b6..9e4c007fe5 100644 --- a/mathics/builtin/control.py +++ b/mathics/builtin/control.py @@ -506,7 +506,7 @@ class FixedPoint(Builtin):
'FixedPoint[$f$, $expr$]'
starting with $expr$, iteratively applies $f$ until the result no longer changes.
'FixedPoint[$f$, $expr$, $n$]' -
performs at most $n$ iterations. +
performs at most $n$ iterations. The same that using $MaxIterations->n$ >> FixedPoint[Cos, 1.0] @@ -524,9 +524,13 @@ class FixedPoint(Builtin): = 0.739085 """ - def apply(self, f, expr, n, evaluation): - "FixedPoint[f_, expr_, n_:DirectedInfinity[1]]" + options = { + "MaxIterations": "Infinity", + "SameTest": "Automatic", + } + def apply(self, f, expr, n, evaluation, options): + "FixedPoint[f_, expr_, n_:DirectedInfinity[1], OptionsPattern[FixedPoint]]" if n == Expression("DirectedInfinity", 1): count = None else: @@ -534,14 +538,32 @@ def apply(self, f, expr, n, evaluation): if count is None or count < 0: evaluation.message("FixedPoint", "intnn") return + + if count is None: + count = self.get_option(options, "MaxIterations", evaluation) + if count.is_numeric(): + count = count.get_int_value() + else: + count = None + result = expr index = 0 + sametest = self.get_option(options, "SameTest", evaluation) + if sametest == Symbol("Automatic"): + sametest = None + while count is None or index < count: evaluation.check_stopped() new_result = Expression(f, result).evaluate(evaluation) - if new_result == result: - result = new_result - break + if sametest: + same = Expression(sametest, result, new_result).evaluate(evaluation) + same = same.is_true() + if same: + break + else: + if new_result == result: + result = new_result + break result = new_result index += 1 diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 033753318c..0d9f3e4c1e 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -305,6 +305,11 @@ class ReplaceRepeated(BinaryOperator): >> a+b+c //. c->d = a + b + d + >> f = ReplaceRepeated[c->d]; + >> f[a+b+c] + = a + b + d + >> Clear[f]; + Simplification of logarithms: >> logrules = {Log[x_ * y_] :> Log[x] + Log[y], Log[x_ ^ y_] :> y * Log[x]}; >> Log[a * (b * c) ^ d ^ e * f] //. logrules @@ -324,8 +329,16 @@ class ReplaceRepeated(BinaryOperator): "rmix": "Elements of `1` are a mixture of lists and nonlists.", } - def apply_list(self, expr, rules, evaluation): - "ReplaceRepeated[expr_, rules_]" + options = { + "MaxIterations": "65535", + } + + rules = { + "ReplaceRepeated[rules_][expr_]": "ReplaceRepeated[expr, rules]", + } + + def apply_list(self, expr, rules, evaluation, options): + "ReplaceRepeated[expr_, rules_, OptionsPattern[ReplaceRepeated]]" try: rules, ret = create_rules(rules, expr, "ReplaceRepeated", evaluation) except PatternError: @@ -335,8 +348,17 @@ def apply_list(self, expr, rules, evaluation): if ret: return rules + maxit = self.get_option(options, "MaxIterations", evaluation) + if maxit.is_numeric(): + maxit = maxit.get_int_value() + else: + maxit = -1 + while True: evaluation.check_stopped() + if maxit == 0: + break + maxit -= 1 result, applied = expr.apply_rules(rules, evaluation) if applied: result = result.evaluate(evaluation)