From adf124483735f8ef246f75393adc76e874d32a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 19 Apr 2022 17:11:38 +0200 Subject: [PATCH 1/4] allow to use flint for Stirling numbers of both kind --- src/sage/combinat/combinat.py | 81 ++++++++++++++++++++++------------- src/sage/libs/flint/arith.pxd | 6 ++- src/sage/libs/flint/arith.pyx | 50 +++++++++++++++++++++ 3 files changed, 105 insertions(+), 32 deletions(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 42fed39d896..15d2705ce9a 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -854,16 +854,23 @@ def lucas_number2(n, P, Q): return libgap.Lucas(P, Q, n)[1].sage() -def stirling_number1(n, k) -> Integer: +def stirling_number1(n, k, algorithm=None) -> Integer: r""" Return the `n`-th Stirling number `S_1(n,k)` of the first kind. This is the number of permutations of `n` points with `k` cycles. - This wraps GAP's ``Stirling1``. - See :wikipedia:`Stirling_numbers_of_the_first_kind`. + INPUT: + + - ``n`` -- nonnegative machine-size integer + - ``k`` -- nonnegative machine-size integer + - ``algorithm``: + + * ``"gap"`` (default) -- use GAP's Stirling1 function + * ``"flint"`` -- use flint's arith_stirling_number_1u function + EXAMPLES:: sage: stirling_number1(3,2) @@ -876,31 +883,49 @@ def stirling_number1(n, k) -> Integer: 269325 Indeed, `S_1(n,k) = S_1(n-1,k-1) + (n-1)S_1(n-1,k)`. + + TESTS:: + + sage: stirling_number1(10,5, algorithm='flint') + 269325 + + sage: s_sage = stirling_number1(50,3, algorithm="mutta") + Traceback (most recent call last): + ... + ValueError: unknown algorithm: mutta """ n = ZZ(n) k = ZZ(k) - from sage.libs.gap.libgap import libgap - return libgap.Stirling1(n, k).sage() + if algorithm is None: + algorithm = 'gap' + if algorithm == 'gap': + from sage.libs.gap.libgap import libgap + return libgap.Stirling1(n, k).sage() + if algorithm == 'flint': + import sage.libs.flint.arith + return sage.libs.flint.arith.stirling_number_1(n, k) + raise ValueError("unknown algorithm: %s" % algorithm) def stirling_number2(n, k, algorithm=None) -> Integer: r""" - Return the `n`-th Stirling number `S_2(n,k)` of the second - kind. + Return the `n`-th Stirling number `S_2(n,k)` of the second kind. This is the number of ways to partition a set of `n` elements into `k` pairwise disjoint nonempty subsets. The `n`-th Bell number is the sum of the `S_2(n,k)`'s, `k=0,...,n`. + See :wikipedia:`Stirling_numbers_of_the_second_kind`. + INPUT: - * ``n`` - nonnegative machine-size integer - * ``k`` - nonnegative machine-size integer - * ``algorithm``: + - ``n`` -- nonnegative machine-size integer + - ``k`` -- nonnegative machine-size integer + - ``algorithm``: - * None (default) - use native implementation - * ``"maxima"`` - use Maxima's stirling2 function - * ``"gap"`` - use GAP's Stirling2 function + * ``None`` (default) -- use native implementation + * ``"flint"`` -- use flint's arith_stirling_number_2 function + * ``"gap"`` -- use GAP's Stirling2 function EXAMPLES: @@ -961,7 +986,7 @@ def stirling_number2(n, k, algorithm=None) -> Integer: 1900842429486 sage: type(n) - sage: n = stirling_number2(20,11,algorithm='maxima') + sage: n = stirling_number2(20,11,algorithm='flint') sage: n 1900842429486 sage: type(n) @@ -969,16 +994,16 @@ def stirling_number2(n, k, algorithm=None) -> Integer: Sage's implementation splitting the computation of the Stirling numbers of the second kind in two cases according to `n`, let us - check the result it gives agree with both maxima and gap. + check the result it gives agree with both flint and gap. For `n<200`:: sage: for n in Subsets(range(100,200), 5).random_element(): ....: for k in Subsets(range(n), 5).random_element(): ....: s_sage = stirling_number2(n,k) - ....: s_maxima = stirling_number2(n,k, algorithm = "maxima") + ....: s_flint = stirling_number2(n,k, algorithm = "flint") ....: s_gap = stirling_number2(n,k, algorithm = "gap") - ....: if not (s_sage == s_maxima and s_sage == s_gap): + ....: if not (s_sage == s_flint and s_sage == s_gap): ....: print("Error with n<200") For `n\geq 200`:: @@ -986,33 +1011,29 @@ def stirling_number2(n, k, algorithm=None) -> Integer: sage: for n in Subsets(range(200,300), 5).random_element(): ....: for k in Subsets(range(n), 5).random_element(): ....: s_sage = stirling_number2(n,k) - ....: s_maxima = stirling_number2(n,k, algorithm = "maxima") + ....: s_flint = stirling_number2(n,k, algorithm = "flint") ....: s_gap = stirling_number2(n,k, algorithm = "gap") - ....: if not (s_sage == s_maxima and s_sage == s_gap): + ....: if not (s_sage == s_flint and s_sage == s_gap): ....: print("Error with n<200") - TESTS: - Checking an exception is raised whenever a wrong value is given - for ``algorithm``:: - - sage: s_sage = stirling_number2(50,3, algorithm = "CloudReading") + sage: s_sage = stirling_number2(50,3, algorithm="namba") Traceback (most recent call last): ... - ValueError: unknown algorithm: CloudReading + ValueError: unknown algorithm: namba """ n = ZZ(n) k = ZZ(k) if algorithm is None: return _stirling_number2(n, k) - elif algorithm == 'gap': + if algorithm == 'gap': from sage.libs.gap.libgap import libgap return libgap.Stirling2(n, k).sage() - elif algorithm == 'maxima': - return ZZ(maxima.stirling2(n, k)) # type:ignore - else: - raise ValueError("unknown algorithm: %s" % algorithm) + if algorithm == 'flint': + import sage.libs.flint.arith + return sage.libs.flint.arith.stirling_number_2(n, k) + raise ValueError("unknown algorithm: %s" % algorithm) def polygonal_number(s, n): diff --git a/src/sage/libs/flint/arith.pxd b/src/sage/libs/flint/arith.pxd index d4c63fcaf13..c8e1fb35566 100644 --- a/src/sage/libs/flint/arith.pxd +++ b/src/sage/libs/flint/arith.pxd @@ -1,13 +1,15 @@ # distutils: libraries = flint # distutils: depends = flint/arith.h -from sage.libs.flint.types cimport fmpz_t, fmpq_t, ulong +from sage.libs.flint.types cimport fmpz_t, fmpq_t, ulong, slong # flint/arith.h cdef extern from "flint_wrap.h": void arith_bell_number(fmpz_t b, ulong n) void arith_bernoulli_number(fmpq_t x, ulong n) - void arith_euler_number ( fmpz_t res , ulong n ) + void arith_euler_number(fmpz_t res, ulong n) + void arith_stirling_number_1u(fmpz_t res, slong n, slong k) + void arith_stirling_number_2(fmpz_t res, slong n, slong k) void arith_number_of_partitions(fmpz_t x, ulong n) void arith_dedekind_sum(fmpq_t, fmpz_t, fmpz_t) void arith_harmonic_number(fmpq_t, unsigned long n) diff --git a/src/sage/libs/flint/arith.pyx b/src/sage/libs/flint/arith.pyx index 4a378ce9862..f24446b4ae4 100644 --- a/src/sage/libs/flint/arith.pyx +++ b/src/sage/libs/flint/arith.pyx @@ -116,6 +116,56 @@ def euler_number(unsigned long n): return ans +def stirling_number_1(long n, long k): + """ + Return the unsigned Stirling number of the first kind. + + EXAMPLES:: + + sage: from sage.libs.flint.arith import stirling_number_1 + sage: [stirling_number_1(8,i) for i in range(9)] + [0, 5040, 13068, 13132, 6769, 1960, 322, 28, 1] + """ + cdef fmpz_t ans_fmpz + cdef Integer ans = Integer(0) + + fmpz_init(ans_fmpz) + + if n > 1000: + sig_on() + arith_stirling_number_1u(ans_fmpz, n, k) + fmpz_get_mpz(ans.value, ans_fmpz) + fmpz_clear(ans_fmpz) + if n > 1000: + sig_off() + return ans + + +def stirling_number_2(long n, long k): + """ + Return the Stirling number of the second kind. + + EXAMPLES:: + + sage: from sage.libs.flint.arith import stirling_number_2 + sage: [stirling_number_2(8,i) for i in range(9)] + [0, 1, 127, 966, 1701, 1050, 266, 28, 1] + """ + cdef fmpz_t ans_fmpz + cdef Integer ans = Integer(0) + + fmpz_init(ans_fmpz) + + if n > 1000: + sig_on() + arith_stirling_number_2(ans_fmpz, n, k) + fmpz_get_mpz(ans.value, ans_fmpz) + fmpz_clear(ans_fmpz) + if n > 1000: + sig_off() + return ans + + def number_of_partitions(unsigned long n): """ Return the number of partitions of the integer `n`. From 722c2db180aa6f8c6d2f1ebf8cda1ff0571abe32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 19 Apr 2022 17:15:30 +0200 Subject: [PATCH 2/4] fix indentation --- src/sage/combinat/combinat.py | 64 +++++++++++++++++------------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 15d2705ce9a..3363eb1dbad 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -889,10 +889,10 @@ def stirling_number1(n, k, algorithm=None) -> Integer: sage: stirling_number1(10,5, algorithm='flint') 269325 - sage: s_sage = stirling_number1(50,3, algorithm="mutta") - Traceback (most recent call last): - ... - ValueError: unknown algorithm: mutta + sage: s_sage = stirling_number1(50,3, algorithm="mutta") + Traceback (most recent call last): + ... + ValueError: unknown algorithm: mutta """ n = ZZ(n) k = ZZ(k) @@ -947,10 +947,10 @@ def stirling_number2(n, k, algorithm=None) -> Integer: Stirling numbers satisfy `S_2(n,k) = S_2(n-1,k-1) + kS_2(n-1,k)`:: - sage: 5*stirling_number2(9,5) + stirling_number2(9,4) - 42525 - sage: stirling_number2(10,5) - 42525 + sage: 5*stirling_number2(9,5) + stirling_number2(9,4) + 42525 + sage: stirling_number2(10,5) + 42525 TESTS:: @@ -992,36 +992,36 @@ def stirling_number2(n, k, algorithm=None) -> Integer: sage: type(n) - Sage's implementation splitting the computation of the Stirling - numbers of the second kind in two cases according to `n`, let us - check the result it gives agree with both flint and gap. + Sage's implementation splitting the computation of the Stirling + numbers of the second kind in two cases according to `n`, let us + check the result it gives agree with both flint and gap. - For `n<200`:: + For `n<200`:: - sage: for n in Subsets(range(100,200), 5).random_element(): - ....: for k in Subsets(range(n), 5).random_element(): - ....: s_sage = stirling_number2(n,k) - ....: s_flint = stirling_number2(n,k, algorithm = "flint") - ....: s_gap = stirling_number2(n,k, algorithm = "gap") - ....: if not (s_sage == s_flint and s_sage == s_gap): - ....: print("Error with n<200") + sage: for n in Subsets(range(100,200), 5).random_element(): + ....: for k in Subsets(range(n), 5).random_element(): + ....: s_sage = stirling_number2(n,k) + ....: s_flint = stirling_number2(n,k, algorithm = "flint") + ....: s_gap = stirling_number2(n,k, algorithm = "gap") + ....: if not (s_sage == s_flint and s_sage == s_gap): + ....: print("Error with n<200") - For `n\geq 200`:: + For `n\geq 200`:: - sage: for n in Subsets(range(200,300), 5).random_element(): - ....: for k in Subsets(range(n), 5).random_element(): - ....: s_sage = stirling_number2(n,k) - ....: s_flint = stirling_number2(n,k, algorithm = "flint") - ....: s_gap = stirling_number2(n,k, algorithm = "gap") - ....: if not (s_sage == s_flint and s_sage == s_gap): - ....: print("Error with n<200") + sage: for n in Subsets(range(200,300), 5).random_element(): + ....: for k in Subsets(range(n), 5).random_element(): + ....: s_sage = stirling_number2(n,k) + ....: s_flint = stirling_number2(n,k, algorithm = "flint") + ....: s_gap = stirling_number2(n,k, algorithm = "gap") + ....: if not (s_sage == s_flint and s_sage == s_gap): + ....: print("Error with n<200") - TESTS: + TESTS: - sage: s_sage = stirling_number2(50,3, algorithm="namba") - Traceback (most recent call last): - ... - ValueError: unknown algorithm: namba + sage: s_sage = stirling_number2(50,3, algorithm="namba") + Traceback (most recent call last): + ... + ValueError: unknown algorithm: namba """ n = ZZ(n) k = ZZ(k) From 384ac6eb47efd51f63297a9bf165534bbe29c01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 19 Apr 2022 17:17:01 +0200 Subject: [PATCH 3/4] remove duplicate TEST --- src/sage/combinat/combinat.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 3363eb1dbad..ae86cf37315 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -1016,8 +1016,6 @@ def stirling_number2(n, k, algorithm=None) -> Integer: ....: if not (s_sage == s_flint and s_sage == s_gap): ....: print("Error with n<200") - TESTS: - sage: s_sage = stirling_number2(50,3, algorithm="namba") Traceback (most recent call last): ... From bbd7807cf9aa149776067cde757b2a81bc85f3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 21 Apr 2022 20:17:34 +0200 Subject: [PATCH 4/4] re-add maxima algo for Stirling --- src/sage/combinat/combinat.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index ae86cf37315..48475d117dc 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -364,9 +364,9 @@ def bell_number(n, algorithm='flint', **options) -> Integer: TESTS:: - sage: all(bell_number(n) == bell_number(n,'dobinski') for n in range(200)) + sage: all(bell_number(n) == bell_number(n,'dobinski') for n in range(100)) True - sage: all(bell_number(n) == bell_number(n,'gap') for n in range(200)) + sage: all(bell_number(n) == bell_number(n,'gap') for n in range(100)) True sage: all(bell_number(n) == bell_number(n,'mpmath', prec=500) for n in range(200, 220)) True @@ -854,7 +854,7 @@ def lucas_number2(n, P, Q): return libgap.Lucas(P, Q, n)[1].sage() -def stirling_number1(n, k, algorithm=None) -> Integer: +def stirling_number1(n, k, algorithm="gap") -> Integer: r""" Return the `n`-th Stirling number `S_1(n,k)` of the first kind. @@ -868,8 +868,8 @@ def stirling_number1(n, k, algorithm=None) -> Integer: - ``k`` -- nonnegative machine-size integer - ``algorithm``: - * ``"gap"`` (default) -- use GAP's Stirling1 function - * ``"flint"`` -- use flint's arith_stirling_number_1u function + * ``"gap"`` (default) -- use GAP's ``Stirling1`` function + * ``"flint"`` -- use flint's ``arith_stirling_number_1u`` function EXAMPLES:: @@ -896,8 +896,6 @@ def stirling_number1(n, k, algorithm=None) -> Integer: """ n = ZZ(n) k = ZZ(k) - if algorithm is None: - algorithm = 'gap' if algorithm == 'gap': from sage.libs.gap.libgap import libgap return libgap.Stirling1(n, k).sage() @@ -924,8 +922,9 @@ def stirling_number2(n, k, algorithm=None) -> Integer: - ``algorithm``: * ``None`` (default) -- use native implementation - * ``"flint"`` -- use flint's arith_stirling_number_2 function - * ``"gap"`` -- use GAP's Stirling2 function + * ``"flint"`` -- use flint's ``arith_stirling_number_2`` function + * ``"gap"`` -- use GAP's ``Stirling2`` function + * ``"maxima"`` -- use Maxima's ``stirling2`` function EXAMPLES: @@ -1016,7 +1015,10 @@ def stirling_number2(n, k, algorithm=None) -> Integer: ....: if not (s_sage == s_flint and s_sage == s_gap): ....: print("Error with n<200") - sage: s_sage = stirling_number2(50,3, algorithm="namba") + sage: stirling_number2(20,3, algorithm="maxima") + 580606446 + + sage: s_sage = stirling_number2(5,3, algorithm="namba") Traceback (most recent call last): ... ValueError: unknown algorithm: namba @@ -1031,6 +1033,8 @@ def stirling_number2(n, k, algorithm=None) -> Integer: if algorithm == 'flint': import sage.libs.flint.arith return sage.libs.flint.arith.stirling_number_2(n, k) + if algorithm == 'maxima': + return ZZ(maxima.stirling2(n, k)) # type:ignore raise ValueError("unknown algorithm: %s" % algorithm) @@ -1423,8 +1427,6 @@ def __bool__(self) -> bool: """ return bool(self._list) - - def __len__(self) -> Integer: """ EXAMPLES:: @@ -2461,7 +2463,6 @@ def __iter__(self): ############################################################################## from sage.sets.image_set import ImageSubobject -from sage.categories.map import is_Map class MapCombinatorialClass(ImageSubobject, CombinatorialClass):