2564 lines
82 KiB
Python
2564 lines
82 KiB
Python
"""
|
|
This module implements some special functions that commonly appear in
|
|
combinatorial contexts (e.g. in power series); in particular,
|
|
sequences of rational numbers such as Bernoulli and Fibonacci numbers.
|
|
|
|
Factorials, binomial coefficients and related functions are located in
|
|
the separate 'factorials' module.
|
|
"""
|
|
from math import prod
|
|
from collections import defaultdict
|
|
from typing import Tuple as tTuple
|
|
|
|
from sympy.core import S, Symbol, Add, Dummy
|
|
from sympy.core.cache import cacheit
|
|
from sympy.core.expr import Expr
|
|
from sympy.core.function import ArgumentIndexError, Function, expand_mul
|
|
from sympy.core.logic import fuzzy_not
|
|
from sympy.core.mul import Mul
|
|
from sympy.core.numbers import E, I, pi, oo, Rational, Integer
|
|
from sympy.core.relational import Eq, is_le, is_gt
|
|
from sympy.external.gmpy import SYMPY_INTS
|
|
from sympy.functions.combinatorial.factorials import (binomial,
|
|
factorial, subfactorial)
|
|
from sympy.functions.elementary.exponential import log
|
|
from sympy.functions.elementary.piecewise import Piecewise
|
|
from sympy.ntheory.primetest import isprime, is_square
|
|
from sympy.polys.appellseqs import bernoulli_poly, euler_poly, genocchi_poly
|
|
from sympy.utilities.enumerative import MultisetPartitionTraverser
|
|
from sympy.utilities.exceptions import sympy_deprecation_warning
|
|
from sympy.utilities.iterables import multiset, multiset_derangements, iterable
|
|
from sympy.utilities.memoization import recurrence_memo
|
|
from sympy.utilities.misc import as_int
|
|
|
|
from mpmath import mp, workprec
|
|
from mpmath.libmp import ifib as _ifib
|
|
|
|
|
|
def _product(a, b):
|
|
return prod(range(a, b + 1))
|
|
|
|
|
|
# Dummy symbol used for computing polynomial sequences
|
|
_sym = Symbol('x')
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Carmichael numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
def _divides(p, n):
|
|
return n % p == 0
|
|
|
|
class carmichael(Function):
|
|
r"""
|
|
Carmichael Numbers:
|
|
|
|
Certain cryptographic algorithms make use of big prime numbers.
|
|
However, checking whether a big number is prime is not so easy.
|
|
Randomized prime number checking tests exist that offer a high degree of
|
|
confidence of accurate determination at low cost, such as the Fermat test.
|
|
|
|
Let 'a' be a random number between $2$ and $n - 1$, where $n$ is the
|
|
number whose primality we are testing. Then, $n$ is probably prime if it
|
|
satisfies the modular arithmetic congruence relation:
|
|
|
|
.. math :: a^{n-1} = 1 \pmod{n}
|
|
|
|
(where mod refers to the modulo operation)
|
|
|
|
If a number passes the Fermat test several times, then it is prime with a
|
|
high probability.
|
|
|
|
Unfortunately, certain composite numbers (non-primes) still pass the Fermat
|
|
test with every number smaller than themselves.
|
|
These numbers are called Carmichael numbers.
|
|
|
|
A Carmichael number will pass a Fermat primality test to every base $b$
|
|
relatively prime to the number, even though it is not actually prime.
|
|
This makes tests based on Fermat's Little Theorem less effective than
|
|
strong probable prime tests such as the Baillie-PSW primality test and
|
|
the Miller-Rabin primality test.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import carmichael
|
|
>>> carmichael.find_first_n_carmichaels(5)
|
|
[561, 1105, 1729, 2465, 2821]
|
|
>>> carmichael.find_carmichael_numbers_in_range(0, 562)
|
|
[561]
|
|
>>> carmichael.find_carmichael_numbers_in_range(0,1000)
|
|
[561]
|
|
>>> carmichael.find_carmichael_numbers_in_range(0,2000)
|
|
[561, 1105, 1729]
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Carmichael_number
|
|
.. [2] https://en.wikipedia.org/wiki/Fermat_primality_test
|
|
.. [3] https://www.jstor.org/stable/23248683?seq=1#metadata_info_tab_contents
|
|
"""
|
|
|
|
@staticmethod
|
|
def is_perfect_square(n):
|
|
sympy_deprecation_warning(
|
|
"""
|
|
is_perfect_square is just a wrapper around sympy.ntheory.primetest.is_square
|
|
so use that directly instead.
|
|
""",
|
|
deprecated_since_version="1.11",
|
|
active_deprecations_target='deprecated-carmichael-static-methods',
|
|
)
|
|
return is_square(n)
|
|
|
|
@staticmethod
|
|
def divides(p, n):
|
|
sympy_deprecation_warning(
|
|
"""
|
|
divides can be replaced by directly testing n % p == 0.
|
|
""",
|
|
deprecated_since_version="1.11",
|
|
active_deprecations_target='deprecated-carmichael-static-methods',
|
|
)
|
|
return n % p == 0
|
|
|
|
@staticmethod
|
|
def is_prime(n):
|
|
sympy_deprecation_warning(
|
|
"""
|
|
is_prime is just a wrapper around sympy.ntheory.primetest.isprime so use that
|
|
directly instead.
|
|
""",
|
|
deprecated_since_version="1.11",
|
|
active_deprecations_target='deprecated-carmichael-static-methods',
|
|
)
|
|
return isprime(n)
|
|
|
|
@staticmethod
|
|
def is_carmichael(n):
|
|
if n >= 0:
|
|
if (n == 1) or isprime(n) or (n % 2 == 0):
|
|
return False
|
|
|
|
divisors = [1, n]
|
|
|
|
# get divisors
|
|
divisors.extend([i for i in range(3, n // 2 + 1, 2) if n % i == 0])
|
|
|
|
for i in divisors:
|
|
if is_square(i) and i != 1:
|
|
return False
|
|
if isprime(i):
|
|
if not _divides(i - 1, n - 1):
|
|
return False
|
|
|
|
return True
|
|
|
|
else:
|
|
raise ValueError('The provided number must be greater than or equal to 0')
|
|
|
|
@staticmethod
|
|
def find_carmichael_numbers_in_range(x, y):
|
|
if 0 <= x <= y:
|
|
if x % 2 == 0:
|
|
return [i for i in range(x + 1, y, 2) if carmichael.is_carmichael(i)]
|
|
else:
|
|
return [i for i in range(x, y, 2) if carmichael.is_carmichael(i)]
|
|
|
|
else:
|
|
raise ValueError('The provided range is not valid. x and y must be non-negative integers and x <= y')
|
|
|
|
@staticmethod
|
|
def find_first_n_carmichaels(n):
|
|
i = 1
|
|
carmichaels = []
|
|
|
|
while len(carmichaels) < n:
|
|
if carmichael.is_carmichael(i):
|
|
carmichaels.append(i)
|
|
i += 2
|
|
|
|
return carmichaels
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Fibonacci numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
|
|
class fibonacci(Function):
|
|
r"""
|
|
Fibonacci numbers / Fibonacci polynomials
|
|
|
|
The Fibonacci numbers are the integer sequence defined by the
|
|
initial terms `F_0 = 0`, `F_1 = 1` and the two-term recurrence
|
|
relation `F_n = F_{n-1} + F_{n-2}`. This definition
|
|
extended to arbitrary real and complex arguments using
|
|
the formula
|
|
|
|
.. math :: F_z = \frac{\phi^z - \cos(\pi z) \phi^{-z}}{\sqrt 5}
|
|
|
|
The Fibonacci polynomials are defined by `F_1(x) = 1`,
|
|
`F_2(x) = x`, and `F_n(x) = x*F_{n-1}(x) + F_{n-2}(x)` for `n > 2`.
|
|
For all positive integers `n`, `F_n(1) = F_n`.
|
|
|
|
* ``fibonacci(n)`` gives the `n^{th}` Fibonacci number, `F_n`
|
|
* ``fibonacci(n, x)`` gives the `n^{th}` Fibonacci polynomial in `x`, `F_n(x)`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import fibonacci, Symbol
|
|
|
|
>>> [fibonacci(x) for x in range(11)]
|
|
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
|
|
>>> fibonacci(5, Symbol('t'))
|
|
t**4 + 3*t**2 + 1
|
|
|
|
See Also
|
|
========
|
|
|
|
bell, bernoulli, catalan, euler, harmonic, lucas, genocchi, partition, tribonacci
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Fibonacci_number
|
|
.. [2] https://mathworld.wolfram.com/FibonacciNumber.html
|
|
|
|
"""
|
|
|
|
@staticmethod
|
|
def _fib(n):
|
|
return _ifib(n)
|
|
|
|
@staticmethod
|
|
@recurrence_memo([None, S.One, _sym])
|
|
def _fibpoly(n, prev):
|
|
return (prev[-2] + _sym*prev[-1]).expand()
|
|
|
|
@classmethod
|
|
def eval(cls, n, sym=None):
|
|
if n is S.Infinity:
|
|
return S.Infinity
|
|
|
|
if n.is_Integer:
|
|
if sym is None:
|
|
n = int(n)
|
|
if n < 0:
|
|
return S.NegativeOne**(n + 1) * fibonacci(-n)
|
|
else:
|
|
return Integer(cls._fib(n))
|
|
else:
|
|
if n < 1:
|
|
raise ValueError("Fibonacci polynomials are defined "
|
|
"only for positive integer indices.")
|
|
return cls._fibpoly(n).subs(_sym, sym)
|
|
|
|
def _eval_rewrite_as_sqrt(self, n, **kwargs):
|
|
from sympy.functions.elementary.miscellaneous import sqrt
|
|
return 2**(-n)*sqrt(5)*((1 + sqrt(5))**n - (-sqrt(5) + 1)**n) / 5
|
|
|
|
def _eval_rewrite_as_GoldenRatio(self,n, **kwargs):
|
|
return (S.GoldenRatio**n - 1/(-S.GoldenRatio)**n)/(2*S.GoldenRatio-1)
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Lucas numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
|
|
class lucas(Function):
|
|
"""
|
|
Lucas numbers
|
|
|
|
Lucas numbers satisfy a recurrence relation similar to that of
|
|
the Fibonacci sequence, in which each term is the sum of the
|
|
preceding two. They are generated by choosing the initial
|
|
values `L_0 = 2` and `L_1 = 1`.
|
|
|
|
* ``lucas(n)`` gives the `n^{th}` Lucas number
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import lucas
|
|
|
|
>>> [lucas(x) for x in range(11)]
|
|
[2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 123]
|
|
|
|
See Also
|
|
========
|
|
|
|
bell, bernoulli, catalan, euler, fibonacci, harmonic, genocchi, partition, tribonacci
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Lucas_number
|
|
.. [2] https://mathworld.wolfram.com/LucasNumber.html
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
def eval(cls, n):
|
|
if n is S.Infinity:
|
|
return S.Infinity
|
|
|
|
if n.is_Integer:
|
|
return fibonacci(n + 1) + fibonacci(n - 1)
|
|
|
|
def _eval_rewrite_as_sqrt(self, n, **kwargs):
|
|
from sympy.functions.elementary.miscellaneous import sqrt
|
|
return 2**(-n)*((1 + sqrt(5))**n + (-sqrt(5) + 1)**n)
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Tribonacci numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
|
|
class tribonacci(Function):
|
|
r"""
|
|
Tribonacci numbers / Tribonacci polynomials
|
|
|
|
The Tribonacci numbers are the integer sequence defined by the
|
|
initial terms `T_0 = 0`, `T_1 = 1`, `T_2 = 1` and the three-term
|
|
recurrence relation `T_n = T_{n-1} + T_{n-2} + T_{n-3}`.
|
|
|
|
The Tribonacci polynomials are defined by `T_0(x) = 0`, `T_1(x) = 1`,
|
|
`T_2(x) = x^2`, and `T_n(x) = x^2 T_{n-1}(x) + x T_{n-2}(x) + T_{n-3}(x)`
|
|
for `n > 2`. For all positive integers `n`, `T_n(1) = T_n`.
|
|
|
|
* ``tribonacci(n)`` gives the `n^{th}` Tribonacci number, `T_n`
|
|
* ``tribonacci(n, x)`` gives the `n^{th}` Tribonacci polynomial in `x`, `T_n(x)`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import tribonacci, Symbol
|
|
|
|
>>> [tribonacci(x) for x in range(11)]
|
|
[0, 1, 1, 2, 4, 7, 13, 24, 44, 81, 149]
|
|
>>> tribonacci(5, Symbol('t'))
|
|
t**8 + 3*t**5 + 3*t**2
|
|
|
|
See Also
|
|
========
|
|
|
|
bell, bernoulli, catalan, euler, fibonacci, harmonic, lucas, genocchi, partition
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Generalizations_of_Fibonacci_numbers#Tribonacci_numbers
|
|
.. [2] https://mathworld.wolfram.com/TribonacciNumber.html
|
|
.. [3] https://oeis.org/A000073
|
|
|
|
"""
|
|
|
|
@staticmethod
|
|
@recurrence_memo([S.Zero, S.One, S.One])
|
|
def _trib(n, prev):
|
|
return (prev[-3] + prev[-2] + prev[-1])
|
|
|
|
@staticmethod
|
|
@recurrence_memo([S.Zero, S.One, _sym**2])
|
|
def _tribpoly(n, prev):
|
|
return (prev[-3] + _sym*prev[-2] + _sym**2*prev[-1]).expand()
|
|
|
|
@classmethod
|
|
def eval(cls, n, sym=None):
|
|
if n is S.Infinity:
|
|
return S.Infinity
|
|
|
|
if n.is_Integer:
|
|
n = int(n)
|
|
if n < 0:
|
|
raise ValueError("Tribonacci polynomials are defined "
|
|
"only for non-negative integer indices.")
|
|
if sym is None:
|
|
return Integer(cls._trib(n))
|
|
else:
|
|
return cls._tribpoly(n).subs(_sym, sym)
|
|
|
|
def _eval_rewrite_as_sqrt(self, n, **kwargs):
|
|
from sympy.functions.elementary.miscellaneous import cbrt, sqrt
|
|
w = (-1 + S.ImaginaryUnit * sqrt(3)) / 2
|
|
a = (1 + cbrt(19 + 3*sqrt(33)) + cbrt(19 - 3*sqrt(33))) / 3
|
|
b = (1 + w*cbrt(19 + 3*sqrt(33)) + w**2*cbrt(19 - 3*sqrt(33))) / 3
|
|
c = (1 + w**2*cbrt(19 + 3*sqrt(33)) + w*cbrt(19 - 3*sqrt(33))) / 3
|
|
Tn = (a**(n + 1)/((a - b)*(a - c))
|
|
+ b**(n + 1)/((b - a)*(b - c))
|
|
+ c**(n + 1)/((c - a)*(c - b)))
|
|
return Tn
|
|
|
|
def _eval_rewrite_as_TribonacciConstant(self, n, **kwargs):
|
|
from sympy.functions.elementary.integers import floor
|
|
from sympy.functions.elementary.miscellaneous import cbrt, sqrt
|
|
b = cbrt(586 + 102*sqrt(33))
|
|
Tn = 3 * b * S.TribonacciConstant**n / (b**2 - 2*b + 4)
|
|
return floor(Tn + S.Half)
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Bernoulli numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
|
|
class bernoulli(Function):
|
|
r"""
|
|
Bernoulli numbers / Bernoulli polynomials / Bernoulli function
|
|
|
|
The Bernoulli numbers are a sequence of rational numbers
|
|
defined by `B_0 = 1` and the recursive relation (`n > 0`):
|
|
|
|
.. math :: n+1 = \sum_{k=0}^n \binom{n+1}{k} B_k
|
|
|
|
They are also commonly defined by their exponential generating
|
|
function, which is `\frac{x}{1 - e^{-x}}`. For odd indices > 1,
|
|
the Bernoulli numbers are zero.
|
|
|
|
The Bernoulli polynomials satisfy the analogous formula:
|
|
|
|
.. math :: B_n(x) = \sum_{k=0}^n (-1)^k \binom{n}{k} B_k x^{n-k}
|
|
|
|
Bernoulli numbers and Bernoulli polynomials are related as
|
|
`B_n(1) = B_n`.
|
|
|
|
The generalized Bernoulli function `\operatorname{B}(s, a)`
|
|
is defined for any complex `s` and `a`, except where `a` is a
|
|
nonpositive integer and `s` is not a nonnegative integer. It is
|
|
an entire function of `s` for fixed `a`, related to the Hurwitz
|
|
zeta function by
|
|
|
|
.. math:: \operatorname{B}(s, a) = \begin{cases}
|
|
-s \zeta(1-s, a) & s \ne 0 \\ 1 & s = 0 \end{cases}
|
|
|
|
When `s` is a nonnegative integer this function reduces to the
|
|
Bernoulli polynomials: `\operatorname{B}(n, x) = B_n(x)`. When
|
|
`a` is omitted it is assumed to be 1, yielding the (ordinary)
|
|
Bernoulli function which interpolates the Bernoulli numbers and is
|
|
related to the Riemann zeta function.
|
|
|
|
We compute Bernoulli numbers using Ramanujan's formula:
|
|
|
|
.. math :: B_n = \frac{A(n) - S(n)}{\binom{n+3}{n}}
|
|
|
|
where:
|
|
|
|
.. math :: A(n) = \begin{cases} \frac{n+3}{3} &
|
|
n \equiv 0\ \text{or}\ 2 \pmod{6} \\
|
|
-\frac{n+3}{6} & n \equiv 4 \pmod{6} \end{cases}
|
|
|
|
and:
|
|
|
|
.. math :: S(n) = \sum_{k=1}^{[n/6]} \binom{n+3}{n-6k} B_{n-6k}
|
|
|
|
This formula is similar to the sum given in the definition, but
|
|
cuts `\frac{2}{3}` of the terms. For Bernoulli polynomials, we use
|
|
Appell sequences.
|
|
|
|
For `n` a nonnegative integer and `s`, `a`, `x` arbitrary complex numbers,
|
|
|
|
* ``bernoulli(n)`` gives the nth Bernoulli number, `B_n`
|
|
* ``bernoulli(s)`` gives the Bernoulli function `\operatorname{B}(s)`
|
|
* ``bernoulli(n, x)`` gives the nth Bernoulli polynomial in `x`, `B_n(x)`
|
|
* ``bernoulli(s, a)`` gives the generalized Bernoulli function
|
|
`\operatorname{B}(s, a)`
|
|
|
|
.. versionchanged:: 1.12
|
|
``bernoulli(1)`` gives `+\frac{1}{2}` instead of `-\frac{1}{2}`.
|
|
This choice of value confers several theoretical advantages [5]_,
|
|
including the extension to complex parameters described above
|
|
which this function now implements. The previous behavior, defined
|
|
only for nonnegative integers `n`, can be obtained with
|
|
``(-1)**n*bernoulli(n)``.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import bernoulli
|
|
>>> from sympy.abc import x
|
|
>>> [bernoulli(n) for n in range(11)]
|
|
[1, 1/2, 1/6, 0, -1/30, 0, 1/42, 0, -1/30, 0, 5/66]
|
|
>>> bernoulli(1000001)
|
|
0
|
|
>>> bernoulli(3, x)
|
|
x**3 - 3*x**2/2 + x/2
|
|
|
|
See Also
|
|
========
|
|
|
|
andre, bell, catalan, euler, fibonacci, harmonic, lucas, genocchi,
|
|
partition, tribonacci, sympy.polys.appellseqs.bernoulli_poly
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Bernoulli_number
|
|
.. [2] https://en.wikipedia.org/wiki/Bernoulli_polynomial
|
|
.. [3] https://mathworld.wolfram.com/BernoulliNumber.html
|
|
.. [4] https://mathworld.wolfram.com/BernoulliPolynomial.html
|
|
.. [5] Peter Luschny, "The Bernoulli Manifesto",
|
|
https://luschny.de/math/zeta/The-Bernoulli-Manifesto.html
|
|
.. [6] Peter Luschny, "An introduction to the Bernoulli function",
|
|
https://arxiv.org/abs/2009.06743
|
|
|
|
"""
|
|
|
|
args: tTuple[Integer]
|
|
|
|
# Calculates B_n for positive even n
|
|
@staticmethod
|
|
def _calc_bernoulli(n):
|
|
s = 0
|
|
a = int(binomial(n + 3, n - 6))
|
|
for j in range(1, n//6 + 1):
|
|
s += a * bernoulli(n - 6*j)
|
|
# Avoid computing each binomial coefficient from scratch
|
|
a *= _product(n - 6 - 6*j + 1, n - 6*j)
|
|
a //= _product(6*j + 4, 6*j + 9)
|
|
if n % 6 == 4:
|
|
s = -Rational(n + 3, 6) - s
|
|
else:
|
|
s = Rational(n + 3, 3) - s
|
|
return s / binomial(n + 3, n)
|
|
|
|
# We implement a specialized memoization scheme to handle each
|
|
# case modulo 6 separately
|
|
_cache = {0: S.One, 2: Rational(1, 6), 4: Rational(-1, 30)}
|
|
_highest = {0: 0, 2: 2, 4: 4}
|
|
|
|
@classmethod
|
|
def eval(cls, n, x=None):
|
|
if x is S.One:
|
|
return cls(n)
|
|
elif n.is_zero:
|
|
return S.One
|
|
elif n.is_integer is False or n.is_nonnegative is False:
|
|
if x is not None and x.is_Integer and x.is_nonpositive:
|
|
return S.NaN
|
|
return
|
|
# Bernoulli numbers
|
|
elif x is None:
|
|
if n is S.One:
|
|
return S.Half
|
|
elif n.is_odd and (n-1).is_positive:
|
|
return S.Zero
|
|
elif n.is_Number:
|
|
n = int(n)
|
|
# Use mpmath for enormous Bernoulli numbers
|
|
if n > 500:
|
|
p, q = mp.bernfrac(n)
|
|
return Rational(int(p), int(q))
|
|
case = n % 6
|
|
highest_cached = cls._highest[case]
|
|
if n <= highest_cached:
|
|
return cls._cache[n]
|
|
# To avoid excessive recursion when, say, bernoulli(1000) is
|
|
# requested, calculate and cache the entire sequence ... B_988,
|
|
# B_994, B_1000 in increasing order
|
|
for i in range(highest_cached + 6, n + 6, 6):
|
|
b = cls._calc_bernoulli(i)
|
|
cls._cache[i] = b
|
|
cls._highest[case] = i
|
|
return b
|
|
# Bernoulli polynomials
|
|
elif n.is_Number:
|
|
return bernoulli_poly(n, x)
|
|
|
|
def _eval_rewrite_as_zeta(self, n, x=1, **kwargs):
|
|
from sympy.functions.special.zeta_functions import zeta
|
|
return Piecewise((1, Eq(n, 0)), (-n * zeta(1-n, x), True))
|
|
|
|
def _eval_evalf(self, prec):
|
|
if not all(x.is_number for x in self.args):
|
|
return
|
|
n = self.args[0]._to_mpmath(prec)
|
|
x = (self.args[1] if len(self.args) > 1 else S.One)._to_mpmath(prec)
|
|
with workprec(prec):
|
|
if n == 0:
|
|
res = mp.mpf(1)
|
|
elif n == 1:
|
|
res = x - mp.mpf(0.5)
|
|
elif mp.isint(n) and n >= 0:
|
|
res = mp.bernoulli(n) if x == 1 else mp.bernpoly(n, x)
|
|
else:
|
|
res = -n * mp.zeta(1-n, x)
|
|
return Expr._from_mpmath(res, prec)
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Bell numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
|
|
class bell(Function):
|
|
r"""
|
|
Bell numbers / Bell polynomials
|
|
|
|
The Bell numbers satisfy `B_0 = 1` and
|
|
|
|
.. math:: B_n = \sum_{k=0}^{n-1} \binom{n-1}{k} B_k.
|
|
|
|
They are also given by:
|
|
|
|
.. math:: B_n = \frac{1}{e} \sum_{k=0}^{\infty} \frac{k^n}{k!}.
|
|
|
|
The Bell polynomials are given by `B_0(x) = 1` and
|
|
|
|
.. math:: B_n(x) = x \sum_{k=1}^{n-1} \binom{n-1}{k-1} B_{k-1}(x).
|
|
|
|
The second kind of Bell polynomials (are sometimes called "partial" Bell
|
|
polynomials or incomplete Bell polynomials) are defined as
|
|
|
|
.. math:: B_{n,k}(x_1, x_2,\dotsc x_{n-k+1}) =
|
|
\sum_{j_1+j_2+j_2+\dotsb=k \atop j_1+2j_2+3j_2+\dotsb=n}
|
|
\frac{n!}{j_1!j_2!\dotsb j_{n-k+1}!}
|
|
\left(\frac{x_1}{1!} \right)^{j_1}
|
|
\left(\frac{x_2}{2!} \right)^{j_2} \dotsb
|
|
\left(\frac{x_{n-k+1}}{(n-k+1)!} \right) ^{j_{n-k+1}}.
|
|
|
|
* ``bell(n)`` gives the `n^{th}` Bell number, `B_n`.
|
|
* ``bell(n, x)`` gives the `n^{th}` Bell polynomial, `B_n(x)`.
|
|
* ``bell(n, k, (x1, x2, ...))`` gives Bell polynomials of the second kind,
|
|
`B_{n,k}(x_1, x_2, \dotsc, x_{n-k+1})`.
|
|
|
|
Notes
|
|
=====
|
|
|
|
Not to be confused with Bernoulli numbers and Bernoulli polynomials,
|
|
which use the same notation.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import bell, Symbol, symbols
|
|
|
|
>>> [bell(n) for n in range(11)]
|
|
[1, 1, 2, 5, 15, 52, 203, 877, 4140, 21147, 115975]
|
|
>>> bell(30)
|
|
846749014511809332450147
|
|
>>> bell(4, Symbol('t'))
|
|
t**4 + 6*t**3 + 7*t**2 + t
|
|
>>> bell(6, 2, symbols('x:6')[1:])
|
|
6*x1*x5 + 15*x2*x4 + 10*x3**2
|
|
|
|
See Also
|
|
========
|
|
|
|
bernoulli, catalan, euler, fibonacci, harmonic, lucas, genocchi, partition, tribonacci
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Bell_number
|
|
.. [2] https://mathworld.wolfram.com/BellNumber.html
|
|
.. [3] https://mathworld.wolfram.com/BellPolynomial.html
|
|
|
|
"""
|
|
|
|
@staticmethod
|
|
@recurrence_memo([1, 1])
|
|
def _bell(n, prev):
|
|
s = 1
|
|
a = 1
|
|
for k in range(1, n):
|
|
a = a * (n - k) // k
|
|
s += a * prev[k]
|
|
return s
|
|
|
|
@staticmethod
|
|
@recurrence_memo([S.One, _sym])
|
|
def _bell_poly(n, prev):
|
|
s = 1
|
|
a = 1
|
|
for k in range(2, n + 1):
|
|
a = a * (n - k + 1) // (k - 1)
|
|
s += a * prev[k - 1]
|
|
return expand_mul(_sym * s)
|
|
|
|
@staticmethod
|
|
def _bell_incomplete_poly(n, k, symbols):
|
|
r"""
|
|
The second kind of Bell polynomials (incomplete Bell polynomials).
|
|
|
|
Calculated by recurrence formula:
|
|
|
|
.. math:: B_{n,k}(x_1, x_2, \dotsc, x_{n-k+1}) =
|
|
\sum_{m=1}^{n-k+1}
|
|
\x_m \binom{n-1}{m-1} B_{n-m,k-1}(x_1, x_2, \dotsc, x_{n-m-k})
|
|
|
|
where
|
|
`B_{0,0} = 1;`
|
|
`B_{n,0} = 0; for n \ge 1`
|
|
`B_{0,k} = 0; for k \ge 1`
|
|
|
|
"""
|
|
if (n == 0) and (k == 0):
|
|
return S.One
|
|
elif (n == 0) or (k == 0):
|
|
return S.Zero
|
|
s = S.Zero
|
|
a = S.One
|
|
for m in range(1, n - k + 2):
|
|
s += a * bell._bell_incomplete_poly(
|
|
n - m, k - 1, symbols) * symbols[m - 1]
|
|
a = a * (n - m) / m
|
|
return expand_mul(s)
|
|
|
|
@classmethod
|
|
def eval(cls, n, k_sym=None, symbols=None):
|
|
if n is S.Infinity:
|
|
if k_sym is None:
|
|
return S.Infinity
|
|
else:
|
|
raise ValueError("Bell polynomial is not defined")
|
|
|
|
if n.is_negative or n.is_integer is False:
|
|
raise ValueError("a non-negative integer expected")
|
|
|
|
if n.is_Integer and n.is_nonnegative:
|
|
if k_sym is None:
|
|
return Integer(cls._bell(int(n)))
|
|
elif symbols is None:
|
|
return cls._bell_poly(int(n)).subs(_sym, k_sym)
|
|
else:
|
|
r = cls._bell_incomplete_poly(int(n), int(k_sym), symbols)
|
|
return r
|
|
|
|
def _eval_rewrite_as_Sum(self, n, k_sym=None, symbols=None, **kwargs):
|
|
from sympy.concrete.summations import Sum
|
|
if (k_sym is not None) or (symbols is not None):
|
|
return self
|
|
|
|
# Dobinski's formula
|
|
if not n.is_nonnegative:
|
|
return self
|
|
k = Dummy('k', integer=True, nonnegative=True)
|
|
return 1 / E * Sum(k**n / factorial(k), (k, 0, S.Infinity))
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Harmonic numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
|
|
class harmonic(Function):
|
|
r"""
|
|
Harmonic numbers
|
|
|
|
The nth harmonic number is given by `\operatorname{H}_{n} =
|
|
1 + \frac{1}{2} + \frac{1}{3} + \ldots + \frac{1}{n}`.
|
|
|
|
More generally:
|
|
|
|
.. math:: \operatorname{H}_{n,m} = \sum_{k=1}^{n} \frac{1}{k^m}
|
|
|
|
As `n \rightarrow \infty`, `\operatorname{H}_{n,m} \rightarrow \zeta(m)`,
|
|
the Riemann zeta function.
|
|
|
|
* ``harmonic(n)`` gives the nth harmonic number, `\operatorname{H}_n`
|
|
|
|
* ``harmonic(n, m)`` gives the nth generalized harmonic number
|
|
of order `m`, `\operatorname{H}_{n,m}`, where
|
|
``harmonic(n) == harmonic(n, 1)``
|
|
|
|
This function can be extended to complex `n` and `m` where `n` is not a
|
|
negative integer or `m` is a nonpositive integer as
|
|
|
|
.. math:: \operatorname{H}_{n,m} = \begin{cases} \zeta(m) - \zeta(m, n+1)
|
|
& m \ne 1 \\ \psi(n+1) + \gamma & m = 1 \end{cases}
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import harmonic, oo
|
|
|
|
>>> [harmonic(n) for n in range(6)]
|
|
[0, 1, 3/2, 11/6, 25/12, 137/60]
|
|
>>> [harmonic(n, 2) for n in range(6)]
|
|
[0, 1, 5/4, 49/36, 205/144, 5269/3600]
|
|
>>> harmonic(oo, 2)
|
|
pi**2/6
|
|
|
|
>>> from sympy import Symbol, Sum
|
|
>>> n = Symbol("n")
|
|
|
|
>>> harmonic(n).rewrite(Sum)
|
|
Sum(1/_k, (_k, 1, n))
|
|
|
|
We can evaluate harmonic numbers for all integral and positive
|
|
rational arguments:
|
|
|
|
>>> from sympy import S, expand_func, simplify
|
|
>>> harmonic(8)
|
|
761/280
|
|
>>> harmonic(11)
|
|
83711/27720
|
|
|
|
>>> H = harmonic(1/S(3))
|
|
>>> H
|
|
harmonic(1/3)
|
|
>>> He = expand_func(H)
|
|
>>> He
|
|
-log(6) - sqrt(3)*pi/6 + 2*Sum(log(sin(_k*pi/3))*cos(2*_k*pi/3), (_k, 1, 1))
|
|
+ 3*Sum(1/(3*_k + 1), (_k, 0, 0))
|
|
>>> He.doit()
|
|
-log(6) - sqrt(3)*pi/6 - log(sqrt(3)/2) + 3
|
|
>>> H = harmonic(25/S(7))
|
|
>>> He = simplify(expand_func(H).doit())
|
|
>>> He
|
|
log(sin(2*pi/7)**(2*cos(16*pi/7))/(14*sin(pi/7)**(2*cos(pi/7))*cos(pi/14)**(2*sin(pi/14)))) + pi*tan(pi/14)/2 + 30247/9900
|
|
>>> He.n(40)
|
|
1.983697455232980674869851942390639915940
|
|
>>> harmonic(25/S(7)).n(40)
|
|
1.983697455232980674869851942390639915940
|
|
|
|
We can rewrite harmonic numbers in terms of polygamma functions:
|
|
|
|
>>> from sympy import digamma, polygamma
|
|
>>> m = Symbol("m", integer=True, positive=True)
|
|
|
|
>>> harmonic(n).rewrite(digamma)
|
|
polygamma(0, n + 1) + EulerGamma
|
|
|
|
>>> harmonic(n).rewrite(polygamma)
|
|
polygamma(0, n + 1) + EulerGamma
|
|
|
|
>>> harmonic(n,3).rewrite(polygamma)
|
|
polygamma(2, n + 1)/2 + zeta(3)
|
|
|
|
>>> simplify(harmonic(n,m).rewrite(polygamma))
|
|
Piecewise((polygamma(0, n + 1) + EulerGamma, Eq(m, 1)),
|
|
(-(-1)**m*polygamma(m - 1, n + 1)/factorial(m - 1) + zeta(m), True))
|
|
|
|
Integer offsets in the argument can be pulled out:
|
|
|
|
>>> from sympy import expand_func
|
|
|
|
>>> expand_func(harmonic(n+4))
|
|
harmonic(n) + 1/(n + 4) + 1/(n + 3) + 1/(n + 2) + 1/(n + 1)
|
|
|
|
>>> expand_func(harmonic(n-4))
|
|
harmonic(n) - 1/(n - 1) - 1/(n - 2) - 1/(n - 3) - 1/n
|
|
|
|
Some limits can be computed as well:
|
|
|
|
>>> from sympy import limit, oo
|
|
|
|
>>> limit(harmonic(n), n, oo)
|
|
oo
|
|
|
|
>>> limit(harmonic(n, 2), n, oo)
|
|
pi**2/6
|
|
|
|
>>> limit(harmonic(n, 3), n, oo)
|
|
zeta(3)
|
|
|
|
For `m > 1`, `H_{n,m}` tends to `\zeta(m)` in the limit of infinite `n`:
|
|
|
|
>>> m = Symbol("m", positive=True)
|
|
>>> limit(harmonic(n, m+1), n, oo)
|
|
zeta(m + 1)
|
|
|
|
See Also
|
|
========
|
|
|
|
bell, bernoulli, catalan, euler, fibonacci, lucas, genocchi, partition, tribonacci
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Harmonic_number
|
|
.. [2] https://functions.wolfram.com/GammaBetaErf/HarmonicNumber/
|
|
.. [3] https://functions.wolfram.com/GammaBetaErf/HarmonicNumber2/
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
def eval(cls, n, m=None):
|
|
from sympy.functions.special.zeta_functions import zeta
|
|
if m is S.One:
|
|
return cls(n)
|
|
if m is None:
|
|
m = S.One
|
|
if n.is_zero:
|
|
return S.Zero
|
|
elif m.is_zero:
|
|
return n
|
|
elif n is S.Infinity:
|
|
if m.is_negative:
|
|
return S.NaN
|
|
elif is_le(m, S.One):
|
|
return S.Infinity
|
|
elif is_gt(m, S.One):
|
|
return zeta(m)
|
|
elif m.is_Integer and m.is_nonpositive:
|
|
return (bernoulli(1-m, n+1) - bernoulli(1-m)) / (1-m)
|
|
elif n.is_Integer:
|
|
if n.is_negative and (m.is_integer is False or m.is_nonpositive is False):
|
|
return S.ComplexInfinity if m is S.One else S.NaN
|
|
if n.is_nonnegative:
|
|
return Add(*(k**(-m) for k in range(1, int(n)+1)))
|
|
|
|
def _eval_rewrite_as_polygamma(self, n, m=S.One, **kwargs):
|
|
from sympy.functions.special.gamma_functions import gamma, polygamma
|
|
if m.is_integer and m.is_positive:
|
|
return Piecewise((polygamma(0, n+1) + S.EulerGamma, Eq(m, 1)),
|
|
(S.NegativeOne**m * (polygamma(m-1, 1) - polygamma(m-1, n+1)) /
|
|
gamma(m), True))
|
|
|
|
def _eval_rewrite_as_digamma(self, n, m=1, **kwargs):
|
|
from sympy.functions.special.gamma_functions import polygamma
|
|
return self.rewrite(polygamma)
|
|
|
|
def _eval_rewrite_as_trigamma(self, n, m=1, **kwargs):
|
|
from sympy.functions.special.gamma_functions import polygamma
|
|
return self.rewrite(polygamma)
|
|
|
|
def _eval_rewrite_as_Sum(self, n, m=None, **kwargs):
|
|
from sympy.concrete.summations import Sum
|
|
k = Dummy("k", integer=True)
|
|
if m is None:
|
|
m = S.One
|
|
return Sum(k**(-m), (k, 1, n))
|
|
|
|
def _eval_rewrite_as_zeta(self, n, m=S.One, **kwargs):
|
|
from sympy.functions.special.zeta_functions import zeta
|
|
from sympy.functions.special.gamma_functions import digamma
|
|
return Piecewise((digamma(n + 1) + S.EulerGamma, Eq(m, 1)),
|
|
(zeta(m) - zeta(m, n+1), True))
|
|
|
|
def _eval_expand_func(self, **hints):
|
|
from sympy.concrete.summations import Sum
|
|
n = self.args[0]
|
|
m = self.args[1] if len(self.args) == 2 else 1
|
|
|
|
if m == S.One:
|
|
if n.is_Add:
|
|
off = n.args[0]
|
|
nnew = n - off
|
|
if off.is_Integer and off.is_positive:
|
|
result = [S.One/(nnew + i) for i in range(off, 0, -1)] + [harmonic(nnew)]
|
|
return Add(*result)
|
|
elif off.is_Integer and off.is_negative:
|
|
result = [-S.One/(nnew + i) for i in range(0, off, -1)] + [harmonic(nnew)]
|
|
return Add(*result)
|
|
|
|
if n.is_Rational:
|
|
# Expansions for harmonic numbers at general rational arguments (u + p/q)
|
|
# Split n as u + p/q with p < q
|
|
p, q = n.as_numer_denom()
|
|
u = p // q
|
|
p = p - u * q
|
|
if u.is_nonnegative and p.is_positive and q.is_positive and p < q:
|
|
from sympy.functions.elementary.exponential import log
|
|
from sympy.functions.elementary.integers import floor
|
|
from sympy.functions.elementary.trigonometric import sin, cos, cot
|
|
k = Dummy("k")
|
|
t1 = q * Sum(1 / (q * k + p), (k, 0, u))
|
|
t2 = 2 * Sum(cos((2 * pi * p * k) / S(q)) *
|
|
log(sin((pi * k) / S(q))),
|
|
(k, 1, floor((q - 1) / S(2))))
|
|
t3 = (pi / 2) * cot((pi * p) / q) + log(2 * q)
|
|
return t1 + t2 - t3
|
|
|
|
return self
|
|
|
|
def _eval_rewrite_as_tractable(self, n, m=1, limitvar=None, **kwargs):
|
|
from sympy.functions.special.zeta_functions import zeta
|
|
from sympy.functions.special.gamma_functions import polygamma
|
|
pg = self.rewrite(polygamma)
|
|
if not isinstance(pg, harmonic):
|
|
return pg.rewrite("tractable", deep=True)
|
|
arg = m - S.One
|
|
if arg.is_nonzero:
|
|
return (zeta(m) - zeta(m, n+1)).rewrite("tractable", deep=True)
|
|
|
|
def _eval_evalf(self, prec):
|
|
if not all(x.is_number for x in self.args):
|
|
return
|
|
n = self.args[0]._to_mpmath(prec)
|
|
m = (self.args[1] if len(self.args) > 1 else S.One)._to_mpmath(prec)
|
|
if mp.isint(n) and n < 0:
|
|
return S.NaN
|
|
with workprec(prec):
|
|
if m == 1:
|
|
res = mp.harmonic(n)
|
|
else:
|
|
res = mp.zeta(m) - mp.zeta(m, n+1)
|
|
return Expr._from_mpmath(res, prec)
|
|
|
|
def fdiff(self, argindex=1):
|
|
from sympy.functions.special.zeta_functions import zeta
|
|
if len(self.args) == 2:
|
|
n, m = self.args
|
|
else:
|
|
n, m = self.args + (1,)
|
|
if argindex == 1:
|
|
return m * zeta(m+1, n+1)
|
|
else:
|
|
raise ArgumentIndexError
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Euler numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
|
|
class euler(Function):
|
|
r"""
|
|
Euler numbers / Euler polynomials / Euler function
|
|
|
|
The Euler numbers are given by:
|
|
|
|
.. math:: E_{2n} = I \sum_{k=1}^{2n+1} \sum_{j=0}^k \binom{k}{j}
|
|
\frac{(-1)^j (k-2j)^{2n+1}}{2^k I^k k}
|
|
|
|
.. math:: E_{2n+1} = 0
|
|
|
|
Euler numbers and Euler polynomials are related by
|
|
|
|
.. math:: E_n = 2^n E_n\left(\frac{1}{2}\right).
|
|
|
|
We compute symbolic Euler polynomials using Appell sequences,
|
|
but numerical evaluation of the Euler polynomial is computed
|
|
more efficiently (and more accurately) using the mpmath library.
|
|
|
|
The Euler polynomials are special cases of the generalized Euler function,
|
|
related to the Genocchi function as
|
|
|
|
.. math:: \operatorname{E}(s, a) = -\frac{\operatorname{G}(s+1, a)}{s+1}
|
|
|
|
with the limit of `\psi\left(\frac{a+1}{2}\right) - \psi\left(\frac{a}{2}\right)`
|
|
being taken when `s = -1`. The (ordinary) Euler function interpolating
|
|
the Euler numbers is then obtained as
|
|
`\operatorname{E}(s) = 2^s \operatorname{E}\left(s, \frac{1}{2}\right)`.
|
|
|
|
* ``euler(n)`` gives the nth Euler number `E_n`.
|
|
* ``euler(s)`` gives the Euler function `\operatorname{E}(s)`.
|
|
* ``euler(n, x)`` gives the nth Euler polynomial `E_n(x)`.
|
|
* ``euler(s, a)`` gives the generalized Euler function `\operatorname{E}(s, a)`.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import euler, Symbol, S
|
|
>>> [euler(n) for n in range(10)]
|
|
[1, 0, -1, 0, 5, 0, -61, 0, 1385, 0]
|
|
>>> [2**n*euler(n,1) for n in range(10)]
|
|
[1, 1, 0, -2, 0, 16, 0, -272, 0, 7936]
|
|
>>> n = Symbol("n")
|
|
>>> euler(n + 2*n)
|
|
euler(3*n)
|
|
|
|
>>> x = Symbol("x")
|
|
>>> euler(n, x)
|
|
euler(n, x)
|
|
|
|
>>> euler(0, x)
|
|
1
|
|
>>> euler(1, x)
|
|
x - 1/2
|
|
>>> euler(2, x)
|
|
x**2 - x
|
|
>>> euler(3, x)
|
|
x**3 - 3*x**2/2 + 1/4
|
|
>>> euler(4, x)
|
|
x**4 - 2*x**3 + x
|
|
|
|
>>> euler(12, S.Half)
|
|
2702765/4096
|
|
>>> euler(12)
|
|
2702765
|
|
|
|
See Also
|
|
========
|
|
|
|
andre, bell, bernoulli, catalan, fibonacci, harmonic, lucas, genocchi,
|
|
partition, tribonacci, sympy.polys.appellseqs.euler_poly
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Euler_numbers
|
|
.. [2] https://mathworld.wolfram.com/EulerNumber.html
|
|
.. [3] https://en.wikipedia.org/wiki/Alternating_permutation
|
|
.. [4] https://mathworld.wolfram.com/AlternatingPermutation.html
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
def eval(cls, n, x=None):
|
|
if n.is_zero:
|
|
return S.One
|
|
elif n is S.NegativeOne:
|
|
if x is None:
|
|
return S.Pi/2
|
|
from sympy.functions.special.gamma_functions import digamma
|
|
return digamma((x+1)/2) - digamma(x/2)
|
|
elif n.is_integer is False or n.is_nonnegative is False:
|
|
return
|
|
# Euler numbers
|
|
elif x is None:
|
|
if n.is_odd and n.is_positive:
|
|
return S.Zero
|
|
elif n.is_Number:
|
|
from mpmath import mp
|
|
n = n._to_mpmath(mp.prec)
|
|
res = mp.eulernum(n, exact=True)
|
|
return Integer(res)
|
|
# Euler polynomials
|
|
elif n.is_Number:
|
|
return euler_poly(n, x)
|
|
|
|
def _eval_rewrite_as_Sum(self, n, x=None, **kwargs):
|
|
from sympy.concrete.summations import Sum
|
|
if x is None and n.is_even:
|
|
k = Dummy("k", integer=True)
|
|
j = Dummy("j", integer=True)
|
|
n = n / 2
|
|
Em = (S.ImaginaryUnit * Sum(Sum(binomial(k, j) * (S.NegativeOne**j *
|
|
(k - 2*j)**(2*n + 1)) /
|
|
(2**k*S.ImaginaryUnit**k * k), (j, 0, k)), (k, 1, 2*n + 1)))
|
|
return Em
|
|
if x:
|
|
k = Dummy("k", integer=True)
|
|
return Sum(binomial(n, k)*euler(k)/2**k*(x - S.Half)**(n - k), (k, 0, n))
|
|
|
|
def _eval_rewrite_as_genocchi(self, n, x=None, **kwargs):
|
|
if x is None:
|
|
return Piecewise((S.Pi/2, Eq(n, -1)),
|
|
(-2**n * genocchi(n+1, S.Half) / (n+1), True))
|
|
from sympy.functions.special.gamma_functions import digamma
|
|
return Piecewise((digamma((x+1)/2) - digamma(x/2), Eq(n, -1)),
|
|
(-genocchi(n+1, x) / (n+1), True))
|
|
|
|
def _eval_evalf(self, prec):
|
|
if not all(i.is_number for i in self.args):
|
|
return
|
|
from mpmath import mp
|
|
m, x = (self.args[0], None) if len(self.args) == 1 else self.args
|
|
m = m._to_mpmath(prec)
|
|
if x is not None:
|
|
x = x._to_mpmath(prec)
|
|
with workprec(prec):
|
|
if mp.isint(m) and m >= 0:
|
|
res = mp.eulernum(m) if x is None else mp.eulerpoly(m, x)
|
|
else:
|
|
if m == -1:
|
|
res = mp.pi if x is None else mp.digamma((x+1)/2) - mp.digamma(x/2)
|
|
else:
|
|
y = 0.5 if x is None else x
|
|
res = 2 * (mp.zeta(-m, y) - 2**(m+1) * mp.zeta(-m, (y+1)/2))
|
|
if x is None:
|
|
res *= 2**m
|
|
return Expr._from_mpmath(res, prec)
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Catalan numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
|
|
class catalan(Function):
|
|
r"""
|
|
Catalan numbers
|
|
|
|
The `n^{th}` catalan number is given by:
|
|
|
|
.. math :: C_n = \frac{1}{n+1} \binom{2n}{n}
|
|
|
|
* ``catalan(n)`` gives the `n^{th}` Catalan number, `C_n`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import (Symbol, binomial, gamma, hyper,
|
|
... catalan, diff, combsimp, Rational, I)
|
|
|
|
>>> [catalan(i) for i in range(1,10)]
|
|
[1, 2, 5, 14, 42, 132, 429, 1430, 4862]
|
|
|
|
>>> n = Symbol("n", integer=True)
|
|
|
|
>>> catalan(n)
|
|
catalan(n)
|
|
|
|
Catalan numbers can be transformed into several other, identical
|
|
expressions involving other mathematical functions
|
|
|
|
>>> catalan(n).rewrite(binomial)
|
|
binomial(2*n, n)/(n + 1)
|
|
|
|
>>> catalan(n).rewrite(gamma)
|
|
4**n*gamma(n + 1/2)/(sqrt(pi)*gamma(n + 2))
|
|
|
|
>>> catalan(n).rewrite(hyper)
|
|
hyper((1 - n, -n), (2,), 1)
|
|
|
|
For some non-integer values of n we can get closed form
|
|
expressions by rewriting in terms of gamma functions:
|
|
|
|
>>> catalan(Rational(1, 2)).rewrite(gamma)
|
|
8/(3*pi)
|
|
|
|
We can differentiate the Catalan numbers C(n) interpreted as a
|
|
continuous real function in n:
|
|
|
|
>>> diff(catalan(n), n)
|
|
(polygamma(0, n + 1/2) - polygamma(0, n + 2) + log(4))*catalan(n)
|
|
|
|
As a more advanced example consider the following ratio
|
|
between consecutive numbers:
|
|
|
|
>>> combsimp((catalan(n + 1)/catalan(n)).rewrite(binomial))
|
|
2*(2*n + 1)/(n + 2)
|
|
|
|
The Catalan numbers can be generalized to complex numbers:
|
|
|
|
>>> catalan(I).rewrite(gamma)
|
|
4**I*gamma(1/2 + I)/(sqrt(pi)*gamma(2 + I))
|
|
|
|
and evaluated with arbitrary precision:
|
|
|
|
>>> catalan(I).evalf(20)
|
|
0.39764993382373624267 - 0.020884341620842555705*I
|
|
|
|
See Also
|
|
========
|
|
|
|
andre, bell, bernoulli, euler, fibonacci, harmonic, lucas, genocchi,
|
|
partition, tribonacci, sympy.functions.combinatorial.factorials.binomial
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Catalan_number
|
|
.. [2] https://mathworld.wolfram.com/CatalanNumber.html
|
|
.. [3] https://functions.wolfram.com/GammaBetaErf/CatalanNumber/
|
|
.. [4] http://geometer.org/mathcircles/catalan.pdf
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
def eval(cls, n):
|
|
from sympy.functions.special.gamma_functions import gamma
|
|
if (n.is_Integer and n.is_nonnegative) or \
|
|
(n.is_noninteger and n.is_negative):
|
|
return 4**n*gamma(n + S.Half)/(gamma(S.Half)*gamma(n + 2))
|
|
|
|
if (n.is_integer and n.is_negative):
|
|
if (n + 1).is_negative:
|
|
return S.Zero
|
|
if (n + 1).is_zero:
|
|
return Rational(-1, 2)
|
|
|
|
def fdiff(self, argindex=1):
|
|
from sympy.functions.elementary.exponential import log
|
|
from sympy.functions.special.gamma_functions import polygamma
|
|
n = self.args[0]
|
|
return catalan(n)*(polygamma(0, n + S.Half) - polygamma(0, n + 2) + log(4))
|
|
|
|
def _eval_rewrite_as_binomial(self, n, **kwargs):
|
|
return binomial(2*n, n)/(n + 1)
|
|
|
|
def _eval_rewrite_as_factorial(self, n, **kwargs):
|
|
return factorial(2*n) / (factorial(n+1) * factorial(n))
|
|
|
|
def _eval_rewrite_as_gamma(self, n, piecewise=True, **kwargs):
|
|
from sympy.functions.special.gamma_functions import gamma
|
|
# The gamma function allows to generalize Catalan numbers to complex n
|
|
return 4**n*gamma(n + S.Half)/(gamma(S.Half)*gamma(n + 2))
|
|
|
|
def _eval_rewrite_as_hyper(self, n, **kwargs):
|
|
from sympy.functions.special.hyper import hyper
|
|
return hyper([1 - n, -n], [2], 1)
|
|
|
|
def _eval_rewrite_as_Product(self, n, **kwargs):
|
|
from sympy.concrete.products import Product
|
|
if not (n.is_integer and n.is_nonnegative):
|
|
return self
|
|
k = Dummy('k', integer=True, positive=True)
|
|
return Product((n + k) / k, (k, 2, n))
|
|
|
|
def _eval_is_integer(self):
|
|
if self.args[0].is_integer and self.args[0].is_nonnegative:
|
|
return True
|
|
|
|
def _eval_is_positive(self):
|
|
if self.args[0].is_nonnegative:
|
|
return True
|
|
|
|
def _eval_is_composite(self):
|
|
if self.args[0].is_integer and (self.args[0] - 3).is_positive:
|
|
return True
|
|
|
|
def _eval_evalf(self, prec):
|
|
from sympy.functions.special.gamma_functions import gamma
|
|
if self.args[0].is_number:
|
|
return self.rewrite(gamma)._eval_evalf(prec)
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Genocchi numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
|
|
class genocchi(Function):
|
|
r"""
|
|
Genocchi numbers / Genocchi polynomials / Genocchi function
|
|
|
|
The Genocchi numbers are a sequence of integers `G_n` that satisfy the
|
|
relation:
|
|
|
|
.. math:: \frac{-2t}{1 + e^{-t}} = \sum_{n=0}^\infty \frac{G_n t^n}{n!}
|
|
|
|
They are related to the Bernoulli numbers by
|
|
|
|
.. math:: G_n = 2 (1 - 2^n) B_n
|
|
|
|
and generalize like the Bernoulli numbers to the Genocchi polynomials and
|
|
function as
|
|
|
|
.. math:: \operatorname{G}(s, a) = 2 \left(\operatorname{B}(s, a) -
|
|
2^s \operatorname{B}\left(s, \frac{a+1}{2}\right)\right)
|
|
|
|
.. versionchanged:: 1.12
|
|
``genocchi(1)`` gives `-1` instead of `1`.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import genocchi, Symbol
|
|
>>> [genocchi(n) for n in range(9)]
|
|
[0, -1, -1, 0, 1, 0, -3, 0, 17]
|
|
>>> n = Symbol('n', integer=True, positive=True)
|
|
>>> genocchi(2*n + 1)
|
|
0
|
|
>>> x = Symbol('x')
|
|
>>> genocchi(4, x)
|
|
-4*x**3 + 6*x**2 - 1
|
|
|
|
See Also
|
|
========
|
|
|
|
bell, bernoulli, catalan, euler, fibonacci, harmonic, lucas, partition, tribonacci
|
|
sympy.polys.appellseqs.genocchi_poly
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Genocchi_number
|
|
.. [2] https://mathworld.wolfram.com/GenocchiNumber.html
|
|
.. [3] Peter Luschny, "An introduction to the Bernoulli function",
|
|
https://arxiv.org/abs/2009.06743
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
def eval(cls, n, x=None):
|
|
if x is S.One:
|
|
return cls(n)
|
|
elif n.is_integer is False or n.is_nonnegative is False:
|
|
return
|
|
# Genocchi numbers
|
|
elif x is None:
|
|
if n.is_odd and (n-1).is_positive:
|
|
return S.Zero
|
|
elif n.is_Number:
|
|
return 2 * (1-S(2)**n) * bernoulli(n)
|
|
# Genocchi polynomials
|
|
elif n.is_Number:
|
|
return genocchi_poly(n, x)
|
|
|
|
def _eval_rewrite_as_bernoulli(self, n, x=1, **kwargs):
|
|
if x == 1 and n.is_integer and n.is_nonnegative:
|
|
return 2 * (1-S(2)**n) * bernoulli(n)
|
|
return 2 * (bernoulli(n, x) - 2**n * bernoulli(n, (x+1) / 2))
|
|
|
|
def _eval_rewrite_as_dirichlet_eta(self, n, x=1, **kwargs):
|
|
from sympy.functions.special.zeta_functions import dirichlet_eta
|
|
return -2*n * dirichlet_eta(1-n, x)
|
|
|
|
def _eval_is_integer(self):
|
|
if len(self.args) > 1 and self.args[1] != 1:
|
|
return
|
|
n = self.args[0]
|
|
if n.is_integer and n.is_nonnegative:
|
|
return True
|
|
|
|
def _eval_is_negative(self):
|
|
if len(self.args) > 1 and self.args[1] != 1:
|
|
return
|
|
n = self.args[0]
|
|
if n.is_integer and n.is_nonnegative:
|
|
if n.is_odd:
|
|
return fuzzy_not((n-1).is_positive)
|
|
return (n/2).is_odd
|
|
|
|
def _eval_is_positive(self):
|
|
if len(self.args) > 1 and self.args[1] != 1:
|
|
return
|
|
n = self.args[0]
|
|
if n.is_integer and n.is_nonnegative:
|
|
if n.is_zero or n.is_odd:
|
|
return False
|
|
return (n/2).is_even
|
|
|
|
def _eval_is_even(self):
|
|
if len(self.args) > 1 and self.args[1] != 1:
|
|
return
|
|
n = self.args[0]
|
|
if n.is_integer and n.is_nonnegative:
|
|
if n.is_even:
|
|
return n.is_zero
|
|
return (n-1).is_positive
|
|
|
|
def _eval_is_odd(self):
|
|
if len(self.args) > 1 and self.args[1] != 1:
|
|
return
|
|
n = self.args[0]
|
|
if n.is_integer and n.is_nonnegative:
|
|
if n.is_even:
|
|
return fuzzy_not(n.is_zero)
|
|
return fuzzy_not((n-1).is_positive)
|
|
|
|
def _eval_is_prime(self):
|
|
if len(self.args) > 1 and self.args[1] != 1:
|
|
return
|
|
n = self.args[0]
|
|
# only G_6 = -3 and G_8 = 17 are prime,
|
|
# but SymPy does not consider negatives as prime
|
|
# so only n=8 is tested
|
|
return (n-8).is_zero
|
|
|
|
def _eval_evalf(self, prec):
|
|
if all(i.is_number for i in self.args):
|
|
return self.rewrite(bernoulli)._eval_evalf(prec)
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Andre numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
|
|
class andre(Function):
|
|
r"""
|
|
Andre numbers / Andre function
|
|
|
|
The Andre number `\mathcal{A}_n` is Luschny's name for half the number of
|
|
*alternating permutations* on `n` elements, where a permutation is alternating
|
|
if adjacent elements alternately compare "greater" and "smaller" going from
|
|
left to right. For example, `2 < 3 > 1 < 4` is an alternating permutation.
|
|
|
|
This sequence is A000111 in the OEIS, which assigns the names *up/down numbers*
|
|
and *Euler zigzag numbers*. It satisfies a recurrence relation similar to that
|
|
for the Catalan numbers, with `\mathcal{A}_0 = 1` and
|
|
|
|
.. math:: 2 \mathcal{A}_{n+1} = \sum_{k=0}^n \binom{n}{k} \mathcal{A}_k \mathcal{A}_{n-k}
|
|
|
|
The Bernoulli and Euler numbers are signed transformations of the odd- and
|
|
even-indexed elements of this sequence respectively:
|
|
|
|
.. math :: \operatorname{B}_{2k} = \frac{2k \mathcal{A}_{2k-1}}{(-4)^k - (-16)^k}
|
|
|
|
.. math :: \operatorname{E}_{2k} = (-1)^k \mathcal{A}_{2k}
|
|
|
|
Like the Bernoulli and Euler numbers, the Andre numbers are interpolated by the
|
|
entire Andre function:
|
|
|
|
.. math :: \mathcal{A}(s) = (-i)^{s+1} \operatorname{Li}_{-s}(i) +
|
|
i^{s+1} \operatorname{Li}_{-s}(-i) = \\ \frac{2 \Gamma(s+1)}{(2\pi)^{s+1}}
|
|
(\zeta(s+1, 1/4) - \zeta(s+1, 3/4) \cos{\pi s})
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import andre, euler, bernoulli
|
|
>>> [andre(n) for n in range(11)]
|
|
[1, 1, 1, 2, 5, 16, 61, 272, 1385, 7936, 50521]
|
|
>>> [(-1)**k * andre(2*k) for k in range(7)]
|
|
[1, -1, 5, -61, 1385, -50521, 2702765]
|
|
>>> [euler(2*k) for k in range(7)]
|
|
[1, -1, 5, -61, 1385, -50521, 2702765]
|
|
>>> [andre(2*k-1) * (2*k) / ((-4)**k - (-16)**k) for k in range(1, 8)]
|
|
[1/6, -1/30, 1/42, -1/30, 5/66, -691/2730, 7/6]
|
|
>>> [bernoulli(2*k) for k in range(1, 8)]
|
|
[1/6, -1/30, 1/42, -1/30, 5/66, -691/2730, 7/6]
|
|
|
|
See Also
|
|
========
|
|
|
|
bernoulli, catalan, euler, sympy.polys.appellseqs.andre_poly
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Alternating_permutation
|
|
.. [2] https://mathworld.wolfram.com/EulerZigzagNumber.html
|
|
.. [3] Peter Luschny, "An introduction to the Bernoulli function",
|
|
https://arxiv.org/abs/2009.06743
|
|
"""
|
|
|
|
@classmethod
|
|
def eval(cls, n):
|
|
if n is S.NaN:
|
|
return S.NaN
|
|
elif n is S.Infinity:
|
|
return S.Infinity
|
|
if n.is_zero:
|
|
return S.One
|
|
elif n == -1:
|
|
return -log(2)
|
|
elif n == -2:
|
|
return -2*S.Catalan
|
|
elif n.is_Integer:
|
|
if n.is_nonnegative and n.is_even:
|
|
return abs(euler(n))
|
|
elif n.is_odd:
|
|
from sympy.functions.special.zeta_functions import zeta
|
|
m = -n-1
|
|
return I**m * Rational(1-2**m, 4**m) * zeta(-n)
|
|
|
|
def _eval_rewrite_as_zeta(self, s, **kwargs):
|
|
from sympy.functions.elementary.trigonometric import cos
|
|
from sympy.functions.special.gamma_functions import gamma
|
|
from sympy.functions.special.zeta_functions import zeta
|
|
return 2 * gamma(s+1) / (2*pi)**(s+1) * \
|
|
(zeta(s+1, S.One/4) - cos(pi*s) * zeta(s+1, S(3)/4))
|
|
|
|
def _eval_rewrite_as_polylog(self, s, **kwargs):
|
|
from sympy.functions.special.zeta_functions import polylog
|
|
return (-I)**(s+1) * polylog(-s, I) + I**(s+1) * polylog(-s, -I)
|
|
|
|
def _eval_is_integer(self):
|
|
n = self.args[0]
|
|
if n.is_integer and n.is_nonnegative:
|
|
return True
|
|
|
|
def _eval_is_positive(self):
|
|
if self.args[0].is_nonnegative:
|
|
return True
|
|
|
|
def _eval_evalf(self, prec):
|
|
if not self.args[0].is_number:
|
|
return
|
|
s = self.args[0]._to_mpmath(prec+12)
|
|
with workprec(prec+12):
|
|
sp, cp = mp.sinpi(s/2), mp.cospi(s/2)
|
|
res = 2*mp.dirichlet(-s, (-sp, cp, sp, -cp))
|
|
return Expr._from_mpmath(res, prec)
|
|
|
|
|
|
#----------------------------------------------------------------------------#
|
|
# #
|
|
# Partition numbers #
|
|
# #
|
|
#----------------------------------------------------------------------------#
|
|
|
|
_npartition = [1, 1]
|
|
class partition(Function):
|
|
r"""
|
|
Partition numbers
|
|
|
|
The Partition numbers are a sequence of integers `p_n` that represent the
|
|
number of distinct ways of representing `n` as a sum of natural numbers
|
|
(with order irrelevant). The generating function for `p_n` is given by:
|
|
|
|
.. math:: \sum_{n=0}^\infty p_n x^n = \prod_{k=1}^\infty (1 - x^k)^{-1}
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import partition, Symbol
|
|
>>> [partition(n) for n in range(9)]
|
|
[1, 1, 2, 3, 5, 7, 11, 15, 22]
|
|
>>> n = Symbol('n', integer=True, negative=True)
|
|
>>> partition(n)
|
|
0
|
|
|
|
See Also
|
|
========
|
|
|
|
bell, bernoulli, catalan, euler, fibonacci, harmonic, lucas, genocchi, tribonacci
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Partition_(number_theory%29
|
|
.. [2] https://en.wikipedia.org/wiki/Pentagonal_number_theorem
|
|
|
|
"""
|
|
|
|
@staticmethod
|
|
def _partition(n):
|
|
L = len(_npartition)
|
|
if n < L:
|
|
return _npartition[n]
|
|
# lengthen cache
|
|
for _n in range(L, n + 1):
|
|
v, p, i = 0, 0, 0
|
|
while 1:
|
|
s = 0
|
|
p += 3*i + 1 # p = pentagonal number: 1, 5, 12, ...
|
|
if _n >= p:
|
|
s += _npartition[_n - p]
|
|
i += 1
|
|
gp = p + i # gp = generalized pentagonal: 2, 7, 15, ...
|
|
if _n >= gp:
|
|
s += _npartition[_n - gp]
|
|
if s == 0:
|
|
break
|
|
else:
|
|
v += s if i%2 == 1 else -s
|
|
_npartition.append(v)
|
|
return v
|
|
|
|
@classmethod
|
|
def eval(cls, n):
|
|
is_int = n.is_integer
|
|
if is_int == False:
|
|
raise ValueError("Partition numbers are defined only for "
|
|
"integers")
|
|
elif is_int:
|
|
if n.is_negative:
|
|
return S.Zero
|
|
|
|
if n.is_zero or (n - 1).is_zero:
|
|
return S.One
|
|
|
|
if n.is_Integer:
|
|
return Integer(cls._partition(n))
|
|
|
|
|
|
def _eval_is_integer(self):
|
|
if self.args[0].is_integer:
|
|
return True
|
|
|
|
def _eval_is_negative(self):
|
|
if self.args[0].is_integer:
|
|
return False
|
|
|
|
def _eval_is_positive(self):
|
|
n = self.args[0]
|
|
if n.is_nonnegative and n.is_integer:
|
|
return True
|
|
|
|
|
|
#######################################################################
|
|
###
|
|
### Functions for enumerating partitions, permutations and combinations
|
|
###
|
|
#######################################################################
|
|
|
|
|
|
class _MultisetHistogram(tuple):
|
|
pass
|
|
|
|
|
|
_N = -1
|
|
_ITEMS = -2
|
|
_M = slice(None, _ITEMS)
|
|
|
|
|
|
def _multiset_histogram(n):
|
|
"""Return tuple used in permutation and combination counting. Input
|
|
is a dictionary giving items with counts as values or a sequence of
|
|
items (which need not be sorted).
|
|
|
|
The data is stored in a class deriving from tuple so it is easily
|
|
recognized and so it can be converted easily to a list.
|
|
"""
|
|
if isinstance(n, dict): # item: count
|
|
if not all(isinstance(v, int) and v >= 0 for v in n.values()):
|
|
raise ValueError
|
|
tot = sum(n.values())
|
|
items = sum(1 for k in n if n[k] > 0)
|
|
return _MultisetHistogram([n[k] for k in n if n[k] > 0] + [items, tot])
|
|
else:
|
|
n = list(n)
|
|
s = set(n)
|
|
lens = len(s)
|
|
lenn = len(n)
|
|
if lens == lenn:
|
|
n = [1]*lenn + [lenn, lenn]
|
|
return _MultisetHistogram(n)
|
|
m = dict(zip(s, range(lens)))
|
|
d = dict(zip(range(lens), (0,)*lens))
|
|
for i in n:
|
|
d[m[i]] += 1
|
|
return _multiset_histogram(d)
|
|
|
|
|
|
def nP(n, k=None, replacement=False):
|
|
"""Return the number of permutations of ``n`` items taken ``k`` at a time.
|
|
|
|
Possible values for ``n``:
|
|
|
|
integer - set of length ``n``
|
|
|
|
sequence - converted to a multiset internally
|
|
|
|
multiset - {element: multiplicity}
|
|
|
|
If ``k`` is None then the total of all permutations of length 0
|
|
through the number of items represented by ``n`` will be returned.
|
|
|
|
If ``replacement`` is True then a given item can appear more than once
|
|
in the ``k`` items. (For example, for 'ab' permutations of 2 would
|
|
include 'aa', 'ab', 'ba' and 'bb'.) The multiplicity of elements in
|
|
``n`` is ignored when ``replacement`` is True but the total number
|
|
of elements is considered since no element can appear more times than
|
|
the number of elements in ``n``.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.functions.combinatorial.numbers import nP
|
|
>>> from sympy.utilities.iterables import multiset_permutations, multiset
|
|
>>> nP(3, 2)
|
|
6
|
|
>>> nP('abc', 2) == nP(multiset('abc'), 2) == 6
|
|
True
|
|
>>> nP('aab', 2)
|
|
3
|
|
>>> nP([1, 2, 2], 2)
|
|
3
|
|
>>> [nP(3, i) for i in range(4)]
|
|
[1, 3, 6, 6]
|
|
>>> nP(3) == sum(_)
|
|
True
|
|
|
|
When ``replacement`` is True, each item can have multiplicity
|
|
equal to the length represented by ``n``:
|
|
|
|
>>> nP('aabc', replacement=True)
|
|
121
|
|
>>> [len(list(multiset_permutations('aaaabbbbcccc', i))) for i in range(5)]
|
|
[1, 3, 9, 27, 81]
|
|
>>> sum(_)
|
|
121
|
|
|
|
See Also
|
|
========
|
|
sympy.utilities.iterables.multiset_permutations
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Permutation
|
|
|
|
"""
|
|
try:
|
|
n = as_int(n)
|
|
except ValueError:
|
|
return Integer(_nP(_multiset_histogram(n), k, replacement))
|
|
return Integer(_nP(n, k, replacement))
|
|
|
|
|
|
@cacheit
|
|
def _nP(n, k=None, replacement=False):
|
|
|
|
if k == 0:
|
|
return 1
|
|
if isinstance(n, SYMPY_INTS): # n different items
|
|
# assert n >= 0
|
|
if k is None:
|
|
return sum(_nP(n, i, replacement) for i in range(n + 1))
|
|
elif replacement:
|
|
return n**k
|
|
elif k > n:
|
|
return 0
|
|
elif k == n:
|
|
return factorial(k)
|
|
elif k == 1:
|
|
return n
|
|
else:
|
|
# assert k >= 0
|
|
return _product(n - k + 1, n)
|
|
elif isinstance(n, _MultisetHistogram):
|
|
if k is None:
|
|
return sum(_nP(n, i, replacement) for i in range(n[_N] + 1))
|
|
elif replacement:
|
|
return n[_ITEMS]**k
|
|
elif k == n[_N]:
|
|
return factorial(k)/prod([factorial(i) for i in n[_M] if i > 1])
|
|
elif k > n[_N]:
|
|
return 0
|
|
elif k == 1:
|
|
return n[_ITEMS]
|
|
else:
|
|
# assert k >= 0
|
|
tot = 0
|
|
n = list(n)
|
|
for i in range(len(n[_M])):
|
|
if not n[i]:
|
|
continue
|
|
n[_N] -= 1
|
|
if n[i] == 1:
|
|
n[i] = 0
|
|
n[_ITEMS] -= 1
|
|
tot += _nP(_MultisetHistogram(n), k - 1)
|
|
n[_ITEMS] += 1
|
|
n[i] = 1
|
|
else:
|
|
n[i] -= 1
|
|
tot += _nP(_MultisetHistogram(n), k - 1)
|
|
n[i] += 1
|
|
n[_N] += 1
|
|
return tot
|
|
|
|
|
|
@cacheit
|
|
def _AOP_product(n):
|
|
"""for n = (m1, m2, .., mk) return the coefficients of the polynomial,
|
|
prod(sum(x**i for i in range(nj + 1)) for nj in n); i.e. the coefficients
|
|
of the product of AOPs (all-one polynomials) or order given in n. The
|
|
resulting coefficient corresponding to x**r is the number of r-length
|
|
combinations of sum(n) elements with multiplicities given in n.
|
|
The coefficients are given as a default dictionary (so if a query is made
|
|
for a key that is not present, 0 will be returned).
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.functions.combinatorial.numbers import _AOP_product
|
|
>>> from sympy.abc import x
|
|
>>> n = (2, 2, 3) # e.g. aabbccc
|
|
>>> prod = ((x**2 + x + 1)*(x**2 + x + 1)*(x**3 + x**2 + x + 1)).expand()
|
|
>>> c = _AOP_product(n); dict(c)
|
|
{0: 1, 1: 3, 2: 6, 3: 8, 4: 8, 5: 6, 6: 3, 7: 1}
|
|
>>> [c[i] for i in range(8)] == [prod.coeff(x, i) for i in range(8)]
|
|
True
|
|
|
|
The generating poly used here is the same as that listed in
|
|
https://tinyurl.com/cep849r, but in a refactored form.
|
|
|
|
"""
|
|
|
|
n = list(n)
|
|
ord = sum(n)
|
|
need = (ord + 2)//2
|
|
rv = [1]*(n.pop() + 1)
|
|
rv.extend((0,) * (need - len(rv)))
|
|
rv = rv[:need]
|
|
while n:
|
|
ni = n.pop()
|
|
N = ni + 1
|
|
was = rv[:]
|
|
for i in range(1, min(N, len(rv))):
|
|
rv[i] += rv[i - 1]
|
|
for i in range(N, need):
|
|
rv[i] += rv[i - 1] - was[i - N]
|
|
rev = list(reversed(rv))
|
|
if ord % 2:
|
|
rv = rv + rev
|
|
else:
|
|
rv[-1:] = rev
|
|
d = defaultdict(int)
|
|
for i, r in enumerate(rv):
|
|
d[i] = r
|
|
return d
|
|
|
|
|
|
def nC(n, k=None, replacement=False):
|
|
"""Return the number of combinations of ``n`` items taken ``k`` at a time.
|
|
|
|
Possible values for ``n``:
|
|
|
|
integer - set of length ``n``
|
|
|
|
sequence - converted to a multiset internally
|
|
|
|
multiset - {element: multiplicity}
|
|
|
|
If ``k`` is None then the total of all combinations of length 0
|
|
through the number of items represented in ``n`` will be returned.
|
|
|
|
If ``replacement`` is True then a given item can appear more than once
|
|
in the ``k`` items. (For example, for 'ab' sets of 2 would include 'aa',
|
|
'ab', and 'bb'.) The multiplicity of elements in ``n`` is ignored when
|
|
``replacement`` is True but the total number of elements is considered
|
|
since no element can appear more times than the number of elements in
|
|
``n``.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.functions.combinatorial.numbers import nC
|
|
>>> from sympy.utilities.iterables import multiset_combinations
|
|
>>> nC(3, 2)
|
|
3
|
|
>>> nC('abc', 2)
|
|
3
|
|
>>> nC('aab', 2)
|
|
2
|
|
|
|
When ``replacement`` is True, each item can have multiplicity
|
|
equal to the length represented by ``n``:
|
|
|
|
>>> nC('aabc', replacement=True)
|
|
35
|
|
>>> [len(list(multiset_combinations('aaaabbbbcccc', i))) for i in range(5)]
|
|
[1, 3, 6, 10, 15]
|
|
>>> sum(_)
|
|
35
|
|
|
|
If there are ``k`` items with multiplicities ``m_1, m_2, ..., m_k``
|
|
then the total of all combinations of length 0 through ``k`` is the
|
|
product, ``(m_1 + 1)*(m_2 + 1)*...*(m_k + 1)``. When the multiplicity
|
|
of each item is 1 (i.e., k unique items) then there are 2**k
|
|
combinations. For example, if there are 4 unique items, the total number
|
|
of combinations is 16:
|
|
|
|
>>> sum(nC(4, i) for i in range(5))
|
|
16
|
|
|
|
See Also
|
|
========
|
|
|
|
sympy.utilities.iterables.multiset_combinations
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Combination
|
|
.. [2] https://tinyurl.com/cep849r
|
|
|
|
"""
|
|
|
|
if isinstance(n, SYMPY_INTS):
|
|
if k is None:
|
|
if not replacement:
|
|
return 2**n
|
|
return sum(nC(n, i, replacement) for i in range(n + 1))
|
|
if k < 0:
|
|
raise ValueError("k cannot be negative")
|
|
if replacement:
|
|
return binomial(n + k - 1, k)
|
|
return binomial(n, k)
|
|
if isinstance(n, _MultisetHistogram):
|
|
N = n[_N]
|
|
if k is None:
|
|
if not replacement:
|
|
return prod(m + 1 for m in n[_M])
|
|
return sum(nC(n, i, replacement) for i in range(N + 1))
|
|
elif replacement:
|
|
return nC(n[_ITEMS], k, replacement)
|
|
# assert k >= 0
|
|
elif k in (1, N - 1):
|
|
return n[_ITEMS]
|
|
elif k in (0, N):
|
|
return 1
|
|
return _AOP_product(tuple(n[_M]))[k]
|
|
else:
|
|
return nC(_multiset_histogram(n), k, replacement)
|
|
|
|
|
|
def _eval_stirling1(n, k):
|
|
if n == k == 0:
|
|
return S.One
|
|
if 0 in (n, k):
|
|
return S.Zero
|
|
|
|
# some special values
|
|
if n == k:
|
|
return S.One
|
|
elif k == n - 1:
|
|
return binomial(n, 2)
|
|
elif k == n - 2:
|
|
return (3*n - 1)*binomial(n, 3)/4
|
|
elif k == n - 3:
|
|
return binomial(n, 2)*binomial(n, 4)
|
|
|
|
return _stirling1(n, k)
|
|
|
|
|
|
@cacheit
|
|
def _stirling1(n, k):
|
|
row = [0, 1]+[0]*(k-1) # for n = 1
|
|
for i in range(2, n+1):
|
|
for j in range(min(k,i), 0, -1):
|
|
row[j] = (i-1) * row[j] + row[j-1]
|
|
return Integer(row[k])
|
|
|
|
|
|
def _eval_stirling2(n, k):
|
|
if n == k == 0:
|
|
return S.One
|
|
if 0 in (n, k):
|
|
return S.Zero
|
|
|
|
# some special values
|
|
if n == k:
|
|
return S.One
|
|
elif k == n - 1:
|
|
return binomial(n, 2)
|
|
elif k == 1:
|
|
return S.One
|
|
elif k == 2:
|
|
return Integer(2**(n - 1) - 1)
|
|
|
|
return _stirling2(n, k)
|
|
|
|
|
|
@cacheit
|
|
def _stirling2(n, k):
|
|
row = [0, 1]+[0]*(k-1) # for n = 1
|
|
for i in range(2, n+1):
|
|
for j in range(min(k,i), 0, -1):
|
|
row[j] = j * row[j] + row[j-1]
|
|
return Integer(row[k])
|
|
|
|
|
|
def stirling(n, k, d=None, kind=2, signed=False):
|
|
r"""Return Stirling number $S(n, k)$ of the first or second (default) kind.
|
|
|
|
The sum of all Stirling numbers of the second kind for $k = 1$
|
|
through $n$ is ``bell(n)``. The recurrence relationship for these numbers
|
|
is:
|
|
|
|
.. math :: {0 \brace 0} = 1; {n \brace 0} = {0 \brace k} = 0;
|
|
|
|
.. math :: {{n+1} \brace k} = j {n \brace k} + {n \brace {k-1}}
|
|
|
|
where $j$ is:
|
|
$n$ for Stirling numbers of the first kind,
|
|
$-n$ for signed Stirling numbers of the first kind,
|
|
$k$ for Stirling numbers of the second kind.
|
|
|
|
The first kind of Stirling number counts the number of permutations of
|
|
``n`` distinct items that have ``k`` cycles; the second kind counts the
|
|
ways in which ``n`` distinct items can be partitioned into ``k`` parts.
|
|
If ``d`` is given, the "reduced Stirling number of the second kind" is
|
|
returned: $S^{d}(n, k) = S(n - d + 1, k - d + 1)$ with $n \ge k \ge d$.
|
|
(This counts the ways to partition $n$ consecutive integers into $k$
|
|
groups with no pairwise difference less than $d$. See example below.)
|
|
|
|
To obtain the signed Stirling numbers of the first kind, use keyword
|
|
``signed=True``. Using this keyword automatically sets ``kind`` to 1.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.functions.combinatorial.numbers import stirling, bell
|
|
>>> from sympy.combinatorics import Permutation
|
|
>>> from sympy.utilities.iterables import multiset_partitions, permutations
|
|
|
|
First kind (unsigned by default):
|
|
|
|
>>> [stirling(6, i, kind=1) for i in range(7)]
|
|
[0, 120, 274, 225, 85, 15, 1]
|
|
>>> perms = list(permutations(range(4)))
|
|
>>> [sum(Permutation(p).cycles == i for p in perms) for i in range(5)]
|
|
[0, 6, 11, 6, 1]
|
|
>>> [stirling(4, i, kind=1) for i in range(5)]
|
|
[0, 6, 11, 6, 1]
|
|
|
|
First kind (signed):
|
|
|
|
>>> [stirling(4, i, signed=True) for i in range(5)]
|
|
[0, -6, 11, -6, 1]
|
|
|
|
Second kind:
|
|
|
|
>>> [stirling(10, i) for i in range(12)]
|
|
[0, 1, 511, 9330, 34105, 42525, 22827, 5880, 750, 45, 1, 0]
|
|
>>> sum(_) == bell(10)
|
|
True
|
|
>>> len(list(multiset_partitions(range(4), 2))) == stirling(4, 2)
|
|
True
|
|
|
|
Reduced second kind:
|
|
|
|
>>> from sympy import subsets, oo
|
|
>>> def delta(p):
|
|
... if len(p) == 1:
|
|
... return oo
|
|
... return min(abs(i[0] - i[1]) for i in subsets(p, 2))
|
|
>>> parts = multiset_partitions(range(5), 3)
|
|
>>> d = 2
|
|
>>> sum(1 for p in parts if all(delta(i) >= d for i in p))
|
|
7
|
|
>>> stirling(5, 3, 2)
|
|
7
|
|
|
|
See Also
|
|
========
|
|
sympy.utilities.iterables.multiset_partitions
|
|
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Stirling_numbers_of_the_first_kind
|
|
.. [2] https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind
|
|
|
|
"""
|
|
# TODO: make this a class like bell()
|
|
|
|
n = as_int(n)
|
|
k = as_int(k)
|
|
if n < 0:
|
|
raise ValueError('n must be nonnegative')
|
|
if k > n:
|
|
return S.Zero
|
|
if d:
|
|
# assert k >= d
|
|
# kind is ignored -- only kind=2 is supported
|
|
return _eval_stirling2(n - d + 1, k - d + 1)
|
|
elif signed:
|
|
# kind is ignored -- only kind=1 is supported
|
|
return S.NegativeOne**(n - k)*_eval_stirling1(n, k)
|
|
|
|
if kind == 1:
|
|
return _eval_stirling1(n, k)
|
|
elif kind == 2:
|
|
return _eval_stirling2(n, k)
|
|
else:
|
|
raise ValueError('kind must be 1 or 2, not %s' % k)
|
|
|
|
|
|
@cacheit
|
|
def _nT(n, k):
|
|
"""Return the partitions of ``n`` items into ``k`` parts. This
|
|
is used by ``nT`` for the case when ``n`` is an integer."""
|
|
# really quick exits
|
|
if k > n or k < 0:
|
|
return 0
|
|
if k in (1, n):
|
|
return 1
|
|
if k == 0:
|
|
return 0
|
|
# exits that could be done below but this is quicker
|
|
if k == 2:
|
|
return n//2
|
|
d = n - k
|
|
if d <= 3:
|
|
return d
|
|
# quick exit
|
|
if 3*k >= n: # or, equivalently, 2*k >= d
|
|
# all the information needed in this case
|
|
# will be in the cache needed to calculate
|
|
# partition(d), so...
|
|
# update cache
|
|
tot = partition._partition(d)
|
|
# and correct for values not needed
|
|
if d - k > 0:
|
|
tot -= sum(_npartition[:d - k])
|
|
return tot
|
|
# regular exit
|
|
# nT(n, k) = Sum(nT(n - k, m), (m, 1, k));
|
|
# calculate needed nT(i, j) values
|
|
p = [1]*d
|
|
for i in range(2, k + 1):
|
|
for m in range(i + 1, d):
|
|
p[m] += p[m - i]
|
|
d -= 1
|
|
# if p[0] were appended to the end of p then the last
|
|
# k values of p are the nT(n, j) values for 0 < j < k in reverse
|
|
# order p[-1] = nT(n, 1), p[-2] = nT(n, 2), etc.... Instead of
|
|
# putting the 1 from p[0] there, however, it is simply added to
|
|
# the sum below which is valid for 1 < k <= n//2
|
|
return (1 + sum(p[1 - k:]))
|
|
|
|
|
|
def nT(n, k=None):
|
|
"""Return the number of ``k``-sized partitions of ``n`` items.
|
|
|
|
Possible values for ``n``:
|
|
|
|
integer - ``n`` identical items
|
|
|
|
sequence - converted to a multiset internally
|
|
|
|
multiset - {element: multiplicity}
|
|
|
|
Note: the convention for ``nT`` is different than that of ``nC`` and
|
|
``nP`` in that
|
|
here an integer indicates ``n`` *identical* items instead of a set of
|
|
length ``n``; this is in keeping with the ``partitions`` function which
|
|
treats its integer-``n`` input like a list of ``n`` 1s. One can use
|
|
``range(n)`` for ``n`` to indicate ``n`` distinct items.
|
|
|
|
If ``k`` is None then the total number of ways to partition the elements
|
|
represented in ``n`` will be returned.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.functions.combinatorial.numbers import nT
|
|
|
|
Partitions of the given multiset:
|
|
|
|
>>> [nT('aabbc', i) for i in range(1, 7)]
|
|
[1, 8, 11, 5, 1, 0]
|
|
>>> nT('aabbc') == sum(_)
|
|
True
|
|
|
|
>>> [nT("mississippi", i) for i in range(1, 12)]
|
|
[1, 74, 609, 1521, 1768, 1224, 579, 197, 50, 9, 1]
|
|
|
|
Partitions when all items are identical:
|
|
|
|
>>> [nT(5, i) for i in range(1, 6)]
|
|
[1, 2, 2, 1, 1]
|
|
>>> nT('1'*5) == sum(_)
|
|
True
|
|
|
|
When all items are different:
|
|
|
|
>>> [nT(range(5), i) for i in range(1, 6)]
|
|
[1, 15, 25, 10, 1]
|
|
>>> nT(range(5)) == sum(_)
|
|
True
|
|
|
|
Partitions of an integer expressed as a sum of positive integers:
|
|
|
|
>>> from sympy import partition
|
|
>>> partition(4)
|
|
5
|
|
>>> nT(4, 1) + nT(4, 2) + nT(4, 3) + nT(4, 4)
|
|
5
|
|
>>> nT('1'*4)
|
|
5
|
|
|
|
See Also
|
|
========
|
|
sympy.utilities.iterables.partitions
|
|
sympy.utilities.iterables.multiset_partitions
|
|
sympy.functions.combinatorial.numbers.partition
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://web.archive.org/web/20210507012732/https://teaching.csse.uwa.edu.au/units/CITS7209/partition.pdf
|
|
|
|
"""
|
|
|
|
if isinstance(n, SYMPY_INTS):
|
|
# n identical items
|
|
if k is None:
|
|
return partition(n)
|
|
if isinstance(k, SYMPY_INTS):
|
|
n = as_int(n)
|
|
k = as_int(k)
|
|
return Integer(_nT(n, k))
|
|
if not isinstance(n, _MultisetHistogram):
|
|
try:
|
|
# if n contains hashable items there is some
|
|
# quick handling that can be done
|
|
u = len(set(n))
|
|
if u <= 1:
|
|
return nT(len(n), k)
|
|
elif u == len(n):
|
|
n = range(u)
|
|
raise TypeError
|
|
except TypeError:
|
|
n = _multiset_histogram(n)
|
|
N = n[_N]
|
|
if k is None and N == 1:
|
|
return 1
|
|
if k in (1, N):
|
|
return 1
|
|
if k == 2 or N == 2 and k is None:
|
|
m, r = divmod(N, 2)
|
|
rv = sum(nC(n, i) for i in range(1, m + 1))
|
|
if not r:
|
|
rv -= nC(n, m)//2
|
|
if k is None:
|
|
rv += 1 # for k == 1
|
|
return rv
|
|
if N == n[_ITEMS]:
|
|
# all distinct
|
|
if k is None:
|
|
return bell(N)
|
|
return stirling(N, k)
|
|
m = MultisetPartitionTraverser()
|
|
if k is None:
|
|
return m.count_partitions(n[_M])
|
|
# MultisetPartitionTraverser does not have a range-limited count
|
|
# method, so need to enumerate and count
|
|
tot = 0
|
|
for discard in m.enum_range(n[_M], k-1, k):
|
|
tot += 1
|
|
return tot
|
|
|
|
|
|
#-----------------------------------------------------------------------------#
|
|
# #
|
|
# Motzkin numbers #
|
|
# #
|
|
#-----------------------------------------------------------------------------#
|
|
|
|
|
|
class motzkin(Function):
|
|
"""
|
|
The nth Motzkin number is the number
|
|
of ways of drawing non-intersecting chords
|
|
between n points on a circle (not necessarily touching
|
|
every point by a chord). The Motzkin numbers are named
|
|
after Theodore Motzkin and have diverse applications
|
|
in geometry, combinatorics and number theory.
|
|
|
|
Motzkin numbers are the integer sequence defined by the
|
|
initial terms `M_0 = 1`, `M_1 = 1` and the two-term recurrence relation
|
|
`M_n = \frac{2*n + 1}{n + 2} * M_{n-1} + \frac{3n - 3}{n + 2} * M_{n-2}`.
|
|
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import motzkin
|
|
|
|
>>> motzkin.is_motzkin(5)
|
|
False
|
|
>>> motzkin.find_motzkin_numbers_in_range(2,300)
|
|
[2, 4, 9, 21, 51, 127]
|
|
>>> motzkin.find_motzkin_numbers_in_range(2,900)
|
|
[2, 4, 9, 21, 51, 127, 323, 835]
|
|
>>> motzkin.find_first_n_motzkins(10)
|
|
[1, 1, 2, 4, 9, 21, 51, 127, 323, 835]
|
|
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Motzkin_number
|
|
.. [2] https://mathworld.wolfram.com/MotzkinNumber.html
|
|
|
|
"""
|
|
|
|
@staticmethod
|
|
def is_motzkin(n):
|
|
try:
|
|
n = as_int(n)
|
|
except ValueError:
|
|
return False
|
|
if n > 0:
|
|
if n in (1, 2):
|
|
return True
|
|
|
|
tn1 = 1
|
|
tn = 2
|
|
i = 3
|
|
while tn < n:
|
|
a = ((2*i + 1)*tn + (3*i - 3)*tn1)/(i + 2)
|
|
i += 1
|
|
tn1 = tn
|
|
tn = a
|
|
|
|
if tn == n:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
else:
|
|
return False
|
|
|
|
@staticmethod
|
|
def find_motzkin_numbers_in_range(x, y):
|
|
if 0 <= x <= y:
|
|
motzkins = []
|
|
if x <= 1 <= y:
|
|
motzkins.append(1)
|
|
tn1 = 1
|
|
tn = 2
|
|
i = 3
|
|
while tn <= y:
|
|
if tn >= x:
|
|
motzkins.append(tn)
|
|
a = ((2*i + 1)*tn + (3*i - 3)*tn1)/(i + 2)
|
|
i += 1
|
|
tn1 = tn
|
|
tn = int(a)
|
|
|
|
return motzkins
|
|
|
|
else:
|
|
raise ValueError('The provided range is not valid. This condition should satisfy x <= y')
|
|
|
|
@staticmethod
|
|
def find_first_n_motzkins(n):
|
|
try:
|
|
n = as_int(n)
|
|
except ValueError:
|
|
raise ValueError('The provided number must be a positive integer')
|
|
if n < 0:
|
|
raise ValueError('The provided number must be a positive integer')
|
|
motzkins = [1]
|
|
if n >= 1:
|
|
motzkins.append(1)
|
|
tn1 = 1
|
|
tn = 2
|
|
i = 3
|
|
while i <= n:
|
|
motzkins.append(tn)
|
|
a = ((2*i + 1)*tn + (3*i - 3)*tn1)/(i + 2)
|
|
i += 1
|
|
tn1 = tn
|
|
tn = int(a)
|
|
|
|
return motzkins
|
|
|
|
@staticmethod
|
|
@recurrence_memo([S.One, S.One])
|
|
def _motzkin(n, prev):
|
|
return ((2*n + 1)*prev[-1] + (3*n - 3)*prev[-2]) // (n + 2)
|
|
|
|
@classmethod
|
|
def eval(cls, n):
|
|
try:
|
|
n = as_int(n)
|
|
except ValueError:
|
|
raise ValueError('The provided number must be a positive integer')
|
|
if n < 0:
|
|
raise ValueError('The provided number must be a positive integer')
|
|
return Integer(cls._motzkin(n - 1))
|
|
|
|
|
|
def nD(i=None, brute=None, *, n=None, m=None):
|
|
"""return the number of derangements for: ``n`` unique items, ``i``
|
|
items (as a sequence or multiset), or multiplicities, ``m`` given
|
|
as a sequence or multiset.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.utilities.iterables import generate_derangements as enum
|
|
>>> from sympy.functions.combinatorial.numbers import nD
|
|
|
|
A derangement ``d`` of sequence ``s`` has all ``d[i] != s[i]``:
|
|
|
|
>>> set([''.join(i) for i in enum('abc')])
|
|
{'bca', 'cab'}
|
|
>>> nD('abc')
|
|
2
|
|
|
|
Input as iterable or dictionary (multiset form) is accepted:
|
|
|
|
>>> assert nD([1, 2, 2, 3, 3, 3]) == nD({1: 1, 2: 2, 3: 3})
|
|
|
|
By default, a brute-force enumeration and count of multiset permutations
|
|
is only done if there are fewer than 9 elements. There may be cases when
|
|
there is high multiplicity with few unique elements that will benefit
|
|
from a brute-force enumeration, too. For this reason, the `brute`
|
|
keyword (default None) is provided. When False, the brute-force
|
|
enumeration will never be used. When True, it will always be used.
|
|
|
|
>>> nD('1111222233', brute=True)
|
|
44
|
|
|
|
For convenience, one may specify ``n`` distinct items using the
|
|
``n`` keyword:
|
|
|
|
>>> assert nD(n=3) == nD('abc') == 2
|
|
|
|
Since the number of derangments depends on the multiplicity of the
|
|
elements and not the elements themselves, it may be more convenient
|
|
to give a list or multiset of multiplicities using keyword ``m``:
|
|
|
|
>>> assert nD('abc') == nD(m=(1,1,1)) == nD(m={1:3}) == 2
|
|
|
|
"""
|
|
from sympy.integrals.integrals import integrate
|
|
from sympy.functions.special.polynomials import laguerre
|
|
from sympy.abc import x
|
|
def ok(x):
|
|
if not isinstance(x, SYMPY_INTS):
|
|
raise TypeError('expecting integer values')
|
|
if x < 0:
|
|
raise ValueError('value must not be negative')
|
|
return True
|
|
|
|
if (i, n, m).count(None) != 2:
|
|
raise ValueError('enter only 1 of i, n, or m')
|
|
if i is not None:
|
|
if isinstance(i, SYMPY_INTS):
|
|
raise TypeError('items must be a list or dictionary')
|
|
if not i:
|
|
return S.Zero
|
|
if type(i) is not dict:
|
|
s = list(i)
|
|
ms = multiset(s)
|
|
elif type(i) is dict:
|
|
all(ok(_) for _ in i.values())
|
|
ms = {k: v for k, v in i.items() if v}
|
|
s = None
|
|
if not ms:
|
|
return S.Zero
|
|
N = sum(ms.values())
|
|
counts = multiset(ms.values())
|
|
nkey = len(ms)
|
|
elif n is not None:
|
|
ok(n)
|
|
if not n:
|
|
return S.Zero
|
|
return subfactorial(n)
|
|
elif m is not None:
|
|
if isinstance(m, dict):
|
|
all(ok(i) and ok(j) for i, j in m.items())
|
|
counts = {k: v for k, v in m.items() if k*v}
|
|
elif iterable(m) or isinstance(m, str):
|
|
m = list(m)
|
|
all(ok(i) for i in m)
|
|
counts = multiset([i for i in m if i])
|
|
else:
|
|
raise TypeError('expecting iterable')
|
|
if not counts:
|
|
return S.Zero
|
|
N = sum(k*v for k, v in counts.items())
|
|
nkey = sum(counts.values())
|
|
s = None
|
|
big = int(max(counts))
|
|
if big == 1: # no repetition
|
|
return subfactorial(nkey)
|
|
nval = len(counts)
|
|
if big*2 > N:
|
|
return S.Zero
|
|
if big*2 == N:
|
|
if nkey == 2 and nval == 1:
|
|
return S.One # aaabbb
|
|
if nkey - 1 == big: # one element repeated
|
|
return factorial(big) # e.g. abc part of abcddd
|
|
if N < 9 and brute is None or brute:
|
|
# for all possibilities, this was found to be faster
|
|
if s is None:
|
|
s = []
|
|
i = 0
|
|
for m, v in counts.items():
|
|
for j in range(v):
|
|
s.extend([i]*m)
|
|
i += 1
|
|
return Integer(sum(1 for i in multiset_derangements(s)))
|
|
from sympy.functions.elementary.exponential import exp
|
|
return Integer(abs(integrate(exp(-x)*Mul(*[
|
|
laguerre(i, x)**m for i, m in counts.items()]), (x, 0, oo))))
|