Skip to content

Commit 2ca5af7

Browse files
authored
Merge pull request #1190 from mathics/add-PartitionsP
Add builtin PartitionsP[]
2 parents d97cdc7 + 9db06f7 commit 2ca5af7

3 files changed

Lines changed: 92 additions & 11 deletions

File tree

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ New builtins
1313
* ``CreateTemporary``
1414
* ``FileNames``
1515
* ``NIntegrate``
16+
* ``PartitionsP``
1617

1718
Enhancements
1819
++++++++++++

mathics/builtin/combinatorial.py

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
#!/usr/bin/env python3
21
# -*- coding: utf-8 -*-
32
"""
43
Combinatorial Functions
54
"""
65

76

8-
import sympy
7+
import math
8+
from functools import lru_cache
99
from sympy.functions.combinatorial.numbers import stirling
1010
from mathics.version import __version__ # noqa used in loading to check consistency.
1111

@@ -80,11 +80,11 @@ def apply(self, values, evaluation):
8080
return Expression("Times", *leaves)
8181

8282

83-
class Fibonacci(Builtin):
83+
class Fibonacci(_MPMathFunction):
8484
"""
8585
<dl>
86-
<dt>'Fibonacci[$n$]'
87-
<dd>computes the $n$th Fibonacci number.
86+
<dt>'Fibonacci[$n$]'
87+
<dd>computes the $n$th Fibonacci number.
8888
</dl>
8989
9090
>> Fibonacci[0]
@@ -97,12 +97,65 @@ class Fibonacci(Builtin):
9797
= 280571172992510140037611932413038677189525
9898
"""
9999

100+
nargs = 1
100101
attributes = ("Listable", "NumericFunction", "ReadProtected")
102+
sympy_name = "fibonacci"
103+
mpmath_name = "fibonacci"
104+
105+
# Note: memoizing functions is a big win. For a value of n, more than
106+
# n different values (positive and negative) occur.
107+
# For this function to be effective across top-level calls,
108+
# it needs to be at the module level rather than inside the class.
109+
# Finally, docs say that `maxsize` is best at a power of 2.
110+
# With 1024 we can handle reasonably values of slightly greater than 1024
111+
# values without trouble:
112+
# PartitionsP[1070] = 366665450770488753893927654278831
113+
@lru_cache(maxsize=1024)
114+
def number_of_partitions(n: int) -> int:
115+
"""Algorithm NumberOfPartitions from Page 67 of Skiena: Implementing
116+
Discrete Mathematics, using Euler's recurrence"""
117+
if n < 0:
118+
return 0
119+
elif n == 0:
120+
return 1
121+
sum = 0
122+
for m in range(math.ceil((1 + math.sqrt(1.0 + 24 * n)) / 6), 0, -1):
123+
mx3 = m * 3
124+
j = n - m * (mx3 - 1) / 2
125+
k = n - m * (mx3 + 1) / 2
126+
# Cut down on memoization by filtering negative numbers here.
127+
# In contrast to the multitude of negative numbers, the n==0
128+
# case isn't worth saving memoization checking since that is
129+
# one single entry.
130+
part_j = 0 if j < 0 else number_of_partitions(j)
131+
part_k = 0 if k < 0 else number_of_partitions(k)
132+
if m % 2:
133+
sum += part_j + part_k
134+
else:
135+
sum += -part_j - part_k
136+
return sum
137+
138+
class PartitionsP(Builtin):
139+
"""
140+
<dl>
141+
<dt>'PartitionsP[$n$]'
142+
<dd>return the number p(n) of unrestricted partitions of the integer $n$.
143+
</dl>
144+
145+
>> Table[PartitionsP[k], {k, -2, 12}]
146+
= {0, 0, 1, 1, 2, 3, 5, 7, 11, 15, 22, 30, 42, 56, 77}
147+
"""
148+
149+
attributes = ("Listable", "NumericFunction", "Orderless")
101150

102151
def apply(self, n, evaluation):
103-
"Fibonacci[n_Integer]"
152+
"PartitionsP[n_Integer]"
104153

105-
return Integer(sympy.fibonacci(n.get_int_value()))
154+
return Integer(number_of_partitions(n.get_int_value()))
155+
156+
# Simpler but inefficient.
157+
# from sympy.utilities.iterables import partitions
158+
# return Integer(len(list(partitions(n.get_int_value()))))
106159

107160

108161
class _NoBoolVector(Exception):
@@ -272,12 +325,13 @@ def _compute(self, n, c_ff, c_ft, c_tf, c_tt):
272325
r = 2 * (c_tf + c_ft)
273326
return Expression("Divide", r, c_tt + c_ff + r)
274327

328+
275329
# Note: WL allows StirlingS1[{2, 4, 6}, 2], but we don't (yet).
276330
class StirlingS1(Builtin):
277331
"""
278332
<dl>
279333
<dt>'StirlingS1[$n$, $m$]'
280-
<dd>gives the Stirling number of the first kind $𝒮_n^m$.
334+
<dd>gives the Stirling number of the first kind $ _n^m$.
281335
</dl>
282336
283337
Integer mathematical function, suitable for both symbolic and numerical manipulation.
@@ -302,10 +356,10 @@ class StirlingS2(Builtin):
302356
"""
303357
<dl>
304358
<dt>'StirlingS2[$n$, $m$]'
305-
<dd>gives the Stirling number of the second kind 𝒮_n^m.
359+
<dd>gives the Stirling number of the second kind _n^m.
306360
</dl>
307361
308-
returns the number of ways of partitioning a set of $n$ elements into $m$ nonempty subsets.
362+
returns the number of ways of partitioning a set of $n$ elements into $m$ non empty subsets.
309363
310364
>> Table[StirlingS2[10, m], {m, 10}]
311365
= {1, 511, 9330, 34105, 42525, 22827, 5880, 750, 45, 1}

test/test_combinatorica.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,33 @@ def test_combinations_1_5():
472472
"{Range[5]} ",
473473
"KSubsets[l, k] == Length(l)",
474474
),
475-
# Start here in section 2.1 ...
475+
):
476+
check_evaluation(str_expr, str_expected, message)
477+
478+
def test_2_1_to_2_3():
479+
480+
for str_expr, str_expected, message in (
481+
(
482+
# 2.1.1 - 2.1.3 are broken
483+
"PartitionsP[10]",
484+
"NumberOfPartitions[10]",
485+
"Counting Partitions 2.1.4, Page 57",
486+
),
487+
(
488+
"NumberOfCompositions[6,3]",
489+
"28",
490+
"Random Compositions 2.2.1, Page 60",
491+
),
492+
(
493+
"TableauQ[{{1,2,5}, {3,4,5}, {6}}]",
494+
"True",
495+
"Young Tableau 2.3, Page 63",
496+
),
497+
(
498+
"TableauQ[{{1,2,5,9,10}, {5,4,7,13}, {4,8,12},{11}}]",
499+
"False",
500+
"Young Tableau 2.3, Page 63",
501+
),
476502
):
477503
check_evaluation(str_expr, str_expected, message)
478504

0 commit comments

Comments
 (0)