def q_binomial(n, k, q=None, algorithm='auto'): r""" Return the `q`-binomial coefficient. This is also known as the Gaussian binomial coefficient, and is defined by .. MATH:: \binom{n}{k}_q = \frac{(1-q^n)(1-q^{n-1}) \cdots (1-q^{n-k+1})} {(1-q)(1-q^2)\cdots (1-q^k)}. See :wikipedia:`Gaussian_binomial_coefficient` If `q` is unspecified, then the variable is the generator `q` for a univariate polynomial ring over the integers. INPUT: - ``n, k`` -- The values, `n` and `k` defined above. - ``q`` -- (Default: ``None``) The variable `q`; if ``None``, then use a default variable in `\ZZ[q]`. - ``algorithm`` -- (Default: ``'auto'``) The algorithm to use and can be one of the following: - ``'auto'`` -- Automatically choose the algorithm; see the algorithm section below - ``'naive'`` -- Use the naive algorithm - ``'cyclotomic'`` -- Use cyclotomic algorithm ALGORITHM: The naive algorithm uses the product formula. The cyclotomic algorithm uses a product of cyclotomic polynomials (cf. [CH2006]_). When the algorithm is set to ``'auto'``, we choose according to the following rules: - If ``q`` is a polynomial: When ``n`` is small or ``k`` is small with respect to ``n``, one uses the naive algorithm. When both ``n`` and ``k`` are big, one uses the cyclotomic algorithm. - If ``q`` is in the symbolic ring, one uses the cyclotomic algorithm. - Otherwise one uses the naive algorithm, unless ``q`` is a root of unity, then one uses the cyclotomic algorithm. EXAMPLES: By default, the variable is the generator of `\ZZ[q]`:: sage: from sage.combinat.q_analogues import q_binomial sage: g = q_binomial(5,1) ; g q^4 + q^3 + q^2 + q + 1 sage: g.parent() Univariate Polynomial Ring in q over Integer Ring The `q`-binomial coefficient vanishes unless `0 \leq k \leq n`:: sage: q_binomial(4,5) 0 sage: q_binomial(5,-1) 0 Other variables can be used, given as third parameter:: sage: p = ZZ['p'].gen() sage: q_binomial(4,2,p) p^4 + p^3 + 2*p^2 + p + 1 The third parameter can also be arbitrary values:: sage: q_binomial(5,1,2) == g.subs(q=2) True sage: q_binomial(5,1,1) 5 sage: q_binomial(4,2,-1) 2 sage: q_binomial(4,2,3.14) 152.030056160000 sage: R = GF(25, 't') sage: t = R.gen(0) sage: q_binomial(6, 3, t) 2*t + 3 We can also do this for more complicated objects such as matrices or symmetric functions:: sage: q_binomial(4,2,matrix([[2,1],[-1,3]])) [ -6 84] [-84 78] sage: Sym = SymmetricFunctions(QQ) sage: s = Sym.schur() sage: q_binomial(4,1, s[2]+s[1]) s[] + s[1] + s[1, 1] + s[1, 1, 1] + 2*s[2] + 4*s[2, 1] + 3*s[2, 1, 1] + 4*s[2, 2] + 3*s[2, 2, 1] + s[2, 2, 2] + 3*s[3] + 7*s[3, 1] + 3*s[3, 1, 1] + 6*s[3, 2] + 2*s[3, 2, 1] + s[3, 3] + 4*s[4] + 6*s[4, 1] + s[4, 1, 1] + 3*s[4, 2] + 3*s[5] + 2*s[5, 1] + s[6] TESTS: One checks that the first two arguments are integers:: sage: q_binomial(1/2,1) Traceback (most recent call last): ... ValueError: arguments (1/2, 1) must be integers One checks that `n` is nonnegative:: sage: q_binomial(-4,1) Traceback (most recent call last): ... ValueError: n must be nonnegative This also works for variables in the symbolic ring:: sage: z = var('z') sage: factor(q_binomial(4,2,z)) (z^2 + z + 1)*(z^2 + 1) This also works for complex roots of unity:: sage: q_binomial(6,1,I) 1 + I Check that the algorithm does not matter:: sage: q_binomial(6,3, algorithm='naive') == q_binomial(6,3, algorithm='cyclotomic') True One more test:: sage: q_binomial(4, 2, Zmod(6)(2), algorithm='naive') 5 REFERENCES: .. [CH2006] William Y.C. Chen and Qing-Hu Hou, "Factors of the Gaussian coefficients", Discrete Mathematics 306 (2006), 1446-1449. :doi:`10.1016/j.disc.2006.03.031` AUTHORS: - Frederic Chapoton, David Joyner and William Stein """ # sanity checks if not (n in ZZ and k in ZZ): raise ValueError("arguments (%s, %s) must be integers" % (n, k)) if n < 0: raise ValueError('n must be nonnegative') if not (0 <= k and k <= n): return 0 k = min(n - k, k) # Pick the smallest k # polynomiality test if q is None: from sage.rings.polynomial.polynomial_ring import polygen q = polygen(ZZ, name='q') is_polynomial = True else: from sage.rings.polynomial.polynomial_element import Polynomial is_polynomial = isinstance(q, Polynomial) from sage.symbolic.ring import SR # heuristic choice of the fastest algorithm if algorithm == 'auto': if is_polynomial: if n <= 70 or k <= n / 4: algorithm = 'naive' else: algorithm = 'cyclo_polynomial' elif q in SR: algorithm = 'cyclo_generic' else: algorithm = 'naive' elif algorithm == 'cyclotomic': if is_polynomial: algorithm = 'cyclo_polynomial' else: algorithm = 'cyclo_generic' elif algorithm != 'naive': raise ValueError("invalid algorithm choice") # the algorithms try: if algorithm == 'naive': denomin = prod([1 - q**i for i in range(1, k + 1)]) if denomin == 0: # q is a root of unity, use the cyclotomic algorithm algorithm = 'cyclo_generic' else: numerat = prod([1 - q**i for i in range(n - k + 1, n + 1)]) try: return numerat // denomin except TypeError: return numerat / denomin from sage.functions.all import floor if algorithm == 'cyclo_generic': from sage.rings.polynomial.cyclotomic import cyclotomic_value return prod( cyclotomic_value(d, q) for d in range(2, n + 1) if floor(n / d) != floor(k / d) + floor((n - k) / d)) if algorithm == 'cyclo_polynomial': R = q.parent() return prod( R.cyclotomic_polynomial(d) for d in range(2, n + 1) if floor(n / d) != floor(k / d) + floor((n - k) / d)) except (ZeroDivisionError, TypeError): # As a last attempt, do the computation formally and then substitute return q_binomial(n, k)(q)
def q_binomial(n, k, q=None, algorithm='auto'): r""" Return the `q`-binomial coefficient. This is also known as the Gaussian binomial coefficient, and is defined by .. MATH:: \binom{n}{k}_q = \frac{(1-q^n)(1-q^{n-1}) \cdots (1-q^{n-k+1})} {(1-q)(1-q^2)\cdots (1-q^k)}. See :wikipedia:`Gaussian_binomial_coefficient`. If `q` is unspecified, then the variable is the generator `q` for a univariate polynomial ring over the integers. INPUT: - ``n, k`` -- the values `n` and `k` defined above - ``q`` -- (default: ``None``) the variable `q`; if ``None``, then use a default variable in `\ZZ[q]` - ``algorithm`` -- (default: ``'auto'``) the algorithm to use and can be one of the following: - ``'auto'`` -- automatically choose the algorithm; see the algorithm section below - ``'naive'`` -- use the naive algorithm - ``'cyclotomic'`` -- use cyclotomic algorithm ALGORITHM: The naive algorithm uses the product formula. The cyclotomic algorithm uses a product of cyclotomic polynomials (cf. [CH2006]_). When the algorithm is set to ``'auto'``, we choose according to the following rules: - If ``q`` is a polynomial: When ``n`` is small or ``k`` is small with respect to ``n``, one uses the naive algorithm. When both ``n`` and ``k`` are big, one uses the cyclotomic algorithm. - If ``q`` is in the symbolic ring, one uses the cyclotomic algorithm. - Otherwise one uses the naive algorithm, unless ``q`` is a root of unity, then one uses the cyclotomic algorithm. EXAMPLES: By default, the variable is the generator of `\ZZ[q]`:: sage: from sage.combinat.q_analogues import q_binomial sage: g = q_binomial(5,1) ; g q^4 + q^3 + q^2 + q + 1 sage: g.parent() Univariate Polynomial Ring in q over Integer Ring The `q`-binomial coefficient vanishes unless `0 \leq k \leq n`:: sage: q_binomial(4,5) 0 sage: q_binomial(5,-1) 0 Other variables can be used, given as third parameter:: sage: p = ZZ['p'].gen() sage: q_binomial(4,2,p) p^4 + p^3 + 2*p^2 + p + 1 The third parameter can also be arbitrary values:: sage: q_binomial(5,1,2) == g.subs(q=2) True sage: q_binomial(5,1,1) 5 sage: q_binomial(4,2,-1) 2 sage: q_binomial(4,2,3.14) 152.030056160000 sage: R = GF(25, 't') sage: t = R.gen(0) sage: q_binomial(6, 3, t) 2*t + 3 We can also do this for more complicated objects such as matrices or symmetric functions:: sage: q_binomial(4,2,matrix([[2,1],[-1,3]])) [ -6 84] [-84 78] sage: Sym = SymmetricFunctions(QQ) sage: s = Sym.schur() sage: q_binomial(4,1, s[2]+s[1]) s[] + s[1] + s[1, 1] + s[1, 1, 1] + 2*s[2] + 4*s[2, 1] + 3*s[2, 1, 1] + 4*s[2, 2] + 3*s[2, 2, 1] + s[2, 2, 2] + 3*s[3] + 7*s[3, 1] + 3*s[3, 1, 1] + 6*s[3, 2] + 2*s[3, 2, 1] + s[3, 3] + 4*s[4] + 6*s[4, 1] + s[4, 1, 1] + 3*s[4, 2] + 3*s[5] + 2*s[5, 1] + s[6] TESTS: One checks that the first two arguments are integers:: sage: q_binomial(1/2,1) Traceback (most recent call last): ... TypeError: no conversion of this rational to integer One checks that `n` is nonnegative:: sage: q_binomial(-4,1) Traceback (most recent call last): ... ValueError: n must be nonnegative This also works for variables in the symbolic ring:: sage: z = var('z') sage: factor(q_binomial(4,2,z)) (z^2 + z + 1)*(z^2 + 1) This also works for complex roots of unity:: sage: q_binomial(6, 1, QQbar(I)) I + 1 Note that the symbolic computation works (see :trac:`14982`):: sage: q_binomial(6, 1, I) I + 1 Check that the algorithm does not matter:: sage: q_binomial(6,3, algorithm='naive') == q_binomial(6,3, algorithm='cyclotomic') True One more test:: sage: q_binomial(4, 2, Zmod(6)(2), algorithm='naive') 5 REFERENCES: .. [CH2006] William Y.C. Chen and Qing-Hu Hou, *Factors of the Gaussian coefficients*, Discrete Mathematics 306 (2006), 1446-1449. :doi:`10.1016/j.disc.2006.03.031` AUTHORS: - Frederic Chapoton, David Joyner and William Stein """ # sanity checks n = ZZ(n) k = ZZ(k) if n < 0: raise ValueError('n must be nonnegative') # polynomiality test if q is None: from sage.rings.polynomial.polynomial_ring import polygen q = polygen(ZZ, name='q') is_polynomial = True else: from sage.rings.polynomial.polynomial_element import Polynomial is_polynomial = isinstance(q, Polynomial) R = parent(q) zero = R.zero() one = R.one() if not(0 <= k and k <= n): return zero k = min(n-k,k) # Pick the smallest k # heuristic choice of the fastest algorithm if algorithm == 'auto': from sage.symbolic.ring import SR if is_polynomial: if n <= 70 or k <= n // 4: algorithm = 'naive' else: algorithm = 'cyclo_polynomial' elif q in SR: algorithm = 'cyclo_generic' else: algorithm = 'naive' elif algorithm == 'cyclotomic': if is_polynomial: algorithm = 'cyclo_polynomial' else: algorithm = 'cyclo_generic' elif algorithm != 'naive': raise ValueError("invalid algorithm choice") # the algorithms if algorithm == 'naive': denom = prod(one - q**i for i in range(1, k+1)) if not denom: # q is a root of unity, use the cyclotomic algorithm return cyclotomic_value(n, k, q, algorithm='cyclotomic') else: num = prod(one - q**i for i in range(n-k+1, n+1)) try: try: return num // denom except TypeError: return num / denom except (TypeError, ZeroDivisionError): # use substitution instead return q_binomial(n,k)(q) elif algorithm == 'cyclo_generic': from sage.rings.polynomial.cyclotomic import cyclotomic_value return prod(cyclotomic_value(d,q) for d in range(2,n+1) if (n//d) != (k//d) + ((n-k)//d)) elif algorithm == 'cyclo_polynomial': return prod(R.cyclotomic_polynomial(d) for d in range(2,n+1) if (n//d) != (k//d) + ((n-k)//d))
def q_binomial(n, k, q=None, algorithm='auto'): r""" Return the `q`-binomial coefficient. This is also known as the Gaussian binomial coefficient, and is defined by .. MATH:: \binom{n}{k}_q = \frac{(1-q^n)(1-q^{n-1}) \cdots (1-q^{n-k+1})} {(1-q)(1-q^2)\cdots (1-q^k)}. See :wikipedia:`Gaussian_binomial_coefficient`. If `q` is unspecified, then the variable is the generator `q` for a univariate polynomial ring over the integers. INPUT: - ``n, k`` -- the values `n` and `k` defined above - ``q`` -- (default: ``None``) the variable `q`; if ``None``, then use a default variable in `\ZZ[q]` - ``algorithm`` -- (default: ``'auto'``) the algorithm to use and can be one of the following: - ``'auto'`` -- automatically choose the algorithm; see the algorithm section below - ``'naive'`` -- use the naive algorithm - ``'cyclotomic'`` -- use cyclotomic algorithm ALGORITHM: The naive algorithm uses the product formula. The cyclotomic algorithm uses a product of cyclotomic polynomials (cf. [CH2006]_). When the algorithm is set to ``'auto'``, we choose according to the following rules: - If ``q`` is a polynomial: When ``n`` is small or ``k`` is small with respect to ``n``, one uses the naive algorithm. When both ``n`` and ``k`` are big, one uses the cyclotomic algorithm. - If ``q`` is in the symbolic ring, one uses the cyclotomic algorithm. - Otherwise one uses the naive algorithm, unless ``q`` is a root of unity, then one uses the cyclotomic algorithm. EXAMPLES: By default, the variable is the generator of `\ZZ[q]`:: sage: from sage.combinat.q_analogues import q_binomial sage: g = q_binomial(5,1) ; g q^4 + q^3 + q^2 + q + 1 sage: g.parent() Univariate Polynomial Ring in q over Integer Ring The `q`-binomial coefficient vanishes unless `0 \leq k \leq n`:: sage: q_binomial(4,5) 0 sage: q_binomial(5,-1) 0 Other variables can be used, given as third parameter:: sage: p = ZZ['p'].gen() sage: q_binomial(4,2,p) p^4 + p^3 + 2*p^2 + p + 1 The third parameter can also be arbitrary values:: sage: q_binomial(5,1,2) == g.subs(q=2) True sage: q_binomial(5,1,1) 5 sage: q_binomial(4,2,-1) 2 sage: q_binomial(4,2,3.14) 152.030056160000 sage: R = GF(25, 't') sage: t = R.gen(0) sage: q_binomial(6, 3, t) 2*t + 3 We can also do this for more complicated objects such as matrices or symmetric functions:: sage: q_binomial(4,2,matrix([[2,1],[-1,3]])) [ -6 84] [-84 78] sage: Sym = SymmetricFunctions(QQ) sage: s = Sym.schur() sage: q_binomial(4,1, s[2]+s[1]) s[] + s[1] + s[1, 1] + s[1, 1, 1] + 2*s[2] + 4*s[2, 1] + 3*s[2, 1, 1] + 4*s[2, 2] + 3*s[2, 2, 1] + s[2, 2, 2] + 3*s[3] + 7*s[3, 1] + 3*s[3, 1, 1] + 6*s[3, 2] + 2*s[3, 2, 1] + s[3, 3] + 4*s[4] + 6*s[4, 1] + s[4, 1, 1] + 3*s[4, 2] + 3*s[5] + 2*s[5, 1] + s[6] TESTS: One checks that the first two arguments are integers:: sage: q_binomial(1/2,1) Traceback (most recent call last): ... TypeError: no conversion of this rational to integer One checks that `n` is nonnegative:: sage: q_binomial(-4,1) Traceback (most recent call last): ... ValueError: n must be nonnegative This also works for variables in the symbolic ring:: sage: z = var('z') sage: factor(q_binomial(4,2,z)) (z^2 + z + 1)*(z^2 + 1) This also works for complex roots of unity:: sage: q_binomial(10, 4, QQbar(I)) 2 Note that the symbolic computation works (see :trac:`14982`):: sage: q_binomial(10, 4, I) 2 Check that the algorithm does not matter:: sage: q_binomial(6, 3, algorithm='naive') == q_binomial(6, 3, algorithm='cyclotomic') True One more test:: sage: q_binomial(4, 2, Zmod(6)(2), algorithm='naive') 5 Check that it works with Python integers:: sage: r = q_binomial(3r, 2r, 1r); r 3 sage: type(r) <type 'int'> Check that arbitrary polynomials work:: sage: R.<x> = ZZ[] sage: q_binomial(2, 1, x^2 - 1, algorithm="naive") x^2 sage: q_binomial(2, 1, x^2 - 1, algorithm="cyclotomic") x^2 Check that the parent is always the parent of ``q``:: sage: R.<q> = CyclotomicField(3) sage: for algo in ["naive", "cyclotomic"]: ....: for n in range(4): ....: for k in range(4): ....: a = q_binomial(n, k, q, algorithm=algo) ....: assert a.parent() is R :: sage: q_binomial(2, 1, x^2 - 1, algorithm="quantum") Traceback (most recent call last): ... ValueError: unknown algorithm 'quantum' REFERENCES: .. [CH2006] William Y.C. Chen and Qing-Hu Hou, *Factors of the Gaussian coefficients*, Discrete Mathematics 306 (2006), 1446-1449. :doi:`10.1016/j.disc.2006.03.031` AUTHORS: - Frederic Chapoton, David Joyner and William Stein """ # sanity checks n = ZZ(n) k = ZZ(k) if n < 0: raise ValueError('n must be nonnegative') k = min(n - k, k) # Pick the smallest k # polynomiality test if q is None: from sage.rings.polynomial.polynomial_ring import polygen q = polygen(ZZ, name='q') is_polynomial = True else: from sage.rings.polynomial.polynomial_element import Polynomial is_polynomial = isinstance(q, Polynomial) # We support non-Sage Elements too, where parent(q) is really # type(q). The calls R(0) and R(1) should work in all cases to # generate the correct 0 and 1 elements. R = parent(q) zero = R(0) one = R(1) if k <= 0: return one if k == 0 else zero # heuristic choice of the fastest algorithm if algorithm == 'auto': if n <= 70 or k <= n // 4: algorithm = 'naive' elif is_polynomial: algorithm = 'cyclotomic' else: from sage.symbolic.ring import SR if R is SR: algorithm = 'cyclotomic' else: algorithm = 'naive' # the algorithms while algorithm == 'naive': denom = prod(one - q**i for i in range(1, k + 1)) if not denom: # q is a root of unity, use the cyclotomic algorithm algorithm = 'cyclotomic' break else: num = prod(one - q**i for i in range(n - k + 1, n + 1)) try: try: return num // denom except TypeError: return num / denom except (TypeError, ZeroDivisionError): # use substitution instead return q_binomial(n, k)(q) if algorithm == 'cyclotomic': from sage.rings.polynomial.cyclotomic import cyclotomic_value return prod( cyclotomic_value(d, q) for d in range(2, n + 1) if (n // d) != (k // d) + ((n - k) // d)) else: raise ValueError("unknown algorithm {!r}".format(algorithm))