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)