ai-content-maker/.venv/Lib/site-packages/sympy/functions/combinatorial/factorials.py

1103 lines
37 KiB
Python
Raw Normal View History

2024-05-03 04:18:51 +03:00
from __future__ import annotations
from functools import reduce
from sympy.core import S, sympify, Dummy, Mod
from sympy.core.cache import cacheit
from sympy.core.function import Function, ArgumentIndexError, PoleError
from sympy.core.logic import fuzzy_and
from sympy.core.numbers import Integer, pi, I
from sympy.core.relational import Eq
from sympy.external.gmpy import HAS_GMPY, gmpy
from sympy.ntheory import sieve
from sympy.polys.polytools import Poly
from math import factorial as _factorial, prod, sqrt as _sqrt
class CombinatorialFunction(Function):
"""Base class for combinatorial functions. """
def _eval_simplify(self, **kwargs):
from sympy.simplify.combsimp import combsimp
# combinatorial function with non-integer arguments is
# automatically passed to gammasimp
expr = combsimp(self)
measure = kwargs['measure']
if measure(expr) <= kwargs['ratio']*measure(self):
return expr
return self
###############################################################################
######################## FACTORIAL and MULTI-FACTORIAL ########################
###############################################################################
class factorial(CombinatorialFunction):
r"""Implementation of factorial function over nonnegative integers.
By convention (consistent with the gamma function and the binomial
coefficients), factorial of a negative integer is complex infinity.
The factorial is very important in combinatorics where it gives
the number of ways in which `n` objects can be permuted. It also
arises in calculus, probability, number theory, etc.
There is strict relation of factorial with gamma function. In
fact `n! = gamma(n+1)` for nonnegative integers. Rewrite of this
kind is very useful in case of combinatorial simplification.
Computation of the factorial is done using two algorithms. For
small arguments a precomputed look up table is used. However for bigger
input algorithm Prime-Swing is used. It is the fastest algorithm
known and computes `n!` via prime factorization of special class
of numbers, called here the 'Swing Numbers'.
Examples
========
>>> from sympy import Symbol, factorial, S
>>> n = Symbol('n', integer=True)
>>> factorial(0)
1
>>> factorial(7)
5040
>>> factorial(-2)
zoo
>>> factorial(n)
factorial(n)
>>> factorial(2*n)
factorial(2*n)
>>> factorial(S(1)/2)
factorial(1/2)
See Also
========
factorial2, RisingFactorial, FallingFactorial
"""
def fdiff(self, argindex=1):
from sympy.functions.special.gamma_functions import (gamma, polygamma)
if argindex == 1:
return gamma(self.args[0] + 1)*polygamma(0, self.args[0] + 1)
else:
raise ArgumentIndexError(self, argindex)
_small_swing = [
1, 1, 1, 3, 3, 15, 5, 35, 35, 315, 63, 693, 231, 3003, 429, 6435, 6435, 109395,
12155, 230945, 46189, 969969, 88179, 2028117, 676039, 16900975, 1300075,
35102025, 5014575, 145422675, 9694845, 300540195, 300540195
]
_small_factorials: list[int] = []
@classmethod
def _swing(cls, n):
if n < 33:
return cls._small_swing[n]
else:
N, primes = int(_sqrt(n)), []
for prime in sieve.primerange(3, N + 1):
p, q = 1, n
while True:
q //= prime
if q > 0:
if q & 1 == 1:
p *= prime
else:
break
if p > 1:
primes.append(p)
for prime in sieve.primerange(N + 1, n//3 + 1):
if (n // prime) & 1 == 1:
primes.append(prime)
L_product = prod(sieve.primerange(n//2 + 1, n + 1))
R_product = prod(primes)
return L_product*R_product
@classmethod
def _recursive(cls, n):
if n < 2:
return 1
else:
return (cls._recursive(n//2)**2)*cls._swing(n)
@classmethod
def eval(cls, n):
n = sympify(n)
if n.is_Number:
if n.is_zero:
return S.One
elif n is S.Infinity:
return S.Infinity
elif n.is_Integer:
if n.is_negative:
return S.ComplexInfinity
else:
n = n.p
if n < 20:
if not cls._small_factorials:
result = 1
for i in range(1, 20):
result *= i
cls._small_factorials.append(result)
result = cls._small_factorials[n-1]
# GMPY factorial is faster, use it when available
elif HAS_GMPY:
result = gmpy.fac(n)
else:
bits = bin(n).count('1')
result = cls._recursive(n)*2**(n - bits)
return Integer(result)
def _facmod(self, n, q):
res, N = 1, int(_sqrt(n))
# Exponent of prime p in n! is e_p(n) = [n/p] + [n/p**2] + ...
# for p > sqrt(n), e_p(n) < sqrt(n), the primes with [n/p] = m,
# occur consecutively and are grouped together in pw[m] for
# simultaneous exponentiation at a later stage
pw = [1]*N
m = 2 # to initialize the if condition below
for prime in sieve.primerange(2, n + 1):
if m > 1:
m, y = 0, n // prime
while y:
m += y
y //= prime
if m < N:
pw[m] = pw[m]*prime % q
else:
res = res*pow(prime, m, q) % q
for ex, bs in enumerate(pw):
if ex == 0 or bs == 1:
continue
if bs == 0:
return 0
res = res*pow(bs, ex, q) % q
return res
def _eval_Mod(self, q):
n = self.args[0]
if n.is_integer and n.is_nonnegative and q.is_integer:
aq = abs(q)
d = aq - n
if d.is_nonpositive:
return S.Zero
else:
isprime = aq.is_prime
if d == 1:
# Apply Wilson's theorem (if a natural number n > 1
# is a prime number, then (n-1)! = -1 mod n) and
# its inverse (if n > 4 is a composite number, then
# (n-1)! = 0 mod n)
if isprime:
return -1 % q
elif isprime is False and (aq - 6).is_nonnegative:
return S.Zero
elif n.is_Integer and q.is_Integer:
n, d, aq = map(int, (n, d, aq))
if isprime and (d - 1 < n):
fc = self._facmod(d - 1, aq)
fc = pow(fc, aq - 2, aq)
if d%2:
fc = -fc
else:
fc = self._facmod(n, aq)
return fc % q
def _eval_rewrite_as_gamma(self, n, piecewise=True, **kwargs):
from sympy.functions.special.gamma_functions import gamma
return gamma(n + 1)
def _eval_rewrite_as_Product(self, n, **kwargs):
from sympy.concrete.products import Product
if n.is_nonnegative and n.is_integer:
i = Dummy('i', integer=True)
return Product(i, (i, 1, 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_integer and self.args[0].is_nonnegative:
return True
def _eval_is_even(self):
x = self.args[0]
if x.is_integer and x.is_nonnegative:
return (x - 2).is_nonnegative
def _eval_is_composite(self):
x = self.args[0]
if x.is_integer and x.is_nonnegative:
return (x - 3).is_nonnegative
def _eval_is_real(self):
x = self.args[0]
if x.is_nonnegative or x.is_noninteger:
return True
def _eval_as_leading_term(self, x, logx=None, cdir=0):
arg = self.args[0].as_leading_term(x)
arg0 = arg.subs(x, 0)
if arg0.is_zero:
return S.One
elif not arg0.is_infinite:
return self.func(arg)
raise PoleError("Cannot expand %s around 0" % (self))
class MultiFactorial(CombinatorialFunction):
pass
class subfactorial(CombinatorialFunction):
r"""The subfactorial counts the derangements of $n$ items and is
defined for non-negative integers as:
.. math:: !n = \begin{cases} 1 & n = 0 \\ 0 & n = 1 \\
(n-1)(!(n-1) + !(n-2)) & n > 1 \end{cases}
It can also be written as ``int(round(n!/exp(1)))`` but the
recursive definition with caching is implemented for this function.
An interesting analytic expression is the following [2]_
.. math:: !x = \Gamma(x + 1, -1)/e
which is valid for non-negative integers `x`. The above formula
is not very useful in case of non-integers. `\Gamma(x + 1, -1)` is
single-valued only for integral arguments `x`, elsewhere on the positive
real axis it has an infinite number of branches none of which are real.
References
==========
.. [1] https://en.wikipedia.org/wiki/Subfactorial
.. [2] https://mathworld.wolfram.com/Subfactorial.html
Examples
========
>>> from sympy import subfactorial
>>> from sympy.abc import n
>>> subfactorial(n + 1)
subfactorial(n + 1)
>>> subfactorial(5)
44
See Also
========
factorial, uppergamma,
sympy.utilities.iterables.generate_derangements
"""
@classmethod
@cacheit
def _eval(self, n):
if not n:
return S.One
elif n == 1:
return S.Zero
else:
z1, z2 = 1, 0
for i in range(2, n + 1):
z1, z2 = z2, (i - 1)*(z2 + z1)
return z2
@classmethod
def eval(cls, arg):
if arg.is_Number:
if arg.is_Integer and arg.is_nonnegative:
return cls._eval(arg)
elif arg is S.NaN:
return S.NaN
elif arg is S.Infinity:
return S.Infinity
def _eval_is_even(self):
if self.args[0].is_odd and self.args[0].is_nonnegative:
return True
def _eval_is_integer(self):
if self.args[0].is_integer and self.args[0].is_nonnegative:
return True
def _eval_rewrite_as_factorial(self, arg, **kwargs):
from sympy.concrete.summations import summation
i = Dummy('i')
f = S.NegativeOne**i / factorial(i)
return factorial(arg) * summation(f, (i, 0, arg))
def _eval_rewrite_as_gamma(self, arg, piecewise=True, **kwargs):
from sympy.functions.elementary.exponential import exp
from sympy.functions.special.gamma_functions import (gamma, lowergamma)
return (S.NegativeOne**(arg + 1)*exp(-I*pi*arg)*lowergamma(arg + 1, -1)
+ gamma(arg + 1))*exp(-1)
def _eval_rewrite_as_uppergamma(self, arg, **kwargs):
from sympy.functions.special.gamma_functions import uppergamma
return uppergamma(arg + 1, -1)/S.Exp1
def _eval_is_nonnegative(self):
if self.args[0].is_integer and self.args[0].is_nonnegative:
return True
def _eval_is_odd(self):
if self.args[0].is_even and self.args[0].is_nonnegative:
return True
class factorial2(CombinatorialFunction):
r"""The double factorial `n!!`, not to be confused with `(n!)!`
The double factorial is defined for nonnegative integers and for odd
negative integers as:
.. math:: n!! = \begin{cases} 1 & n = 0 \\
n(n-2)(n-4) \cdots 1 & n\ \text{positive odd} \\
n(n-2)(n-4) \cdots 2 & n\ \text{positive even} \\
(n+2)!!/(n+2) & n\ \text{negative odd} \end{cases}
References
==========
.. [1] https://en.wikipedia.org/wiki/Double_factorial
Examples
========
>>> from sympy import factorial2, var
>>> n = var('n')
>>> n
n
>>> factorial2(n + 1)
factorial2(n + 1)
>>> factorial2(5)
15
>>> factorial2(-1)
1
>>> factorial2(-5)
1/3
See Also
========
factorial, RisingFactorial, FallingFactorial
"""
@classmethod
def eval(cls, arg):
# TODO: extend this to complex numbers?
if arg.is_Number:
if not arg.is_Integer:
raise ValueError("argument must be nonnegative integer "
"or negative odd integer")
# This implementation is faster than the recursive one
# It also avoids "maximum recursion depth exceeded" runtime error
if arg.is_nonnegative:
if arg.is_even:
k = arg / 2
return 2**k * factorial(k)
return factorial(arg) / factorial2(arg - 1)
if arg.is_odd:
return arg*(S.NegativeOne)**((1 - arg)/2) / factorial2(-arg)
raise ValueError("argument must be nonnegative integer "
"or negative odd integer")
def _eval_is_even(self):
# Double factorial is even for every positive even input
n = self.args[0]
if n.is_integer:
if n.is_odd:
return False
if n.is_even:
if n.is_positive:
return True
if n.is_zero:
return False
def _eval_is_integer(self):
# Double factorial is an integer for every nonnegative input, and for
# -1 and -3
n = self.args[0]
if n.is_integer:
if (n + 1).is_nonnegative:
return True
if n.is_odd:
return (n + 3).is_nonnegative
def _eval_is_odd(self):
# Double factorial is odd for every odd input not smaller than -3, and
# for 0
n = self.args[0]
if n.is_odd:
return (n + 3).is_nonnegative
if n.is_even:
if n.is_positive:
return False
if n.is_zero:
return True
def _eval_is_positive(self):
# Double factorial is positive for every nonnegative input, and for
# every odd negative input which is of the form -1-4k for an
# nonnegative integer k
n = self.args[0]
if n.is_integer:
if (n + 1).is_nonnegative:
return True
if n.is_odd:
return ((n + 1) / 2).is_even
def _eval_rewrite_as_gamma(self, n, piecewise=True, **kwargs):
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.piecewise import Piecewise
from sympy.functions.special.gamma_functions import gamma
return 2**(n/2)*gamma(n/2 + 1) * Piecewise((1, Eq(Mod(n, 2), 0)),
(sqrt(2/pi), Eq(Mod(n, 2), 1)))
###############################################################################
######################## RISING and FALLING FACTORIALS ########################
###############################################################################
class RisingFactorial(CombinatorialFunction):
r"""
Rising factorial (also called Pochhammer symbol [1]_) is a double valued
function arising in concrete mathematics, hypergeometric functions
and series expansions. It is defined by:
.. math:: \texttt{rf(y, k)} = (x)^k = x \cdot (x+1) \cdots (x+k-1)
where `x` can be arbitrary expression and `k` is an integer. For
more information check "Concrete mathematics" by Graham, pp. 66
or visit https://mathworld.wolfram.com/RisingFactorial.html page.
When `x` is a `~.Poly` instance of degree $\ge 1$ with a single variable,
`(x)^k = x(y) \cdot x(y+1) \cdots x(y+k-1)`, where `y` is the
variable of `x`. This is as described in [2]_.
Examples
========
>>> from sympy import rf, Poly
>>> from sympy.abc import x
>>> rf(x, 0)
1
>>> rf(1, 5)
120
>>> rf(x, 5) == x*(1 + x)*(2 + x)*(3 + x)*(4 + x)
True
>>> rf(Poly(x**3, x), 2)
Poly(x**6 + 3*x**5 + 3*x**4 + x**3, x, domain='ZZ')
Rewriting is complicated unless the relationship between
the arguments is known, but rising factorial can
be rewritten in terms of gamma, factorial, binomial,
and falling factorial.
>>> from sympy import Symbol, factorial, ff, binomial, gamma
>>> n = Symbol('n', integer=True, positive=True)
>>> R = rf(n, n + 2)
>>> for i in (rf, ff, factorial, binomial, gamma):
... R.rewrite(i)
...
RisingFactorial(n, n + 2)
FallingFactorial(2*n + 1, n + 2)
factorial(2*n + 1)/factorial(n - 1)
binomial(2*n + 1, n + 2)*factorial(n + 2)
gamma(2*n + 2)/gamma(n)
See Also
========
factorial, factorial2, FallingFactorial
References
==========
.. [1] https://en.wikipedia.org/wiki/Pochhammer_symbol
.. [2] Peter Paule, "Greatest Factorial Factorization and Symbolic
Summation", Journal of Symbolic Computation, vol. 20, pp. 235-268,
1995.
"""
@classmethod
def eval(cls, x, k):
x = sympify(x)
k = sympify(k)
if x is S.NaN or k is S.NaN:
return S.NaN
elif x is S.One:
return factorial(k)
elif k.is_Integer:
if k.is_zero:
return S.One
else:
if k.is_positive:
if x is S.Infinity:
return S.Infinity
elif x is S.NegativeInfinity:
if k.is_odd:
return S.NegativeInfinity
else:
return S.Infinity
else:
if isinstance(x, Poly):
gens = x.gens
if len(gens)!= 1:
raise ValueError("rf only defined for "
"polynomials on one generator")
else:
return reduce(lambda r, i:
r*(x.shift(i)),
range(int(k)), 1)
else:
return reduce(lambda r, i: r*(x + i),
range(int(k)), 1)
else:
if x is S.Infinity:
return S.Infinity
elif x is S.NegativeInfinity:
return S.Infinity
else:
if isinstance(x, Poly):
gens = x.gens
if len(gens)!= 1:
raise ValueError("rf only defined for "
"polynomials on one generator")
else:
return 1/reduce(lambda r, i:
r*(x.shift(-i)),
range(1, abs(int(k)) + 1), 1)
else:
return 1/reduce(lambda r, i:
r*(x - i),
range(1, abs(int(k)) + 1), 1)
if k.is_integer == False:
if x.is_integer and x.is_negative:
return S.Zero
def _eval_rewrite_as_gamma(self, x, k, piecewise=True, **kwargs):
from sympy.functions.elementary.piecewise import Piecewise
from sympy.functions.special.gamma_functions import gamma
if not piecewise:
if (x <= 0) == True:
return S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1)
return gamma(x + k) / gamma(x)
return Piecewise(
(gamma(x + k) / gamma(x), x > 0),
(S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1), True))
def _eval_rewrite_as_FallingFactorial(self, x, k, **kwargs):
return FallingFactorial(x + k - 1, k)
def _eval_rewrite_as_factorial(self, x, k, **kwargs):
from sympy.functions.elementary.piecewise import Piecewise
if x.is_integer and k.is_integer:
return Piecewise(
(factorial(k + x - 1)/factorial(x - 1), x > 0),
(S.NegativeOne**k*factorial(-x)/factorial(-k - x), True))
def _eval_rewrite_as_binomial(self, x, k, **kwargs):
if k.is_integer:
return factorial(k) * binomial(x + k - 1, k)
def _eval_rewrite_as_tractable(self, x, k, limitvar=None, **kwargs):
from sympy.functions.special.gamma_functions import gamma
if limitvar:
k_lim = k.subs(limitvar, S.Infinity)
if k_lim is S.Infinity:
return (gamma(x + k).rewrite('tractable', deep=True) / gamma(x))
elif k_lim is S.NegativeInfinity:
return (S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1).rewrite('tractable', deep=True))
return self.rewrite(gamma).rewrite('tractable', deep=True)
def _eval_is_integer(self):
return fuzzy_and((self.args[0].is_integer, self.args[1].is_integer,
self.args[1].is_nonnegative))
class FallingFactorial(CombinatorialFunction):
r"""
Falling factorial (related to rising factorial) is a double valued
function arising in concrete mathematics, hypergeometric functions
and series expansions. It is defined by
.. math:: \texttt{ff(x, k)} = (x)_k = x \cdot (x-1) \cdots (x-k+1)
where `x` can be arbitrary expression and `k` is an integer. For
more information check "Concrete mathematics" by Graham, pp. 66
or [1]_.
When `x` is a `~.Poly` instance of degree $\ge 1$ with single variable,
`(x)_k = x(y) \cdot x(y-1) \cdots x(y-k+1)`, where `y` is the
variable of `x`. This is as described in
>>> from sympy import ff, Poly, Symbol
>>> from sympy.abc import x
>>> n = Symbol('n', integer=True)
>>> ff(x, 0)
1
>>> ff(5, 5)
120
>>> ff(x, 5) == x*(x - 1)*(x - 2)*(x - 3)*(x - 4)
True
>>> ff(Poly(x**2, x), 2)
Poly(x**4 - 2*x**3 + x**2, x, domain='ZZ')
>>> ff(n, n)
factorial(n)
Rewriting is complicated unless the relationship between
the arguments is known, but falling factorial can
be rewritten in terms of gamma, factorial and binomial
and rising factorial.
>>> from sympy import factorial, rf, gamma, binomial, Symbol
>>> n = Symbol('n', integer=True, positive=True)
>>> F = ff(n, n - 2)
>>> for i in (rf, ff, factorial, binomial, gamma):
... F.rewrite(i)
...
RisingFactorial(3, n - 2)
FallingFactorial(n, n - 2)
factorial(n)/2
binomial(n, n - 2)*factorial(n - 2)
gamma(n + 1)/2
See Also
========
factorial, factorial2, RisingFactorial
References
==========
.. [1] https://mathworld.wolfram.com/FallingFactorial.html
.. [2] Peter Paule, "Greatest Factorial Factorization and Symbolic
Summation", Journal of Symbolic Computation, vol. 20, pp. 235-268,
1995.
"""
@classmethod
def eval(cls, x, k):
x = sympify(x)
k = sympify(k)
if x is S.NaN or k is S.NaN:
return S.NaN
elif k.is_integer and x == k:
return factorial(x)
elif k.is_Integer:
if k.is_zero:
return S.One
else:
if k.is_positive:
if x is S.Infinity:
return S.Infinity
elif x is S.NegativeInfinity:
if k.is_odd:
return S.NegativeInfinity
else:
return S.Infinity
else:
if isinstance(x, Poly):
gens = x.gens
if len(gens)!= 1:
raise ValueError("ff only defined for "
"polynomials on one generator")
else:
return reduce(lambda r, i:
r*(x.shift(-i)),
range(int(k)), 1)
else:
return reduce(lambda r, i: r*(x - i),
range(int(k)), 1)
else:
if x is S.Infinity:
return S.Infinity
elif x is S.NegativeInfinity:
return S.Infinity
else:
if isinstance(x, Poly):
gens = x.gens
if len(gens)!= 1:
raise ValueError("rf only defined for "
"polynomials on one generator")
else:
return 1/reduce(lambda r, i:
r*(x.shift(i)),
range(1, abs(int(k)) + 1), 1)
else:
return 1/reduce(lambda r, i: r*(x + i),
range(1, abs(int(k)) + 1), 1)
def _eval_rewrite_as_gamma(self, x, k, piecewise=True, **kwargs):
from sympy.functions.elementary.piecewise import Piecewise
from sympy.functions.special.gamma_functions import gamma
if not piecewise:
if (x < 0) == True:
return S.NegativeOne**k*gamma(k - x) / gamma(-x)
return gamma(x + 1) / gamma(x - k + 1)
return Piecewise(
(gamma(x + 1) / gamma(x - k + 1), x >= 0),
(S.NegativeOne**k*gamma(k - x) / gamma(-x), True))
def _eval_rewrite_as_RisingFactorial(self, x, k, **kwargs):
return rf(x - k + 1, k)
def _eval_rewrite_as_binomial(self, x, k, **kwargs):
if k.is_integer:
return factorial(k) * binomial(x, k)
def _eval_rewrite_as_factorial(self, x, k, **kwargs):
from sympy.functions.elementary.piecewise import Piecewise
if x.is_integer and k.is_integer:
return Piecewise(
(factorial(x)/factorial(-k + x), x >= 0),
(S.NegativeOne**k*factorial(k - x - 1)/factorial(-x - 1), True))
def _eval_rewrite_as_tractable(self, x, k, limitvar=None, **kwargs):
from sympy.functions.special.gamma_functions import gamma
if limitvar:
k_lim = k.subs(limitvar, S.Infinity)
if k_lim is S.Infinity:
return (S.NegativeOne**k*gamma(k - x).rewrite('tractable', deep=True) / gamma(-x))
elif k_lim is S.NegativeInfinity:
return (gamma(x + 1) / gamma(x - k + 1).rewrite('tractable', deep=True))
return self.rewrite(gamma).rewrite('tractable', deep=True)
def _eval_is_integer(self):
return fuzzy_and((self.args[0].is_integer, self.args[1].is_integer,
self.args[1].is_nonnegative))
rf = RisingFactorial
ff = FallingFactorial
###############################################################################
########################### BINOMIAL COEFFICIENTS #############################
###############################################################################
class binomial(CombinatorialFunction):
r"""Implementation of the binomial coefficient. It can be defined
in two ways depending on its desired interpretation:
.. math:: \binom{n}{k} = \frac{n!}{k!(n-k)!}\ \text{or}\
\binom{n}{k} = \frac{(n)_k}{k!}
First, in a strict combinatorial sense it defines the
number of ways we can choose `k` elements from a set of
`n` elements. In this case both arguments are nonnegative
integers and binomial is computed using an efficient
algorithm based on prime factorization.
The other definition is generalization for arbitrary `n`,
however `k` must also be nonnegative. This case is very
useful when evaluating summations.
For the sake of convenience, for negative integer `k` this function
will return zero no matter the other argument.
To expand the binomial when `n` is a symbol, use either
``expand_func()`` or ``expand(func=True)``. The former will keep
the polynomial in factored form while the latter will expand the
polynomial itself. See examples for details.
Examples
========
>>> from sympy import Symbol, Rational, binomial, expand_func
>>> n = Symbol('n', integer=True, positive=True)
>>> binomial(15, 8)
6435
>>> binomial(n, -1)
0
Rows of Pascal's triangle can be generated with the binomial function:
>>> for N in range(8):
... print([binomial(N, i) for i in range(N + 1)])
...
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
As can a given diagonal, e.g. the 4th diagonal:
>>> N = -4
>>> [binomial(N, i) for i in range(1 - N)]
[1, -4, 10, -20, 35]
>>> binomial(Rational(5, 4), 3)
-5/128
>>> binomial(Rational(-5, 4), 3)
-195/128
>>> binomial(n, 3)
binomial(n, 3)
>>> binomial(n, 3).expand(func=True)
n**3/6 - n**2/2 + n/3
>>> expand_func(binomial(n, 3))
n*(n - 2)*(n - 1)/6
References
==========
.. [1] https://www.johndcook.com/blog/binomial_coefficients/
"""
def fdiff(self, argindex=1):
from sympy.functions.special.gamma_functions import polygamma
if argindex == 1:
# https://functions.wolfram.com/GammaBetaErf/Binomial/20/01/01/
n, k = self.args
return binomial(n, k)*(polygamma(0, n + 1) - \
polygamma(0, n - k + 1))
elif argindex == 2:
# https://functions.wolfram.com/GammaBetaErf/Binomial/20/01/02/
n, k = self.args
return binomial(n, k)*(polygamma(0, n - k + 1) - \
polygamma(0, k + 1))
else:
raise ArgumentIndexError(self, argindex)
@classmethod
def _eval(self, n, k):
# n.is_Number and k.is_Integer and k != 1 and n != k
if k.is_Integer:
if n.is_Integer and n >= 0:
n, k = int(n), int(k)
if k > n:
return S.Zero
elif k > n // 2:
k = n - k
if HAS_GMPY:
return Integer(gmpy.bincoef(n, k))
d, result = n - k, 1
for i in range(1, k + 1):
d += 1
result = result * d // i
return Integer(result)
else:
d, result = n - k, 1
for i in range(1, k + 1):
d += 1
result *= d
return result / _factorial(k)
@classmethod
def eval(cls, n, k):
n, k = map(sympify, (n, k))
d = n - k
n_nonneg, n_isint = n.is_nonnegative, n.is_integer
if k.is_zero or ((n_nonneg or n_isint is False)
and d.is_zero):
return S.One
if (k - 1).is_zero or ((n_nonneg or n_isint is False)
and (d - 1).is_zero):
return n
if k.is_integer:
if k.is_negative or (n_nonneg and n_isint and d.is_negative):
return S.Zero
elif n.is_number:
res = cls._eval(n, k)
return res.expand(basic=True) if res else res
elif n_nonneg is False and n_isint:
# a special case when binomial evaluates to complex infinity
return S.ComplexInfinity
elif k.is_number:
from sympy.functions.special.gamma_functions import gamma
return gamma(n + 1)/(gamma(k + 1)*gamma(n - k + 1))
def _eval_Mod(self, q):
n, k = self.args
if any(x.is_integer is False for x in (n, k, q)):
raise ValueError("Integers expected for binomial Mod")
if all(x.is_Integer for x in (n, k, q)):
n, k = map(int, (n, k))
aq, res = abs(q), 1
# handle negative integers k or n
if k < 0:
return S.Zero
if n < 0:
n = -n + k - 1
res = -1 if k%2 else 1
# non negative integers k and n
if k > n:
return S.Zero
isprime = aq.is_prime
aq = int(aq)
if isprime:
if aq < n:
# use Lucas Theorem
N, K = n, k
while N or K:
res = res*binomial(N % aq, K % aq) % aq
N, K = N // aq, K // aq
else:
# use Factorial Modulo
d = n - k
if k > d:
k, d = d, k
kf = 1
for i in range(2, k + 1):
kf = kf*i % aq
df = kf
for i in range(k + 1, d + 1):
df = df*i % aq
res *= df
for i in range(d + 1, n + 1):
res = res*i % aq
res *= pow(kf*df % aq, aq - 2, aq)
res %= aq
else:
# Binomial Factorization is performed by calculating the
# exponents of primes <= n in `n! /(k! (n - k)!)`,
# for non-negative integers n and k. As the exponent of
# prime in n! is e_p(n) = [n/p] + [n/p**2] + ...
# the exponent of prime in binomial(n, k) would be
# e_p(n) - e_p(k) - e_p(n - k)
M = int(_sqrt(n))
for prime in sieve.primerange(2, n + 1):
if prime > n - k:
res = res*prime % aq
elif prime > n // 2:
continue
elif prime > M:
if n % prime < k % prime:
res = res*prime % aq
else:
N, K = n, k
exp = a = 0
while N > 0:
a = int((N % prime) < (K % prime + a))
N, K = N // prime, K // prime
exp += a
if exp > 0:
res *= pow(prime, exp, aq)
res %= aq
return S(res % q)
def _eval_expand_func(self, **hints):
"""
Function to expand binomial(n, k) when m is positive integer
Also,
n is self.args[0] and k is self.args[1] while using binomial(n, k)
"""
n = self.args[0]
if n.is_Number:
return binomial(*self.args)
k = self.args[1]
if (n-k).is_Integer:
k = n - k
if k.is_Integer:
if k.is_zero:
return S.One
elif k.is_negative:
return S.Zero
else:
n, result = self.args[0], 1
for i in range(1, k + 1):
result *= n - k + i
return result / _factorial(k)
else:
return binomial(*self.args)
def _eval_rewrite_as_factorial(self, n, k, **kwargs):
return factorial(n)/(factorial(k)*factorial(n - k))
def _eval_rewrite_as_gamma(self, n, k, piecewise=True, **kwargs):
from sympy.functions.special.gamma_functions import gamma
return gamma(n + 1)/(gamma(k + 1)*gamma(n - k + 1))
def _eval_rewrite_as_tractable(self, n, k, limitvar=None, **kwargs):
return self._eval_rewrite_as_gamma(n, k).rewrite('tractable')
def _eval_rewrite_as_FallingFactorial(self, n, k, **kwargs):
if k.is_integer:
return ff(n, k) / factorial(k)
def _eval_is_integer(self):
n, k = self.args
if n.is_integer and k.is_integer:
return True
elif k.is_integer is False:
return False
def _eval_is_nonnegative(self):
n, k = self.args
if n.is_integer and k.is_integer:
if n.is_nonnegative or k.is_negative or k.is_even:
return True
elif k.is_even is False:
return False
def _eval_as_leading_term(self, x, logx=None, cdir=0):
from sympy.functions.special.gamma_functions import gamma
return self.rewrite(gamma)._eval_as_leading_term(x, logx=logx, cdir=cdir)