From ecc4fd988119a25f17e24015c692f771e5413f26 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 15 Feb 2026 10:51:27 -0500 Subject: [PATCH 1/3] Go over mathics.builtin.intfs.recurrence... * Add argument checking * Go through sympy tracking in GCD, LCM, SterlingS1, and SterlingS2 * expand tracing to handle keyword arguments * Move BernoulliB to "Recurrence and Sum" section * Go over docstrings and sort builtin class attributes --- mathics/builtin/intfns/divlike.py | 5 +- mathics/builtin/intfns/misc.py | 48 --------- mathics/builtin/intfns/recurrence.py | 139 ++++++++++++++++++--------- mathics/eval/tracing.py | 16 +-- 4 files changed, 106 insertions(+), 102 deletions(-) delete mode 100644 mathics/builtin/intfns/misc.py diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index 5fb5aede9..522898be8 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -10,6 +10,7 @@ import sympy from sympy import Q, ask +import mathics.eval.tracing as tracing from mathics.core.atoms import Integer from mathics.core.attributes import ( A_FLAT, @@ -130,7 +131,7 @@ def eval(self, ns, evaluation: Evaluation): value = n.value if value is None: return - result = sympy.gcd(result, value) + result = tracing.run_sympy(sympy.gcd, result, value) return Integer(result) @@ -169,7 +170,7 @@ def eval(self, ns: List[Integer], evaluation: Evaluation): value = n.value if value is None: return - result = sympy.lcm(result, value) + result = tracing.run_sympy(sympy.lcm, result, value) return Integer(result) diff --git a/mathics/builtin/intfns/misc.py b/mathics/builtin/intfns/misc.py deleted file mode 100644 index bea98cf07..000000000 --- a/mathics/builtin/intfns/misc.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Miscelanea of Integer Functions -""" - - -from mathics.core.attributes import A_LISTABLE, A_PROTECTED -from mathics.core.builtin import MPMathFunction - - -class BernoulliB(MPMathFunction): - """ - :WMA link:https://reference.wolfram.com/language/ref/BernoulliB.html - -
-
'BernoulliB'[$n$] -
represents the Bernoulli number $B_n$. - -
'BernouilliB'[$n$, $x$] -
represents the Bernoulli polynomial $B_n(x)$. -
- - >> BernoulliB[42] - = 1520097643918070802691 / 1806 - - First five Bernoulli numbers: - - >> Table[BernoulliB[k], {k, 0, 5}] - = ... - - ## This must be (according to WMA) - ## = {1, -1 / 2, 1 / 6, 0, -1 / 30, 0} - ## but for some reason, in the CI the previous test produces - ## the output: - ## {1, 1 / 2, 1 / 6, 0, -1 / 30, 0} - - First five Bernoulli polynomials: - - >> Table[BernoulliB[k, z], {k, 0, 3}] - = {1, -1 / 2 + z, 1 / 6 - z + z ^ 2, z / 2 - 3 z ^ 2 / 2 + z ^ 3} - """ - - attributes = A_PROTECTED | A_LISTABLE - mpmath_name = "bernoulli" - nargs = {1, 2} - summary_text = "Bernoulli number and function" - sympy_name = "bernoulli" diff --git a/mathics/builtin/intfns/recurrence.py b/mathics/builtin/intfns/recurrence.py index d377cfec0..1198ed5bf 100644 --- a/mathics/builtin/intfns/recurrence.py +++ b/mathics/builtin/intfns/recurrence.py @@ -8,9 +8,9 @@ as a function of the preceding terms. """ - from sympy.functions.combinatorial.numbers import stirling +import mathics.eval.tracing as tracing from mathics.core.atoms import Integer from mathics.core.attributes import ( A_LISTABLE, @@ -22,46 +22,91 @@ from mathics.core.evaluation import Evaluation -class Fibonacci(MPMathFunction): +class BernoulliB(MPMathFunction): """ - - :Fibonacci Sequence: - https://en.wikipedia.org/wiki/Fibonacci_sequence, ( - :WMA link:https://reference.wolfram.com/language/ref/Fibonacci.html) + :Bernoulli number: + https://en.wikipedia.org/wiki/Bernoulli_number (:WMA link:https://reference.wolfram.com/language/ref/BernoulliB.html) -
-
'Fibonacci'[$n$] -
computes the $n$-th Fibonacci number. -
'Fibonacci'[$n$, $x$] -
computes the Fibonacci polynomial $F_n(x)$. -
+
+
'BernoulliB'[$n$] +
represents the Bernoulli number $B_n$. - >> Fibonacci[0] - = 0 - >> Fibonacci[1] - = 1 - >> Fibonacci[10] - = 55 - >> Fibonacci[200] - = 280571172992510140037611932413038677189525 - >> Fibonacci[7, x] - = 1 + 6 x ^ 2 + 5 x ^ 4 + x ^ 6 +
'BernouilliB'[$n$, $x$] +
represents the Bernoulli polynomial $B_n(x)$. +
- See also - :LinearRecurrence: - /doc/reference-of-built-in-symbols/integer-functions/recurrence-and-sum-functions/linearrecurrence. + >> BernoulliB[42] + = 1520097643918070802691 / 1806 + + First five Bernoulli numbers: + + >> Table[BernoulliB[k], {k, 0, 5}] + = ... + + ## This must be (according to WMA) + ## = {1, -1 / 2, 1 / 6, 0, -1 / 30, 0} + ## but for some reason, in the CI the previous test produces + ## the output: + ## {1, 1 / 2, 1 / 6, 0, -1 / 30, 0} + + First five Bernoulli polynomials: + + >> Table[BernoulliB[k, z], {k, 0, 3}] + = {1, -1 / 2 + z, 1 / 6 - z + z ^ 2, z / 2 - 3 z ^ 2 / 2 + z ^ 3} + """ + + attributes = A_LISTABLE | A_PROTECTED + eval_error = Builtin.generic_argument_error + expected_args = (1, 2) + mpmath_name = "bernoulli" + nargs = {1, 2} + summary_text = "Bernoulli numbers and polynomials" + sympy_name = "bernoulli" + + +class Fibonacci(MPMathFunction): + """ + + :Fibonacci Sequence: + https://en.wikipedia.org/wiki/Fibonacci_sequence and + :Fibonacci polynomials: + https://en.wikipedia.org/wiki/Fibonacci_polynomials ( + :WMA link:https://reference.wolfram.com/language/ref/Fibonacci.html) + +
+
'Fibonacci'[$n$] +
computes the $n$-th Fibonacci number. +
'Fibonacci'[$n$, $x$] +
computes the Fibonacci polynomial $F_n(x)$. +
+ + >> Fibonacci[0] + = 0 + >> Fibonacci[1] + = 1 + >> Fibonacci[10] + = 55 + >> Fibonacci[200] + = 280571172992510140037611932413038677189525 + >> Fibonacci[7, x] + = 1 + 6 x ^ 2 + 5 x ^ 4 + x ^ 6 + + See also + :LinearRecurrence: + /doc/reference-of-built-in-symbols/integer-functions/recurrence-and-sum-functions/linearrecurrence. """ - nargs = {1} attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED | A_READ_PROTECTED - sympy_name = "fibonacci" + eval_error = Builtin.generic_argument_error + expected_args = (1, 2) mpmath_name = "fibonacci" - summary_text = "Fibonacci's numbers" - + nargs = {1} rules = { "Fibonacci[0, x_]": "0", "Fibonacci[n_Integer?Negative, x_]": "Fibonacci[-n, x]", } + summary_text = "Fibonacci sequences and polynomials" + sympy_name = "fibonacci" class HarmonicNumber(MPMathFunction): @@ -81,19 +126,22 @@ class HarmonicNumber(MPMathFunction): = 2.03806 """ + attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_READ_PROTECTED | A_PROTECTED + eval_error = Builtin.generic_argument_error + expected_args = (1, 2) + mpmath_name = "harmonic" rules = { "HarmonicNumber[-1]": "ComplexInfinity", } summary_text = "Harmonic numbers" - mpmath_name = "harmonic" sympy_name = "harmonic" class LinearRecurrence(Builtin): """ :Linear recurrence with constant coefficients: - https://en.wikipedia.org/wiki/Linear_recurrence_with_constant_coefficients, - :WMA link:https://reference.wolfram.com/language/ref/LinearRecurrence.html + https://en.wikipedia.org/wiki/Linear_recurrence_with_constant_coefficients ( + :WMA link:https://reference.wolfram.com/language/ref/LinearRecurrence.html)
'LinearRecurrence'[$ker$, $init$, $n$] @@ -124,13 +172,15 @@ class LinearRecurrence(Builtin): """ attributes = A_PROTECTED | A_READ_PROTECTED - summary_text = "linear recurrence" + eval_error = Builtin.generic_argument_error + expected_args = 3 rules = { "LinearRecurrence[ker_List, init_List, n_Integer]": "Nest[Append[#, Reverse[ker] . Take[#, -Length[ker]]] &, init, n - Length[init]]", "LinearRecurrence[ker_List, init_List, {n_Integer?Positive}]": "LinearRecurrence[ker, init, n][[n]]", "LinearRecurrence[ker_List, init_List, {nmin_Integer?Positive, nmax_Integer?Positive}]": "LinearRecurrence[ker, init, nmax][[nmin;;nmax]]", } + summary_text = "linear recurrence" # Note: WL allows StirlingS1[{2, 4, 6}, 2], but we don't (yet). @@ -155,17 +205,18 @@ class StirlingS1(Builtin): """ attributes = A_LISTABLE | A_PROTECTED - + eval_error = Builtin.generic_argument_error + expected_args = 2 + mpmath_name = "stirling1" nargs = {2} summary_text = "Stirling numbers of the first kind" sympy_name = "functions.combinatorial.stirling" - mpmath_name = "stirling1" def eval(self, n: Integer, m: Integer, evaluation: Evaluation): - "%(name)s[n_Integer, m_Integer]" - n_value = n.value - m_value = m.value - return Integer(stirling(n_value, m_value, kind=1, signed=True)) + "StirlingS1[n_Integer, m_Integer]" + return Integer( + tracing.run_sympy(stirling, n.value, m.value, kind=1, signed=True) + ) class StirlingS2(Builtin): @@ -188,13 +239,13 @@ class StirlingS2(Builtin): """ attributes = A_LISTABLE | A_PROTECTED - nargs = {2} + eval_error = Builtin.generic_argument_error + expected_args = 2 sympy_name = "functions.combinatorial.numbers.stirling" mpmath_name = "stirling2" + nargs = {2} summary_text = "Stirling numbers of the second kind" def eval(self, m: Integer, n: Integer, evaluation: Evaluation): - "%(name)s[n_Integer, m_Integer]" - n_value = n.value - m_value = m.value - return Integer(stirling(n_value, m_value, kind=2)) + "StirlingS2[n_Integer, m_Integer]" + return Integer(tracing.run_sympy(stirling, n.value, m.value, kind=2)) diff --git a/mathics/eval/tracing.py b/mathics/eval/tracing.py index 55ba37bc7..997598a80 100644 --- a/mathics/eval/tracing.py +++ b/mathics/eval/tracing.py @@ -224,13 +224,13 @@ def wrapper(*args) -> Any: @trace_fn_call_event -def trace_call(fn: Callable, *args) -> Any: +def trace_call(fn: Callable, *args, **kwargs) -> Any: """ Runs a function inside a decorator that traps call and return information that can be used in a tracer or debugger """ - return fn(*args) + return fn(*args, **kwargs) def call_event_print(event: TraceEvent, fn: Callable, *args) -> bool: @@ -254,22 +254,22 @@ def return_event_print(event: TraceEvent, result: Any) -> Any: return result -def run_fast(fn: Callable, *args) -> Any: +def run_fast(fn: Callable, *args, **kwargs) -> Any: """ Fast-path call to run a event-tracable function, but no tracing is in effect. This add another level of indirection to some function calls, but Jit'ing will probably remove this when it is a bottleneck. """ - return fn(*args) + return fn(*args, **kwargs) -def run_mpmath_traced(fn: Callable, *args) -> Any: - return trace_call(TraceEvent.mpmath, fn, *args) +def run_mpmath_traced(fn: Callable, *args, **kwargs) -> Any: + return trace_call(TraceEvent.mpmath, fn, *args, **kwargs) -def run_sympy_traced(fn: Callable, *args) -> Any: - return trace_call(TraceEvent.SymPy, fn, *args) +def run_sympy_traced(fn: Callable, *args, **kwargs) -> Any: + return trace_call(TraceEvent.SymPy, fn, *args, **kwargs) # The below functions are changed by a tracer or debugger From 15ac39b7c3d43eff22fa9cfebcf06a1d8bce256e Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 15 Feb 2026 12:59:17 -0500 Subject: [PATCH 2/3] Create some divlike eval functions... Also remove an unneccessary SymPy call to GCD, and LCM; add tracing form ModularInverse --- mathics/builtin/intfns/divlike.py | 31 +++++----------------- mathics/eval/hyperbolic.py | 2 +- mathics/eval/intfns/__init__.py | 3 +++ mathics/eval/intfns/divlike.py | 44 +++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 25 deletions(-) create mode 100644 mathics/eval/intfns/__init__.py create mode 100644 mathics/eval/intfns/divlike.py diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index 522898be8..4d5602631 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -10,7 +10,6 @@ import sympy from sympy import Q, ask -import mathics.eval.tracing as tracing from mathics.core.atoms import Integer from mathics.core.attributes import ( A_FLAT, @@ -31,6 +30,7 @@ SymbolQuotient, SymbolQuotientRemainder, ) +from mathics.eval.intfns.divlike import eval_GCD, eval_LCM, eval_ModularInverse class CompositeQ(Builtin): @@ -125,14 +125,7 @@ class GCD(Builtin): def eval(self, ns, evaluation: Evaluation): "GCD[ns___Integer]" - ns = ns.get_sequence() - result = 0 - for n in ns: - value = n.value - if value is None: - return - result = tracing.run_sympy(sympy.gcd, result, value) - return Integer(result) + return eval_GCD(ns.get_sequence()) class LCM(Builtin): @@ -161,17 +154,11 @@ class LCM(Builtin): def eval(self, ns: List[Integer], evaluation: Evaluation): "LCM[ns___Integer]" - ns = ns.get_sequence() - if len(ns) == 0: + ns_tuple = ns.get_sequence() + if len(ns_tuple) == 0: evaluation.message("LCM", "argm") return - result = 1 - for n in ns: - value = n.value - if value is None: - return - result = tracing.run_sympy(sympy.lcm, result, value) - return Integer(result) + return eval_LCM(ns_tuple) class Mod(SympyFunction): @@ -248,13 +235,9 @@ class ModularInverse(SympyFunction): summary_text = "returns the modular inverse $k^(-1)$ mod $n$" sympy_name = "mod_inverse" - def eval_k_n(self, k: Integer, n: Integer, evaluation: Evaluation): + def eval(self, k: Integer, n: Integer, evaluation: Evaluation): "ModularInverse[k_Integer, n_Integer]" - try: - r = sympy.mod_inverse(k.value, n.value) - except ValueError: - return - return Integer(r) + return eval_ModularInverse(k.value, n.value) class PowerMod(Builtin): diff --git a/mathics/eval/hyperbolic.py b/mathics/eval/hyperbolic.py index 75ac87b85..598486667 100644 --- a/mathics/eval/hyperbolic.py +++ b/mathics/eval/hyperbolic.py @@ -1,5 +1,5 @@ """ -Mathics3 builtins from mathics.core.numbers.hyperbolic +Mathics3 builtins from mathics.builtin.numbers.hyperbolic """ from sympy import Symbol as SympySymbol diff --git a/mathics/eval/intfns/__init__.py b/mathics/eval/intfns/__init__.py new file mode 100644 index 000000000..baa92ba1b --- /dev/null +++ b/mathics/eval/intfns/__init__.py @@ -0,0 +1,3 @@ +""" +Evaluation functions associated with mathics.builtin.intfns. +""" diff --git a/mathics/eval/intfns/divlike.py b/mathics/eval/intfns/divlike.py new file mode 100644 index 000000000..ca29a95e1 --- /dev/null +++ b/mathics/eval/intfns/divlike.py @@ -0,0 +1,44 @@ +""" +Mathics3 builtins from mathics.builtin.intfns.divlike +""" + +from typing import Optional + +import sympy + +import mathics.eval.tracing as tracing +from mathics.core.atoms import Integer, Integer0 + + +def eval_GCD(ns: tuple) -> Optional[Integer]: + if len(ns) == 0: + return Integer0 + if (result := ns[0].value) is None: + return + for n in ns[1:]: + value = n.value + if value is None: + return + result = tracing.run_sympy(sympy.gcd, result, value) + return Integer(result) + + +def eval_LCM(ns: tuple) -> Optional[Integer]: + if len(ns) == 0: + return Integer0 + if (result := ns[0].value) is None: + return + for n in ns[1:]: + value = n.value + if value is None: + return + result = tracing.run_sympy(sympy.lcm, result, value) + return Integer(result) + + +def eval_ModularInverse(k: int, n: int) -> Optional[Integer]: + try: + r = tracing.run_sympy(sympy.mod_inverse, k, n) + except ValueError: + return + return Integer(r) From d1b4293e4141bfeb5e923e3ad597d6f232064e84 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 15 Feb 2026 13:12:05 -0500 Subject: [PATCH 3/3] Add a few more annotations --- mathics/builtin/intfns/divlike.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index 4d5602631..93593f1a0 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -5,7 +5,7 @@ """ import sys -from typing import List +from typing import List, Optional import sympy from sympy import Q, ask @@ -122,7 +122,7 @@ class GCD(Builtin): attributes = A_FLAT | A_LISTABLE | A_ONE_IDENTITY | A_ORDERLESS | A_PROTECTED summary_text = "greatest common divisor" - def eval(self, ns, evaluation: Evaluation): + def eval(self, ns, evaluation: Evaluation) -> Optional[Integer]: "GCD[ns___Integer]" return eval_GCD(ns.get_sequence()) @@ -151,7 +151,7 @@ class LCM(Builtin): } summary_text = "least common multiple" - def eval(self, ns: List[Integer], evaluation: Evaluation): + def eval(self, ns: List[Integer], evaluation: Evaluation) -> Optional[Integer]: "LCM[ns___Integer]" ns_tuple = ns.get_sequence() @@ -235,7 +235,7 @@ class ModularInverse(SympyFunction): summary_text = "returns the modular inverse $k^(-1)$ mod $n$" sympy_name = "mod_inverse" - def eval(self, k: Integer, n: Integer, evaluation: Evaluation): + def eval(self, k: Integer, n: Integer, evaluation: Evaluation) -> Optional[Integer]: "ModularInverse[k_Integer, n_Integer]" return eval_ModularInverse(k.value, n.value)