1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102 |
- 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)
|