def weight(rg, t=None): r""" Return the weight of a rigging. INPUT: - ``rg`` -- a rigging, a list of partitions - ``t`` -- an optional parameter, (default: the generator from `\ZZ['t']`) OUTPUT: - a polynomial in the parameter ``t`` EXAMPLES:: sage: from sage.combinat.sf.kfpoly import weight sage: weight([[2,1], [1]]) 1 sage: weight([[3], [1]]) t^2 + t sage: weight([[2,1], [3]]) t^4 sage: weight([[2, 2], [1, 1]]) 1 sage: weight([[3, 1], [1, 1]]) t sage: weight([[4], [1, 1]], 2) 16 sage: weight([[4], [2]], t=2) 4 """ from sage.combinat.q_analogues import q_binomial if t is None: t = polygen(ZZ, 't') nu = rg + [[]] l = 1 + max(map(len, nu)) nu = [list(mu) + [0] * l for mu in nu] res = t**int(sum(i * (i - 1) // 2 for i in rg[-1])) for k in range(1, len(nu) - 1): sa = 0 mid = nu[k] for i in range(max(len(rg[k]), len(rg[k - 1]))): sa += nu[k - 1][i] - 2 * mid[i] + nu[k + 1][i] if mid[i] - mid[i + 1] + sa >= 0: res *= q_binomial(mid[i] - mid[i + 1] + sa, sa, t) mu = nu[k - 1][i] - mid[i] res *= t**int(mu * (mu - 1) // 2) return res
def weight(rg, t=None): r""" Return the weight of a rigging. INPUT: - ``rg`` -- a rigging, a list of partitions - ``t`` -- an optional parameter, (default: the generator from `\ZZ['t']`) OUTPUT: - a polynomial in the parameter ``t`` EXAMPLES:: sage: from sage.combinat.sf.kfpoly import weight sage: weight([[2,1], [1]]) 1 sage: weight([[3], [1]]) t^2 + t sage: weight([[2,1], [3]]) t^4 sage: weight([[2, 2], [1, 1]]) 1 sage: weight([[3, 1], [1, 1]]) t sage: weight([[4], [1, 1]], 2) 16 sage: weight([[4], [2]], t=2) 4 """ from sage.combinat.q_analogues import q_binomial if t is None: t = polygen(ZZ, 't') nu = rg + [ [] ] l = 1 + max( map(len, nu) ) nu = [ list(mu) + [0]*l for mu in nu ] res = t**int(sum(i * (i-1) // 2 for i in rg[-1])) for k in range(1, len(nu)-1): sa = 0 mid = nu[k] for i in range( max(len(rg[k]), len(rg[k-1])) ): sa += nu[k-1][i] - 2*mid[i] + nu[k+1][i] if mid[i] - mid[i+1] + sa >= 0: res *= q_binomial(mid[i]-mid[i+1]+sa, sa, t) mu = nu[k-1][i] - mid[i] res *= t**int(mu * (mu-1) // 2) return res
def AffineGeometryDesign(n, d, F, point_coordinates=True, check=True): r""" Return an affine geometry design. The affine geometry design `AG_d(n,q)` is the 2-design whose blocks are the `d`-vector subspaces in `\GF{q}^n`. It has parameters .. MATH:: v = q^n,\ k = q^d,\ \lambda = \binom{n-1}{d-1}_q where the `q`-binomial coefficient `\binom{m}{r}_q` is defined by .. MATH:: \binom{m}{r}_q = \frac{(q^m - 1)(q^{m-1} - 1) \cdots (q^{m-r+1}-1)} {(q^r-1)(q^{r-1}-1)\cdots (q-1)} .. SEEALSO:: :func:`ProjectiveGeometryDesign` INPUT: - ``n`` (integer) -- the Euclidean dimension. The number of points of the design is `v=|\GF{q}^n|`. - ``d`` (integer) -- the dimension of the (affine) subspaces of `\GF{q}^n` which make up the blocks. - ``F`` -- a finite field or a prime power. - ``point_coordinates`` -- (optional, default ``True``) whether we use coordinates in `\GF{q}^n` or plain integers for the points of the design. - ``check`` -- (optional, default ``True``) whether to check the output. EXAMPLES:: sage: BD = designs.AffineGeometryDesign(3, 1, GF(2)) sage: BD.is_t_design(return_parameters=True) (True, (2, 8, 2, 1)) sage: BD = designs.AffineGeometryDesign(3, 2, GF(4)) sage: BD.is_t_design(return_parameters=True) (True, (2, 64, 16, 5)) sage: BD = designs.AffineGeometryDesign(4, 2, GF(3)) sage: BD.is_t_design(return_parameters=True) (True, (2, 81, 9, 13)) With ``F`` an integer instead of a finite field:: sage: BD = designs.AffineGeometryDesign(3, 2, 4) sage: BD.is_t_design(return_parameters=True) (True, (2, 64, 16, 5)) Testing the option ``point_coordinates``:: sage: designs.AffineGeometryDesign(3, 1, GF(2), point_coordinates=True).blocks()[0] [(0, 0, 0), (0, 0, 1)] sage: designs.AffineGeometryDesign(3, 1, GF(2), point_coordinates=False).blocks()[0] [0, 1] """ try: q = int(F) except TypeError: q = F.cardinality() else: from sage.rings.finite_rings.finite_field_constructor import GF F = GF(q) n = int(n) d = int(d) from itertools import islice from sage.combinat.q_analogues import q_binomial from sage.matrix.echelon_matrix import reduced_echelon_matrix_iterator points = { p: i for i, p in enumerate( reduced_echelon_matrix_iterator( F, 1, n + 1, copy=True, set_immutable=True)) if p[0, 0] } blocks = [] l1 = int(q_binomial(n + 1, d + 1, q) - q_binomial(n, d + 1, q)) l2 = q**d for m1 in islice( reduced_echelon_matrix_iterator(F, d + 1, n + 1, copy=False), int(l1)): b = [] for m2 in islice( reduced_echelon_matrix_iterator(F, 1, d + 1, copy=False), int(l2)): m = m2 * m1 m.echelonize() m.set_immutable() b.append(points[m]) blocks.append(b) B = BlockDesign(len(points), blocks, name="AffineGeometryDesign", check=check) if point_coordinates: rd = {i: p[0][1:] for p, i in points.items()} for v in rd.values(): v.set_immutable() B.relabel(rd) if check: if not B.is_t_design( t=2, v=q**n, k=q**d, l=q_binomial(n - 1, d - 1, q)): raise RuntimeError( "error in AffineGeometryDesign " "construction. Please e-mail [email protected]") return B
def ProjectiveGeometryDesign(n, d, F, algorithm=None, point_coordinates=True, check=True): r""" Return a projective geometry design. The projective geometry design `PG_d(n,q)` has for points the lines of `\GF{q}^{n+1}`, and for blocks the `d+1`-dimensional subspaces of `\GF{q}^{n+1}`, each of which contains `\frac {|\GF{q}|^{d+1}-1} {|\GF{q}|-1}` lines. It is a `2`-design with parameters .. MATH:: v = \binom{n+1}{1}_q,\ k = \binom{d+1}{1}_q,\ \lambda = \binom{n-1}{d-1}_q where the `q`-binomial coefficient `\binom{m}{r}_q` is defined by .. MATH:: \binom{m}{r}_q = \frac{(q^m - 1)(q^{m-1} - 1) \cdots (q^{m-r+1}-1)} {(q^r-1)(q^{r-1}-1)\cdots (q-1)} .. SEEALSO:: :func:`AffineGeometryDesign` INPUT: - ``n`` is the projective dimension - ``d`` is the dimension of the subspaces which make up the blocks. - ``F`` -- a finite field or a prime power. - ``algorithm`` -- set to ``None`` by default, which results in using Sage's own implementation. In order to use GAP's implementation instead (i.e. its ``PGPointFlatBlockDesign`` function) set ``algorithm="gap"``. Note that GAP's "design" package must be available in this case, and that it can be installed with the ``gap_packages`` spkg. - ``point_coordinates`` -- ``True`` by default. Ignored and assumed to be ``False`` if ``algorithm="gap"``. If ``True``, the ground set is indexed by coordinates in `\GF{q}^{n+1}`. Otherwise the ground set is indexed by integers. - ``check`` -- (optional, default to ``True``) whether to check the output. EXAMPLES: The set of `d`-dimensional subspaces in a `n`-dimensional projective space forms `2`-designs (or balanced incomplete block designs):: sage: PG = designs.ProjectiveGeometryDesign(4, 2, GF(2)) sage: PG Incidence structure with 31 points and 155 blocks sage: PG.is_t_design(return_parameters=True) (True, (2, 31, 7, 7)) sage: PG = designs.ProjectiveGeometryDesign(3, 1, GF(4)) sage: PG.is_t_design(return_parameters=True) (True, (2, 85, 5, 1)) Check with ``F`` being a prime power:: sage: PG = designs.ProjectiveGeometryDesign(3, 2, 4) sage: PG Incidence structure with 85 points and 85 blocks Use coordinates:: sage: PG = designs.ProjectiveGeometryDesign(2, 1, GF(3)) sage: PG.blocks()[0] [(1, 0, 0), (1, 0, 1), (1, 0, 2), (0, 0, 1)] Use indexing by integers:: sage: PG = designs.ProjectiveGeometryDesign(2,1,GF(3),point_coordinates=0) sage: PG.blocks()[0] [0, 1, 2, 12] Check that the constructor using gap also works:: sage: BD = designs.ProjectiveGeometryDesign(2, 1, GF(2), algorithm="gap") # optional - gap_packages (design package) sage: BD.is_t_design(return_parameters=True) # optional - gap_packages (design package) (True, (2, 7, 3, 1)) """ try: q = int(F) except TypeError: q = F.cardinality() else: from sage.rings.finite_rings.finite_field_constructor import GF F = GF(q) if algorithm is None: from sage.matrix.echelon_matrix import reduced_echelon_matrix_iterator points = { p: i for i, p in enumerate( reduced_echelon_matrix_iterator( F, 1, n + 1, copy=True, set_immutable=True)) } blocks = [] for m1 in reduced_echelon_matrix_iterator(F, d + 1, n + 1, copy=False): b = [] for m2 in reduced_echelon_matrix_iterator(F, 1, d + 1, copy=False): m = m2 * m1 m.echelonize() m.set_immutable() b.append(points[m]) blocks.append(b) B = BlockDesign(len(points), blocks, name="ProjectiveGeometryDesign", check=check) if point_coordinates: B.relabel({i: p[0] for p, i in points.items()}) elif algorithm == "gap": # Requires GAP's Design libgap.load_package("design") D = libgap.PGPointFlatBlockDesign(n, F.order(), d) v = D['v'].sage() gblcks = D['blocks'].sage() gB = [] for b in gblcks: gB.append([x - 1 for x in b]) B = BlockDesign(v, gB, name="ProjectiveGeometryDesign", check=check) if check: from sage.combinat.q_analogues import q_binomial q = F.cardinality() if not B.is_t_design(t=2, v=q_binomial(n + 1, 1, q), k=q_binomial(d + 1, 1, q), l=q_binomial(n - 1, d - 1, q)): raise RuntimeError( "error in ProjectiveGeometryDesign " "construction. Please e-mail [email protected]") return B
def principal_specialization(self, n=infinity, q=None): r""" Return the principal specialization of a symmetric function. The *principal specialization* of order `n` at `q` is the ring homomorphism `ps_{n,q}` from the ring of symmetric functions to another commutative ring `R` given by `x_i \mapsto q^{i-1}` for `i \in \{1,\dots,n\}` and `x_i \mapsto 0` for `i > n`. Here, `q` is a given element of `R`, and we assume that the variables of our symmetric functions are `x_1, x_2, x_3, \ldots`. (To be more precise, `ps_{n,q}` is a `K`-algebra homomorphism, where `K` is the base ring.) See Section 7.8 of [EnumComb2]_. The *stable principal specialization* at `q` is the ring homomorphism `ps_q` from the ring of symmetric functions to another commutative ring `R` given by `x_i \mapsto q^{i-1}` for all `i`. This is well-defined only if the resulting infinite sums converge; thus, in particular, setting `q = 1` in the stable principal specialization is an invalid operation. INPUT: - ``n`` (default: ``infinity``) -- a nonnegative integer or ``infinity``, specifying whether to compute the principal specialization of order ``n`` or the stable principal specialization. - ``q`` (default: ``None``) -- the value to use for `q`; the default is to create a ring of polynomials in ``q`` (or a field of rational functions in ``q``) over the given coefficient ring. We use the formulas from Proposition 7.8.3 of [EnumComb2]_ (using Gaussian binomial coefficients `\binom{u}{v}_q`): .. MATH:: ps_{n,q}(h_\lambda) = \prod_i \binom{n+\lambda_i-1}{\lambda_i}_q, ps_{n,1}(h_\lambda) = \prod_i \binom{n+\lambda_i-1}{\lambda_i}, ps_q(h_\lambda) = 1 / \prod_i \prod_{j=1}^{\lambda_i} (1-q^j). EXAMPLES:: sage: h = SymmetricFunctions(QQ).h() sage: x = h[2,1] sage: x.principal_specialization(3) q^6 + 2*q^5 + 4*q^4 + 4*q^3 + 4*q^2 + 2*q + 1 sage: x = 3*h[2] + 2*h[1] + 1 sage: x.principal_specialization(3, q=var("q")) 2*(q^3 - 1)/(q - 1) + 3*(q^4 - 1)*(q^3 - 1)/((q^2 - 1)*(q - 1)) + 1 TESTS:: sage: x = h.zero() sage: s = x.principal_specialization(3); s 0 """ from sage.combinat.q_analogues import q_binomial def get_variable(ring, name): try: ring(name) except TypeError: from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing return PolynomialRing(ring, name).gen() else: raise ValueError( "the variable %s is in the base ring, pass it explicitly" % name) if q is None: q = get_variable(self.base_ring(), 'q') if q == 1: if n == infinity: raise ValueError( "the stable principal specialization at q=1 is not defined" ) f = lambda partition: prod( binomial(n + part - 1, part) for part in partition) elif n == infinity: f = lambda partition: prod(1 / prod( (1 - q**i) for i in range(1, part + 1)) for part in partition) else: f = lambda partition: prod( q_binomial(n + part - 1, part, q=q) for part in partition) return self.parent()._apply_module_morphism(self, f, q.parent())
def AffineGeometryDesign(n, d, F, point_coordinates=True, check=True): r""" Return an affine geometry design. The affine geometry design `AG_d(n,q)` is the 2-design whose blocks are the `d`-vector subspaces in `\GF{q}^n`. It has parameters .. MATH:: v = q^n,\ k = q^d,\ \lambda = \binom{n-1}{d-1}_q where the `q`-binomial coefficient `\binom{m}{r}_q` is defined by .. MATH:: \binom{m}{r}_q = \frac{(q^m - 1)(q^{m-1} - 1) \cdots (q^{m-r+1}-1)} {(q^r-1)(q^{r-1}-1)\cdots (q-1)} .. SEEALSO:: :func:`ProjectiveGeometryDesign` INPUT: - ``n`` (integer) -- the Euclidean dimension. The number of points of the design is `v=|\GF{q}^n|`. - ``d`` (integer) -- the dimension of the (affine) subspaces of `\GF(q)^n` which make up the blocks. - ``F`` -- a finite field or a prime power. - ``point_coordinates`` -- (optional, default ``True``) whether we use coordinates in `\GF(q)^n` or plain integers for the points of the design. - ``check`` -- (optional, default ``True``) whether to check the output. EXAMPLES:: sage: BD = designs.AffineGeometryDesign(3, 1, GF(2)) sage: BD.is_t_design(return_parameters=True) (True, (2, 8, 2, 1)) sage: BD = designs.AffineGeometryDesign(3, 2, GF(4)) sage: BD.is_t_design(return_parameters=True) (True, (2, 64, 16, 5)) sage: BD = designs.AffineGeometryDesign(4, 2, GF(3)) sage: BD.is_t_design(return_parameters=True) (True, (2, 81, 9, 13)) With ``F`` an integer instead of a finite field:: sage: BD = designs.AffineGeometryDesign(3, 2, 4) sage: BD.is_t_design(return_parameters=True) (True, (2, 64, 16, 5)) Testing the option ``point_coordinates``:: sage: designs.AffineGeometryDesign(3, 1, GF(2), point_coordinates=True).blocks()[0] [(0, 0, 0), (0, 0, 1)] sage: designs.AffineGeometryDesign(3, 1, GF(2), point_coordinates=False).blocks()[0] [0, 1] """ try: q = int(F) except TypeError: q = F.cardinality() else: from sage.rings.finite_rings.finite_field_constructor import GF F = GF(q) n = int(n) d = int(d) from itertools import islice from sage.combinat.q_analogues import q_binomial from sage.matrix.echelon_matrix import reduced_echelon_matrix_iterator points = {p:i for i,p in enumerate(reduced_echelon_matrix_iterator(F,1,n+1,copy=True,set_immutable=True)) if p[0,0]} blocks = [] l1 = q_binomial(n+1, d+1, q) - q_binomial(n, d+1, q) l2 = q**d for m1 in islice(reduced_echelon_matrix_iterator(F,d+1,n+1,copy=False), l1): b = [] for m2 in islice(reduced_echelon_matrix_iterator(F,1,d+1,copy=False), l2): m = m2*m1 m.echelonize() m.set_immutable() b.append(points[m]) blocks.append(b) B = BlockDesign(len(points), blocks, name="AffineGeometryDesign", check=check) if point_coordinates: rd = {i:p[0][1:] for p,i in points.iteritems()} for v in rd.values(): v.set_immutable() B.relabel(rd) if check: if not B.is_t_design(t=2, v=q**n, k=q**d, l=q_binomial(n-1, d-1, q)): raise RuntimeError("error in AffineGeometryDesign " "construction. Please e-mail [email protected]") return B
def ProjectiveGeometryDesign(n, d, F, algorithm=None, point_coordinates=True, check=True): r""" Return a projective geometry design. The projective geometry design `PG_d(n,q)` has for points the lines of `\GF{q}^{n+1}`, and for blocks the `d+1`-dimensional subspaces of `\GF{q}^{n+1}`, each of which contains `\frac {|\GF{q}|^{d+1}-1} {|\GF{q}|-1}` lines. It is a `2`-design with parameters .. MATH:: v = \binom{n+1}{1}_q,\ k = \binom{d+1}{1}_q,\ \lambda = \binom{n-1}{d-1}_q where the `q`-binomial coefficient `\binom{m}{r}_q` is defined by .. MATH:: \binom{m}{r}_q = \frac{(q^m - 1)(q^{m-1} - 1) \cdots (q^{m-r+1}-1)} {(q^r-1)(q^{r-1}-1)\cdots (q-1)} .. SEEALSO:: :func:`AffineGeometryDesign` INPUT: - ``n`` is the projective dimension - ``d`` is the dimension of the subspaces which make up the blocks. - ``F`` -- a finite field or a prime power. - ``algorithm`` -- set to ``None`` by default, which results in using Sage's own implementation. In order to use GAP's implementation instead (i.e. its ``PGPointFlatBlockDesign`` function) set ``algorithm="gap"``. Note that GAP's "design" package must be available in this case, and that it can be installed with the ``gap_packages`` spkg. - ``point_coordinates`` -- ``True`` by default. Ignored and assumed to be ``False`` if ``algorithm="gap"``. If ``True``, the ground set is indexed by coordinates in `\GF{q}^{n+1}`. Otherwise the ground set is indexed by integers. - ``check`` -- (optional, default to ``True``) whether to check the output. EXAMPLES: The set of `d`-dimensional subspaces in a `n`-dimensional projective space forms `2`-designs (or balanced incomplete block designs):: sage: PG = designs.ProjectiveGeometryDesign(4, 2, GF(2)) sage: PG Incidence structure with 31 points and 155 blocks sage: PG.is_t_design(return_parameters=True) (True, (2, 31, 7, 7)) sage: PG = designs.ProjectiveGeometryDesign(3, 1, GF(4)) sage: PG.is_t_design(return_parameters=True) (True, (2, 85, 5, 1)) Check with ``F`` being a prime power:: sage: PG = designs.ProjectiveGeometryDesign(3, 2, 4) sage: PG Incidence structure with 85 points and 85 blocks Use coordinates:: sage: PG = designs.ProjectiveGeometryDesign(2, 1, GF(3)) sage: PG.blocks()[0] [(1, 0, 0), (1, 0, 1), (1, 0, 2), (0, 0, 1)] Use indexing by integers:: sage: PG = designs.ProjectiveGeometryDesign(2,1,GF(3),point_coordinates=0) sage: PG.blocks()[0] [0, 1, 2, 12] Check that the constructor using gap also works:: sage: BD = designs.ProjectiveGeometryDesign(2, 1, GF(2), algorithm="gap") # optional - gap_packages (design package) sage: BD.is_t_design(return_parameters=True) # optional - gap_packages (design package) (True, (2, 7, 3, 1)) """ try: q = int(F) except TypeError: q = F.cardinality() else: from sage.rings.finite_rings.finite_field_constructor import GF F = GF(q) if algorithm is None: from sage.matrix.echelon_matrix import reduced_echelon_matrix_iterator points = {p:i for i,p in enumerate(reduced_echelon_matrix_iterator(F,1,n+1,copy=True,set_immutable=True))} blocks = [] for m1 in reduced_echelon_matrix_iterator(F,d+1,n+1,copy=False): b = [] for m2 in reduced_echelon_matrix_iterator(F,1,d+1,copy=False): m = m2*m1 m.echelonize() m.set_immutable() b.append(points[m]) blocks.append(b) B = BlockDesign(len(points), blocks, name="ProjectiveGeometryDesign", check=check) if point_coordinates: B.relabel({i:p[0] for p,i in points.iteritems()}) elif algorithm == "gap": # Requires GAP's Design from sage.interfaces.gap import gap gap.load_package("design") gap.eval("D := PGPointFlatBlockDesign( %s, %s, %d )"%(n,F.order(),d)) v = eval(gap.eval("D.v")) gblcks = eval(gap.eval("D.blocks")) gB = [] for b in gblcks: gB.append([x-1 for x in b]) B = BlockDesign(v, gB, name="ProjectiveGeometryDesign", check=check) if check: from sage.combinat.q_analogues import q_binomial q = F.cardinality() if not B.is_t_design(t=2, v=q_binomial(n+1,1,q), k=q_binomial(d+1,1,q), l=q_binomial(n-1, d-1, q)): raise RuntimeError("error in ProjectiveGeometryDesign " "construction. Please e-mail [email protected]") return B
def hall_polynomial(nu, mu, la, q=None): r""" Return the (classical) Hall polynomial `P^{\nu}_{\mu,\lambda}` (where `\nu`, `\mu` and `\lambda` are the inputs ``nu``, ``mu`` and ``la``). Let `\nu,\mu,\lambda` be partitions. The Hall polynomial `P^{\nu}_{\mu,\lambda}(q)` (in the indeterminate `q`) is defined as follows: Specialize `q` to a prime power, and consider the category of `\GF{q}`-vector spaces with a distinguished nilpotent endomorphism. The morphisms in this category shall be the linear maps commuting with the distinguished endomorphisms. The *type* of an object in the category will be the Jordan type of the distinguished endomorphism; this is a partition. Now, if `N` is any fixed object of type `\nu` in this category, then the polynomial `P^{\nu}_{\mu,\lambda}(q)` specialized at the prime power `q` counts the number of subobjects `L` of `N` having type `\lambda` such that the quotient object `N / L` has type `\mu`. This determines the values of the polynomial `P^{\nu}_{\mu,\lambda}` at infinitely many points (namely, at all prime powers), and hence `P^{\nu}_{\mu,\lambda}` is uniquely determined. The degree of this polynomial is at most `n(\nu) - n(\lambda) - n(\mu)`, where `n(\kappa) = \sum_i (i-1) \kappa_i` for every partition `\kappa`. (If this is negative, then the polynomial is zero.) These are the structure coefficients of the :class:`(classical) Hall algebra <HallAlgebra>`. If `\lvert \nu \rvert \neq \lvert \mu \rvert + \lvert \lambda \rvert`, then we have `P^{\nu}_{\mu,\lambda} = 0`. More generally, if the Littlewood-Richardson coefficient `c^{\nu}_{\mu,\lambda}` vanishes, then `P^{\nu}_{\mu,\lambda} = 0`. The Hall polynomials satisfy the symmetry property `P^{\nu}_{\mu,\lambda} = P^{\nu}_{\lambda,\mu}`. ALGORITHM: If `\lambda = (1^r)` and `\lvert \nu \rvert = \lvert \mu \rvert + \lvert \lambda \rvert`, then we compute `P^{\nu}_{\mu,\lambda}` as follows (cf. Example 2.4 in [Sch2006]_): First, write `\nu = (1^{l_1}, 2^{l_2}, \ldots, n^{l_n})`, and define a sequence `r = r_0 \geq r_1 \geq \cdots \geq r_n` such that .. MATH:: \mu = \left( 1^{l_1 - r_0 + 2r_1 - r_2}, 2^{l_2 - r_1 + 2r_2 - r_3}, \ldots , (n-1)^{l_{n-1} - r_{n-2} + 2r_{n-1} - r_n}, n^{l_n - r_{n-1} + r_n} \right). Thus if `\mu = (1^{m_1}, \ldots, n^{m_n})`, we have the following system of equations: .. MATH:: \begin{aligned} m_1 & = l_1 - r + 2r_1 - r_2, \\ m_2 & = l_2 - r_1 + 2r_2 - r_3, \\ & \vdots , \\ m_{n-1} & = l_{n-1} - r_{n-2} + 2r_{n-1} - r_n, \\ m_n & = l_n - r_{n-1} + r_n \end{aligned} and solving for `r_i` and back substituting we obtain the equations: .. MATH:: \begin{aligned} r_n & = r_{n-1} + m_n - l_n, \\ r_{n-1} & = r_{n-2} + m_{n-1} - l_{n-1} + m_n - l_n, \\ & \vdots , \\ r_1 & = r + \sum_{k=1}^n (m_k - l_k), \end{aligned} or in general we have the recursive equation: .. MATH:: r_i = r_{i-1} + \sum_{k=i}^n (m_k - l_k). This, combined with the condition that `r_0 = r`, determines the `r_i` uniquely (recursively). Next we define .. MATH:: t = (r_{n-2} - r_{n-1})(l_n - r_{n-1}) + (r_{n-3} - r_{n-2})(l_{n-1} + l_n - r_{n-2}) + \cdots + (r_0 - r_1)(l_2 + \cdots + l_n - r_1), and with these notations we have .. MATH:: P^{\nu}_{\mu,(1^r)} = q^t \binom{l_n}{r_{n-1}}_q \binom{l_{n-1}}{r_{n-2} - r_{n-1}}_q \cdots \binom{l_1}{r_0 - r_1}_q. To compute `P^{\nu}_{\mu,\lambda}` in general, we compute the product `I_{\mu} I_{\lambda}` in the Hall algebra and return the coefficient of `I_{\nu}`. EXAMPLES:: sage: from sage.combinat.hall_polynomial import hall_polynomial sage: hall_polynomial([1,1],[1],[1]) q + 1 sage: hall_polynomial([2],[1],[1]) 1 sage: hall_polynomial([2,1],[2],[1]) q sage: hall_polynomial([2,2,1],[2,1],[1,1]) q^2 + q sage: hall_polynomial([2,2,2,1],[2,2,1],[1,1]) q^4 + q^3 + q^2 sage: hall_polynomial([3,2,2,1], [3,2], [2,1]) q^6 + q^5 sage: hall_polynomial([4,2,1,1], [3,1,1], [2,1]) 2*q^3 + q^2 - q - 1 sage: hall_polynomial([4,2], [2,1], [2,1], 0) 1 TESTS:: sage: hall_polynomial([3], [1], [1], 0) 0 """ if q is None: q = ZZ['q'].gen() R = q.parent() # Make sure they are partitions nu = Partition(nu) mu = Partition(mu) la = Partition(la) if sum(nu) != sum(mu) + sum(la): return R.zero() if all(x == 1 for x in la): r = [len(la)] # r will be [r_0, r_1, ..., r_n]. exp_nu = nu.to_exp() # exp_nu == [l_1, l_2, ..., l_n]. exp_mu = mu.to_exp() # exp_mu == [m_1, m_2, ..., m_n]. n = max(len(exp_nu), len(exp_mu)) for k in range(n): r.append(r[-1] + sum(exp_mu[k:]) - sum(exp_nu[k:])) # Now, r is [r_0, r_1, ..., r_n]. exp_nu += [0] * (n - len(exp_nu)) # Pad with 0's until it has length n # Note that all -1 for exp_nu is due to indexing t = sum((r[k - 2] - r[k - 1]) * (sum(exp_nu[k - 1:]) - r[k - 1]) for k in range(2, n + 1)) if t < 0: # This case needs short-circuiting, since otherwise q**-t # might throw an exception if q is non-invertible. return R.zero() return q**t * q_binomial(exp_nu[n-1], r[n-1], q) \ * prod([q_binomial(exp_nu[k-1], r[k-1] - r[k], q) for k in range(1, n)], R.one()) from sage.algebras.hall_algebra import HallAlgebra H = HallAlgebra(R, q) return (H[mu] * H[la]).coefficient(nu)
def hall_polynomial(nu, mu, la, q=None): r""" Return the (classical) Hall polynomial `P^{\nu}_{\mu,\lambda}` (where `\nu`, `\mu` and `\lambda` are the inputs ``nu``, ``mu`` and ``la``). Let `\nu,\mu,\lambda` be partitions. The Hall polynomial `P^{\nu}_{\mu,\lambda}(q)` (in the indeterminate `q`) is defined as follows: Specialize `q` to a prime power, and consider the category of `\GF{q}`-vector spaces with a distinguished nilpotent endomorphism. The morphisms in this category shall be the linear maps commuting with the distinguished endomorphisms. The *type* of an object in the category will be the Jordan type of the distinguished endomorphism; this is a partition. Now, if `N` is any fixed object of type `\nu` in this category, then the polynomial `P^{\nu}_{\mu,\lambda}(q)` specialized at the prime power `q` counts the number of subobjects `L` of `N` having type `\lambda` such that the quotient object `N / L` has type `\mu`. This determines the values of the polynomial `P^{\nu}_{\mu,\lambda}` at infinitely many points (namely, at all prime powers), and hence `P^{\nu}_{\mu,\lambda}` is uniquely determined. The degree of this polynomial is at most `n(\nu) - n(\lambda) - n(\mu)`, where `n(\kappa) = \sum_i (i-1) \kappa_i` for every partition `\kappa`. (If this is negative, then the polynomial is zero.) These are the structure coefficients of the :class:`(classical) Hall algebra <HallAlgebra>`. If `\lvert \nu \rvert \neq \lvert \mu \rvert + \lvert \lambda \rvert`, then we have `P^{\nu}_{\mu,\lambda} = 0`. More generally, if the Littlewood-Richardson coefficient `c^{\nu}_{\mu,\lambda}` vanishes, then `P^{\nu}_{\mu,\lambda} = 0`. The Hall polynomials satisfy the symmetry property `P^{\nu}_{\mu,\lambda} = P^{\nu}_{\lambda,\mu}`. ALGORITHM: If `\lambda = (1^r)` and `\lvert \nu \rvert = \lvert \mu \rvert + \lvert \lambda \rvert`, then we compute `P^{\nu}_{\mu,\lambda}` as follows (cf. Example 2.4 in [Schiffmann]_): First, write `\nu = (1^{l_1}, 2^{l_2}, \ldots, n^{l_n})`, and define a sequence `r = r_0 \geq r_1 \geq \cdots \geq r_n` such that .. MATH:: \mu = \left( 1^{l_1 - r_0 + 2r_1 - r_2}, 2^{l_2 - r_1 + 2r_2 - r_3}, \ldots , (n-1)^{l_{n-1} - r_{n-2} + 2r_{n-1} - r_n}, n^{l_n - r_{n-1} + r_n} \right). Thus if `\mu = (1^{m_1}, \ldots, n^{m_n})`, we have the following system of equations: .. MATH:: \begin{aligned} m_1 & = l_1 - r + 2r_1 - r_2, \\ m_2 & = l_2 - r_1 + 2r_2 - r_3, \\ & \vdots , \\ m_{n-1} & = l_{n-1} - r_{n-2} + 2r_{n-1} - r_n, \\ m_n & = l_n - r_{n-1} + r_n \end{aligned} and solving for `r_i` and back substituting we obtain the equations: .. MATH:: \begin{aligned} r_n & = r_{n-1} + m_n - l_n, \\ r_{n-1} & = r_{n-2} + m_{n-1} - l_{n-1} + m_n - l_n, \\ & \vdots , \\ r_1 & = r + \sum_{k=1}^n (m_k - l_k), \end{aligned} or in general we have the recursive equation: .. MATH:: r_i = r_{i-1} + \sum_{k=i}^n (m_k - l_k). This, combined with the condition that `r_0 = r`, determines the `r_i` uniquely (recursively). Next we define .. MATH:: t = (r_{n-2} - r_{n-1})(l_n - r_{n-1}) + (r_{n-3} - r_{n-2})(l_{n-1} + l_n - r_{n-2}) + \cdots + (r_0 - r_1)(l_2 + \cdots + l_n - r_1), and with these notations we have .. MATH:: P^{\nu}_{\mu,(1^r)} = q^t \binom{l_n}{r_{n-1}}_q \binom{l_{n-1}}{r_{n-2} - r_{n-1}}_q \cdots \binom{l_1}{r_0 - r_1}_q. To compute `P^{\nu}_{\mu,\lambda}` in general, we compute the product `I_{\mu} I_{\lambda}` in the Hall algebra and return the coefficient of `I_{\nu}`. EXAMPLES:: sage: from sage.combinat.hall_polynomial import hall_polynomial sage: hall_polynomial([1,1],[1],[1]) q + 1 sage: hall_polynomial([2],[1],[1]) 1 sage: hall_polynomial([2,1],[2],[1]) q sage: hall_polynomial([2,2,1],[2,1],[1,1]) q^2 + q sage: hall_polynomial([2,2,2,1],[2,2,1],[1,1]) q^4 + q^3 + q^2 sage: hall_polynomial([3,2,2,1], [3,2], [2,1]) q^6 + q^5 sage: hall_polynomial([4,2,1,1], [3,1,1], [2,1]) 2*q^3 + q^2 - q - 1 sage: hall_polynomial([4,2], [2,1], [2,1], 0) 1 """ if q is None: q = ZZ['q'].gen() R = q.parent() # Make sure they are partitions nu = Partition(nu) mu = Partition(mu) la = Partition(la) if sum(nu) != sum(mu) + sum(la): return R.zero() if all(x == 1 for x in la): r = [len(la)] # r will be [r_0, r_1, ..., r_n]. exp_nu = nu.to_exp() # exp_nu == [l_1, l_2, ..., l_n]. exp_mu = mu.to_exp() # exp_mu == [m_1, m_2, ..., m_n]. n = max(len(exp_nu), len(exp_mu)) for k in range(n): r.append(r[-1] + sum(exp_mu[k:]) - sum(exp_nu[k:])) # Now, r is [r_0, r_1, ..., r_n]. exp_nu += [0]*(n - len(exp_nu)) # Pad with 0's until it has length n # Note that all -1 for exp_nu is due to indexing t = sum((r[k-2] - r[k-1])*(sum(exp_nu[k-1:]) - r[k-1]) for k in range(2,n+1)) if t < 0: # This case needs short-circuiting, since otherwise q**-t # might throw an exception if q is non-invertible. return R.zero() return q**t * q_binomial(exp_nu[n-1], r[n-1], q) \ * prod([q_binomial(exp_nu[k-1], r[k-1] - r[k], q) for k in range(1, n)], R.one()) from sage.algebras.hall_algebra import HallAlgebra H = HallAlgebra(R, q) return (H[mu]*H[la]).coefficient(nu)