Skip to content

Commit 8c1295c

Browse files
davidarmmatera
andauthored
Add missing builtins (#1133)
I added a bunch of WMA builtins that aren't in Mathics yet: - NumberDigit - BellB - EulerE - JacobiSymbol - KroneckerSymbol - LucasL - PolygonalNumber - SquaresR - LinearRecurrence - ~~RootSum~~ I think this one needs some more work for converting to sympy - SeriesCoefficient - DivisorSigma - DivisorSum - IntegerPart - IntegerPartitions - MersennePrimeExponent - MoebiusMu - PowersRepresentations - HypergeometricU - LambertW - Subfactorial - PolyLog - ReverseSort I also expanded the functionality of a few existing builtins to match the behaviour of WMA: - handle non-integer bounds in Sum - fixed a bug in RealDigits where it would report the wrong number of digits for powers of 10 (or whatever base is specified) - support Fibonacci polynomials - support symbolic bounds in Table (and other IterationFunctions) - support providing SeriesData to CoefficientList - added the listable attribute to a few functions - allow supplying a precision when calling `N[]` on a complex number I can split some of these out into separate PRs if they need more work to merge. I've started setting up some automated tests to pull all the Mathematica programs listed in OEIS and check that Mathics can evaluate them to the correct sequence values - these are all just the low hanging fruit that fell out of doing that for the first couple of hundred sequences. --------- Co-authored-by: Juan Mauricio Matera <matera@fisica.unlp.edu.ar>
1 parent 744cbe0 commit 8c1295c

16 files changed

Lines changed: 354 additions & 35 deletions

File tree

CHANGES.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,31 @@ New Builtins
99
* ``FileNameDrop``
1010
* ``SetEnvironment``
1111

12+
By `@davidar <https://github.com/davidar>`_:
13+
14+
* ``BellB``
15+
* ``DivisorSigma``
16+
* ``DivisorSum``
17+
* ``EulerE``
18+
* ``HypergeometricU``
19+
* ``IntegerPart``
20+
* ``IntegerPartitions``
21+
* ``JacobiSymbol``
22+
* ``KroneckerSymbol``
23+
* ``LambertW``
24+
* ``LinearRecurrence``
25+
* ``LucasL``
26+
* ``MersennePrimeExponent``
27+
* ``MoebiusMu``
28+
* ``NumberDigit``
29+
* ``PolygonalNumber``
30+
* ``PolyLog``
31+
* ``PowersRepresentations``
32+
* ``ReverseSort``
33+
* ``SeriesCoefficient``
34+
* ``SquaresR``
35+
* ``Subfactorial``
36+
1237
``mathics`` command line
1338
++++++++++++++++++++++++
1439

mathics/builtin/arithmetic.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,14 @@ class Sum(IterationFunction, SympyFunction):
959959
Verify algebraic identities:
960960
>> Sum[x ^ 2, {x, 1, y}] - y * (y + 1) * (2 * y + 1) / 6
961961
= 0
962+
963+
Non-integer bounds:
964+
>> Sum[i, {i, 1, 2.5}]
965+
= 3
966+
>> Sum[i, {i, 1.1, 2.5}]
967+
= 3.2
968+
>> Sum[k, {k, I, I + 1.5}]
969+
= 1 + 2 I
962970
"""
963971

964972
summary_text = "discrete sum"
@@ -1020,7 +1028,11 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]:
10201028
# If we have integer bounds, we'll use Mathics's iterator Sum
10211029
# (which is Plus)
10221030

1023-
if all(hasattr(i, "is_integer") and i.is_integer for i in bounds[1:]):
1031+
if all(
1032+
(hasattr(i, "is_integer") and i.is_integer)
1033+
or (hasattr(i, "is_finite") and i.is_finite and i.is_constant())
1034+
for i in bounds[1:]
1035+
):
10241036
# When we have integer bounds, it is better to not use Sympy but
10251037
# use Mathics evaluation. We turn:
10261038
# Sum[f[x], {<limits>}] into
@@ -1029,9 +1041,10 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]:
10291041
values = Expression(SymbolTable, *expr.elements).evaluate(
10301042
evaluation
10311043
)
1032-
ret = self.get_result(values.elements).evaluate(evaluation)
1033-
# Make sure to convert the result back to sympy.
1034-
return ret.to_sympy()
1044+
if values.get_head_name() != SymbolTable.get_name():
1045+
ret = self.get_result(values.elements).evaluate(evaluation)
1046+
# Make sure to convert the result back to sympy.
1047+
return ret.to_sympy()
10351048

10361049
if None not in bounds:
10371050
return sympy.summation(f_sympy, bounds)

mathics/builtin/atomic/numbers.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
MachineReal,
3131
Rational,
3232
)
33-
from mathics.core.attributes import A_LISTABLE, A_PROTECTED
33+
from mathics.core.attributes import A_LISTABLE, A_PROTECTED, A_READ_PROTECTED
3434
from mathics.core.builtin import Builtin, Predefined
3535
from mathics.core.convert.python import from_python
3636
from mathics.core.expression import Expression
@@ -60,7 +60,9 @@
6060

6161
@lru_cache()
6262
def log_n_b(py_n, py_b) -> int:
63-
return int(mpmath.ceil(mpmath.log(py_n, py_b))) if py_n != 0 and py_n != 1 else 1
63+
return (
64+
int(mpmath.floor(mpmath.log(py_n, py_b))) + 1 if py_n != 0 and py_n != 1 else 1
65+
)
6466

6567

6668
def check_finite_decimal(denominator):
@@ -376,6 +378,33 @@ def eval(self, n, b, evaluation):
376378
return Integer(j)
377379

378380

381+
class NumberDigit(Builtin):
382+
"""
383+
<url>:WMA link:
384+
https://reference.wolfram.com/language/ref/NumberDigit.html</url>
385+
386+
<dl>
387+
<dt>'NumberDigit[$x$, $n$, $b$]'
388+
<dd>returns the coefficient of $b^n$ in the base-$b$ representation of $x$.
389+
</dl>
390+
391+
>> NumberDigit[123456, 2]
392+
= 4
393+
>> NumberDigit[12.3456, -1]
394+
= 3
395+
396+
"""
397+
398+
attributes = A_PROTECTED | A_READ_PROTECTED
399+
400+
summary_text = "digits of a real number"
401+
402+
rules = {
403+
"NumberDigit[x_, n_Integer]": "NumberDigit[x, n, 10]",
404+
"NumberDigit[x_, n_Integer, b_Integer]": "RealDigits[x, b, 1, n][[1]][[1]]",
405+
}
406+
407+
379408
class RealDigits(Builtin):
380409
"""
381410
<url>:WMA link:
@@ -420,6 +449,9 @@ class RealDigits(Builtin):
420449
Return 25 digits of in base 10:
421450
>> RealDigits[Pi, 10, 25]
422451
= {{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3}, 1}
452+
453+
>> RealDigits[10]
454+
= {{1, 0}, 2}
423455
"""
424456

425457
attributes = A_LISTABLE | A_PROTECTED
@@ -445,7 +477,7 @@ def eval_rational_with_base(self, n, b, evaluation):
445477
if check_finite_decimal(n.denominator().get_int_value()) and not py_b % 2:
446478
return self.eval_with_base(n, b, evaluation)
447479
else:
448-
exp = int(mpmath.ceil(mpmath.log(py_n, py_b)))
480+
exp = log_n_b(py_n, py_b)
449481
(head, tails) = convert_repeating_decimal(
450482
py_n.as_numer_denom()[0], py_n.as_numer_denom()[1], py_b
451483
)

mathics/builtin/intfns/recurrence.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class Fibonacci(MPMathFunction):
2929
<dl>
3030
<dt>'Fibonacci[$n$]'
3131
<dd>computes the $n$th Fibonacci number.
32+
<dt>'Fibonacci[$n$, $x$]'
33+
<dd>computes the Fibonacci polynomial $F$_$n$($x$).
3234
</dl>
3335
3436
>> Fibonacci[0]
@@ -39,6 +41,8 @@ class Fibonacci(MPMathFunction):
3941
= 55
4042
>> Fibonacci[200]
4143
= 280571172992510140037611932413038677189525
44+
>> Fibonacci[7, x]
45+
= 1 + 6 x ^ 2 + 5 x ^ 4 + x ^ 6
4246
"""
4347

4448
nargs = {1}
@@ -47,6 +51,11 @@ class Fibonacci(MPMathFunction):
4751
mpmath_name = "fibonacci"
4852
summary_text = "Fibonacci's numbers"
4953

54+
rules = {
55+
"Fibonacci[0, x_]": "0",
56+
"Fibonacci[n_Integer?Negative, x_]": "Fibonacci[-n, x]",
57+
}
58+
5059

5160
class HarmonicNumber(MPMathFunction):
5261
"""
@@ -73,6 +82,31 @@ class HarmonicNumber(MPMathFunction):
7382
sympy_name = "harmonic"
7483

7584

85+
class LinearRecurrence(Builtin):
86+
"""
87+
<url>:WMA link:https://reference.wolfram.com/language/ref/LinearRecurrence.html</url>
88+
89+
<dl>
90+
<dt>'LinearRecurrence[$ker$, $init$, $n$]'
91+
<dd>computes $n$ terms of the linear recurrence with kernel $ker$ and intial values $init$
92+
</dl>
93+
94+
>> LinearRecurrence[{1, 1}, {1, 1}, 10]
95+
= {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}
96+
>> LinearRecurrence[{1, 1}, {1, 1}, {5, 5}]
97+
= {5}
98+
"""
99+
100+
attributes = A_PROTECTED | A_READ_PROTECTED
101+
summary_text = "linear recurrence"
102+
103+
rules = {
104+
"LinearRecurrence[ker_List, init_List, n_Integer]": "Nest[Append[#, Reverse[ker] . Take[#, -Length[ker]]] &, init, n - Length[init]]",
105+
"LinearRecurrence[ker_List, init_List, {n_Integer?Positive}]": "LinearRecurrence[ker, init, n][[n]]",
106+
"LinearRecurrence[ker_List, init_List, {nmin_Integer?Positive, nmax_Integer?Positive}]": "LinearRecurrence[ker, init, nmax][[nmin;;nmax]]",
107+
}
108+
109+
76110
# Note: WL allows StirlingS1[{2, 4, 6}, 2], but we don't (yet).
77111
class StirlingS1(Builtin):
78112
"""

mathics/builtin/list/constructing.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ class Table(IterationFunction):
522522
= {x, x, x}
523523
>> n = 0; Table[n = n + 1, {5}]
524524
= {1, 2, 3, 4, 5}
525+
#> Clear[n]
525526
>> Table[i, {i, 4}]
526527
= {1, 2, 3, 4}
527528
>> Table[i, {i, 2, 5}]
@@ -536,6 +537,14 @@ class Table(IterationFunction):
536537
'Table' supports multi-dimensional tables:
537538
>> Table[{i, j}, {i, {a, b}}, {j, 1, 2}]
538539
= {{{a, 1}, {a, 2}}, {{b, 1}, {b, 2}}}
540+
541+
Symbolic bounds:
542+
>> Table[x, {x, a, a + 5 n, n}]
543+
= {a, a + n, a + 2 n, a + 3 n, a + 4 n, a + 5 n}
544+
545+
The lower bound is always included even for large step sizes:
546+
>> Table[i, {i, 1, 9, Infinity}]
547+
= {1}
539548
"""
540549

541550
rules = {

mathics/builtin/numbers/algebra.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,8 @@ class CoefficientList(Builtin):
901901
= {{5, d, 0, b}, {c, 0, 0, 0}, {a, 0, 0, 0}}
902902
>> CoefficientList[(x - 2 y + 3 z)^3, {x, y, z}]
903903
= {{{0, 0, 0, 27}, {0, 0, -54, 0}, {0, 36, 0, 0}, {-8, 0, 0, 0}}, {{0, 0, 27, 0}, {0, -36, 0, 0}, {12, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 9, 0, 0}, {-6, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{1, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}}
904+
>> CoefficientList[Series[Log[1-x], {x, 0, 9}], x]
905+
= {0, -1, -1 / 2, -1 / 3, -1 / 4, -1 / 5, -1 / 6, -1 / 7, -1 / 8, -1 / 9}
904906
"""
905907

906908
messages = {
@@ -914,7 +916,7 @@ def eval_noform(self, expr, evaluation):
914916
"CoefficientList[expr_]"
915917
evaluation.message("CoefficientList", "argtu")
916918

917-
def eval(self, expr, form, evaluation):
919+
def eval(self, expr: Expression, form: Expression, evaluation: Evaluation):
918920
"CoefficientList[expr_, form_]"
919921
vars = [form] if not form.has_form("List", None) else [v for v in form.elements]
920922

@@ -937,6 +939,18 @@ def eval(self, expr, form, evaluation):
937939
return ListExpression(expr)
938940
elif form.has_form("List", 0):
939941
return expr
942+
elif expr.get_head_name() == "System`SeriesData":
943+
coeffs: ListExpression
944+
nmin: Integer
945+
nmax: Integer
946+
x, x0, coeffs, nmin, nmax, den = expr.elements
947+
if x == form and x0 == Integer0 and den == Integer1:
948+
return ListExpression(
949+
*[
950+
coeffs.elements[i - nmin.value] if i >= nmin.value else Integer0
951+
for i in range(0, nmax.value)
952+
]
953+
)
940954

941955
sympy_expr = expr.to_sympy()
942956
sympy_vars = [v.to_sympy() for v in vars]
@@ -953,8 +967,7 @@ def eval(self, expr, form, evaluation):
953967

954968
# single & multiple variables cases
955969
if not form.has_form("List", None):
956-
return Expression(
957-
SymbolList,
970+
return ListExpression(
958971
*[
959972
_coefficient(
960973
self.__class__.__name__, expr, form, Integer(n), evaluation
@@ -964,8 +977,7 @@ def eval(self, expr, form, evaluation):
964977
)
965978
elif form.has_form("List", 1):
966979
form = form.elements[0]
967-
return Expression(
968-
SymbolList,
980+
return ListExpression(
969981
*[
970982
_coefficient(
971983
self.__class__.__name__, expr, form, Integer(n), evaluation

mathics/builtin/numbers/calculus.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
SymbolConditionalExpression,
6464
SymbolD,
6565
SymbolDerivative,
66+
SymbolIndeterminate,
6667
SymbolInfinity,
6768
SymbolInfix,
6869
SymbolIntegrate,
@@ -1731,6 +1732,50 @@ def eval_multivariate_series(self, f, varspec, evaluation: Evaluation):
17311732
return None
17321733

17331734

1735+
class SeriesCoefficient(Builtin):
1736+
"""
1737+
<url>:WMA link:https://reference.wolfram.com/language/ref/SeriesCoefficient.html</url>
1738+
1739+
<dl>
1740+
<dt>'SeriesCoefficient[$series$, $n$]'
1741+
<dd>Find the $n$th coefficient in the given $series$
1742+
</dl>
1743+
1744+
>> SeriesCoefficient[Series[Exp[Sin[x]], {x, 0, 10}], 8]
1745+
= 31 / 5760
1746+
>> SeriesCoefficient[Exp[-x], {x, 0, 5}]
1747+
= -1 / 120
1748+
1749+
>> SeriesCoefficient[SeriesData[x, c, Table[i^2, {i, 10}], 7, 17, 3], 14/3]
1750+
= 64
1751+
>> SeriesCoefficient[SeriesData[x, c, Table[i^2, {i, 10}], 7, 17, 3], 6/3]
1752+
= 0
1753+
>> SeriesCoefficient[SeriesData[x, c, Table[i^2, {i, 10}], 7, 17, 3], 17/3]
1754+
= Indeterminate
1755+
"""
1756+
1757+
attributes = A_PROTECTED
1758+
summary_text = "power series coefficient"
1759+
1760+
rules = {
1761+
"SeriesCoefficient[f_, {x_Symbol, x0_, n_Integer}]": "SeriesCoefficient[Series[f, {x, x0, n}], n]"
1762+
}
1763+
1764+
def eval(self, series: Expression, n: Rational, evaluation: Evaluation):
1765+
"""SeriesCoefficient[series_SeriesData, n_]"""
1766+
coeffs: ListExpression
1767+
nmin: Integer
1768+
nmax: Integer
1769+
den: Integer
1770+
coeffs, nmin, nmax, den = series.elements[2:]
1771+
index = n.value * den.value - nmin.value
1772+
if index < 0:
1773+
return Integer0
1774+
if index >= nmax.value - nmin.value:
1775+
return SymbolIndeterminate
1776+
return coeffs[index]
1777+
1778+
17341779
class SeriesData(Builtin):
17351780
"""
17361781

mathics/builtin/specialfns/bessel.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,28 @@ class HankelH2(_Bessel):
558558
sympy_name = "hankel2"
559559

560560

561+
class HypergeometricU(MPMathFunction):
562+
"""
563+
<url>
564+
:Confluent hypergeometric function: https://en.wikipedia.org/wiki/Confluent_hypergeometric_function</url> (<url>
565+
:mpmath: https://mpmath.org/doc/current/functions/bessel.html#mpmath.hyperu</url>, <url>
566+
:WMA: https://reference.wolfram.com/language/ref/HypergeometricU.html</url>)
567+
<dl>
568+
<dt>'HypergeometricU[$a$, $b$, $z$]'
569+
<dd>returns $U$($a$, $b$, $z$).
570+
</dl>
571+
572+
>> HypergeometricU[3, 2, 1.]
573+
= 0.105479
574+
"""
575+
576+
attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED | A_READ_PROTECTED
577+
summary_text = "Tricomi confluent hypergeometric function"
578+
mpmath_name = "hyperu"
579+
sympy_name = ""
580+
nargs = {3}
581+
582+
561583
# Kelvin Functions
562584

563585

0 commit comments

Comments
 (0)