def verify_representation(self): r""" Verify the representation: tests that the images of the simple transpositions are involutions and tests that the braid relations hold. EXAMPLES:: sage: spc = SymmetricGroupRepresentation([1,1,1]) sage: spc.verify_representation() True sage: spc = SymmetricGroupRepresentation([4,2,1]) sage: spc.verify_representation() True """ n = self._partition.size() transpositions = [] for i in range(1,n): si = Permutation(range(1,i) + [i+1,i] + range(i+2,n+1)) transpositions.append(si) repn_matrices = map(self.representation_matrix, transpositions) for (i,si) in enumerate(repn_matrices): for (j,sj) in enumerate(repn_matrices): if i == j: if si*sj != si.parent().identity_matrix(): return False, "si si != 1 for i = %s" % (i,) elif abs(i-j) > 1: if si*sj != sj*si: return False, "si sj != sj si for (i,j) =(%s,%s)" % (i,j) else: if si*sj*si != sj*si*sj: return False, "si sj si != sj si sj for (i,j) = (%s,%s)" % (i,j) return True
def to_non_crossing_set_partition(self): r""" Returns the noncrossing set partition (on half as many elements) corresponding to the perfect matching if the perfect matching is noncrossing, and otherwise gives an error. OUTPUT: The realization of ``self`` as a noncrossing set partition. EXAMPLES:: sage: PerfectMatching([[1,3], [4,2]]).to_non_crossing_set_partition() Traceback (most recent call last): ... ValueError: matching must be non-crossing sage: PerfectMatching([[1,4], [3,2]]).to_non_crossing_set_partition() {{1, 2}} sage: PerfectMatching([]).to_non_crossing_set_partition() {} """ from sage.combinat.set_partition import SetPartition if not self.is_non_crossing(): raise ValueError("matching must be non-crossing") else: perm = self.to_permutation() perm2 = Permutation([(perm[2*i])/2 for i in range(len(perm)/2)]) return SetPartition(perm2.cycle_tuples())
def _element_constructor_(self, x): r""" Convert ``x`` into ``self``. EXAMPLES:: sage: R = algebras.FQSym(QQ).G() sage: x, y, z = R([1]), R([2,1]), R([3,2,1]) sage: R(x) G[1] sage: R(x+4*y) G[1] + 4*G[2, 1] sage: R(1) G[] sage: D = algebras.FQSym(ZZ).G() sage: X, Y, Z = D([1]), D([2,1]), D([3,2,1]) sage: R(X-Y).parent() Free Quasi-symmetric functions over Rational Field in the G basis sage: R([1, 3, 2]) G[1, 3, 2] sage: R(Permutation([1, 3, 2])) G[1, 3, 2] sage: R(SymmetricGroup(4)(Permutation([1,3,4,2]))) G[1, 3, 4, 2] sage: RF = algebras.FQSym(QQ).F() sage: R(RF([2, 3, 4, 1])) G[4, 1, 2, 3] sage: R(RF([3, 2, 4, 1])) G[4, 2, 1, 3] sage: DF = algebras.FQSym(ZZ).F() sage: D(DF([2, 3, 4, 1])) G[4, 1, 2, 3] sage: R(DF([2, 3, 4, 1])) G[4, 1, 2, 3] sage: RF(R[2, 3, 4, 1]) F[4, 1, 2, 3] """ if isinstance(x, (list, tuple, PermutationGroupElement)): x = Permutation(x) try: P = x.parent() if isinstance(P, FreeQuasisymmetricFunctions.G): if P is self: return x return self.element_class(self, x.monomial_coefficients()) except AttributeError: pass return CombinatorialFreeModule._element_constructor_(self, x)
def SymmetricGroupBruhatIntervalPoset(start, end): """ The poset of permutations with respect to Bruhat order. INPUT: - ``start`` - list permutation - ``end`` - list permutation (same n, of course) .. note:: Must have ``start`` <= ``end``. EXAMPLES: Any interval is rank symmetric if and only if it avoids these permutations:: sage: P1 = Posets.SymmetricGroupBruhatIntervalPoset([1,2,3,4], [3,4,1,2]) sage: P2 = Posets.SymmetricGroupBruhatIntervalPoset([1,2,3,4], [4,2,3,1]) sage: ranks1 = [P1.rank(v) for v in P1] sage: ranks2 = [P2.rank(v) for v in P2] sage: [ranks1.count(i) for i in uniq(ranks1)] [1, 3, 5, 4, 1] sage: [ranks2.count(i) for i in uniq(ranks2)] [1, 3, 5, 6, 4, 1] """ start = Permutation(start) end = Permutation(end) if len(start) != len(end): raise TypeError("Start (%s) and end (%s) must have same length."%(start, end)) if not start.bruhat_lequal(end): raise TypeError("Must have start (%s) <= end (%s) in Bruhat order."%(start, end)) unseen = [start] nodes = {} while len(unseen) > 0: perm = unseen.pop(0) nodes[perm] = [succ_perm for succ_perm in perm.bruhat_succ() if succ_perm.bruhat_lequal(end)] for succ_perm in nodes[perm]: if succ_perm not in nodes: unseen.append(succ_perm) return Poset(nodes)
def scalar_product_matrix(self, permutation=None): r""" Return the scalar product matrix corresponding to ``permutation``. The entries are given by the scalar products of ``u`` and ``permutation.action(v)``, where ``u`` is a vertex in the underlying Yang-Baxter graph and ``v`` is a vertex in the dual graph. EXAMPLES:: sage: spc = SymmetricGroupRepresentation([3,1], 'specht') sage: spc.scalar_product_matrix() [ 1 0 0] [ 0 -1 0] [ 0 0 1] """ if permutation is None: permutation = Permutation(range(1,1+self._partition.size())) Q = matrix(QQ, len(self._yang_baxter_graph)) for (i,v) in enumerate(self._dual_vertices): for (j,u) in enumerate(self._yang_baxter_graph): Q[i,j] = self.scalar_product(tuple(permutation.action(v)), u) return Q
def __call__(self, x, orientation=False): """ Input is a simplex of the domain. Output is the image simplex. If the optional argument ``orientation`` is ``True``, then this returns a pair ``(image simplex, oriented)`` where ``oriented`` is 1 or `-1` depending on whether the map preserves or reverses the orientation of the image simplex. EXAMPLES:: sage: S = simplicial_complexes.Sphere(2) sage: T = simplicial_complexes.Sphere(3) sage: S Minimal triangulation of the 2-sphere sage: T Minimal triangulation of the 3-sphere sage: f = {0:0,1:1,2:2,3:3} sage: H = Hom(S,T) sage: x = H(f) sage: from sage.topology.simplicial_complex import Simplex sage: x(Simplex([0,2,3])) (0, 2, 3) An orientation-reversing example:: sage: X = SimplicialComplex([[0,1]], is_mutable=False) sage: g = Hom(X,X)({0:1, 1:0}) sage: g(Simplex([0,1])) (0, 1) sage: g(Simplex([0,1]), orientation=True) ((0, 1), -1) """ dim = self.domain().dimension() if not isinstance(x, Simplex) or x.dimension( ) > dim or x not in self.domain().faces()[x.dimension()]: raise ValueError("x must be a simplex of the source of f") tup = x.tuple() fx = [] for j in tup: fx.append(self._vertex_dictionary[j]) if orientation: if len(set(fx)) == len(tup): oriented = Permutation(convert_perm(fx)).signature() else: oriented = 1 return (Simplex(set(fx)), oriented) else: return Simplex(set(fx))
def sort_subscript(subscript): """ A subscript is a range of integers. This function sorts a subscript in the sense of arranging it in ascending order. The return values are the sign of the subscript and the sorted subscript, where the sign is defined as follows: #. sign == 0 if two or more entries in the subscript were equal. #. sign == +1, -1 if a positive (resp. negative) permutation was used to sort the subscript. INPUT: - ``subscript`` -- a subscript, i.e. a range of not necessarily distinct integers OUTPUT: - Sign of the permutation used to arrange the subscript, where 0 means that the original subscript had two or more entries that were the same - Sorted subscript. EXAMPLES:: sage: from sage.tensor.differential_form_element import sort_subscript sage: sort_subscript((1, 3, 2)) (-1, (1, 2, 3)) sage: sort_subscript((1, 3)) (1, (1, 3)) sage: sort_subscript((4, 2, 7, 9, 8)) (1, (2, 4, 7, 8, 9)) """ if len(subscript) == 0: return 1, () sub_list = list(subscript) sub_list.sort() offsets = [subscript.index(x) + 1 for x in sub_list] # Check that offsets is a true permutation of 1..n n = len(offsets) if sum(offsets) != n * (n + 1) / 2: sign = 0 else: sign = Permutation(offsets).signature() return sign, tuple(sub_list)
def __init__(self, l, pi=None): """ EXAMPLES:: sage: a = AugmentedLatticeDiagramFilling([[1,6],[2],[3,4,2],[],[],[5,5]]) sage: a == loads(dumps(a)) True sage: pi = Permutation([2,3,1]).to_permutation_group_element() sage: a = AugmentedLatticeDiagramFilling([[1,6],[2],[3,4,2],[],[],[5,5]],pi) sage: a == loads(dumps(a)) True """ if pi is None: pi = Permutation([1]).to_permutation_group_element() self._list = [[pi(i + 1)] + li for i, li in enumerate(l)]
def permutation_group_element(self): """ Returns self as a permutation group element. EXAMPLES:: sage: p = PermutationGroupElement((2,3,4)) sage: P = species.PermutationSpecies() sage: a = P.structures(["a", "b", "c", "d"]).random_element(); a ['a', 'c', 'b', 'd'] sage: a.permutation_group_element() (2,3) """ from sage.groups.all import PermutationGroupElement return Permutation(self._list).to_permutation_group_element()
def verify_representation(self): r""" Verify the representation: tests that the images of the simple transpositions are involutions and tests that the braid relations hold. EXAMPLES:: sage: spc = SymmetricGroupRepresentation([1,1,1]) sage: spc.verify_representation() True sage: spc = SymmetricGroupRepresentation([4,2,1]) sage: spc.verify_representation() True """ n = self._partition.size() transpositions = [] for i in range(1, n): si = Permutation( list(range(1, i)) + [i + 1, i] + list(range(i + 2, n + 1))) transpositions.append(si) repn_matrices = [self.representation_matrix(_) for _ in transpositions] for (i, si) in enumerate(repn_matrices): for (j, sj) in enumerate(repn_matrices): if i == j: if si * sj != si.parent().identity_matrix(): return False, "si si != 1 for i = %s" % (i, ) elif abs(i - j) > 1: if si * sj != sj * si: return False, "si sj != sj si for (i,j) =(%s,%s)" % (i, j) else: if si * sj * si != sj * si * sj: return False, "si sj si != sj si sj for (i,j) = (%s,%s)" % ( i, j) return True
def from_symmetric_group_algebra(self, x): """ Return the element of `FQSym` corresponding to the element `x` of a symmetric group algebra. EXAMPLES:: sage: A = algebras.FQSym(QQ).F() sage: SGA4 = SymmetricGroupAlgebra(QQ, 4) sage: x = SGA4([1,3,2,4]) + 5/2 * SGA4([1,2,4,3]) sage: A.from_symmetric_group_algebra(x) 5/2*F[1, 2, 4, 3] + F[1, 3, 2, 4] sage: A.from_symmetric_group_algebra(SGA4.zero()) 0 """ return self._from_dict({Permutation(key): c for (key, c) in x})
def canonical_label(self): """ EXAMPLES:: sage: P = species.PermutationSpecies() sage: S = P.structures(["a", "b", "c"]) sage: [s.canonical_label() for s in S] [['a', 'b', 'c'], ['b', 'a', 'c'], ['b', 'a', 'c'], ['b', 'c', 'a'], ['b', 'c', 'a'], ['b', 'a', 'c']] """ P = self.parent() return P._canonical_rep_from_partition(self.__class__, self._labels, Permutation(self._list).cycle_type())
def to_permutation(self): """ Return a permutation with the entries of ``self`` obtained by reading ``self`` row by row, from the bottommost to the topmost row, with each row being read from left to right, in English convention. See :meth:`to_word_by_row()`. EXAMPLES:: sage: SkewTableau([[None,2],[3,4],[None],[1]]).to_permutation() [1, 3, 4, 2] sage: SkewTableau([[None]]).to_permutation() [] """ from sage.combinat.permutation import Permutation return Permutation(self.to_word())
def diagonal_reading_word(self): r""" Return a diagonal word of the labelled Dyck path corresponding to parking function (see [Hag08]_ p. 75). INPUT: - ``self`` -- parking function word OUTPUT: - returns a word, read diagonally from NE to SW of the pretty print of the labelled Dyck path that corresponds to ``self`` and the same size as ``self`` EXAMPLES:: sage: PF = ParkingFunction([6, 1, 5, 2, 2, 1, 5]) sage: PF.diagonal_reading_word() [5, 1, 7, 4, 6, 3, 2] :: sage: ParkingFunction([1, 1, 1]).diagonal_reading_word() [3, 2, 1] sage: ParkingFunction([1, 2, 3]).diagonal_reading_word() [3, 2, 1] sage: ParkingFunction([1, 1, 3, 4]).diagonal_reading_word() [2, 4, 3, 1] :: sage: ParkingFunction([1, 1, 1]).diagonal_word() [3, 2, 1] sage: ParkingFunction([1, 2, 3]).diagonal_word() [3, 2, 1] sage: ParkingFunction([1, 4, 3, 1]).diagonal_word() [4, 2, 3, 1] """ L = self.to_labelling_permutation() D = self.to_area_sequence() m = max(D) return Permutation([ L[-j - 1] for i in range(m + 1) for j in range(len(L)) if D[-j - 1] == m - i ])
def __call__(self, u): r""" Return the object obtained from swapping the items in positions ``i`` and ``i+1`` of ``u``. EXAMPLES:: sage: from sage.combinat.yang_baxter_graph import SwapOperator sage: s3 = SwapOperator(3) sage: s3((1,2,3,4,5)) (1, 2, 3, 5, 4) sage: s3([1,2,3,4,5]) [1, 2, 3, 5, 4] """ i = self._position if isinstance(u, Permutation): return Permutation(u[:i] + u[i:i+2][::-1] + u[i+2:]) return type(u)(u[:i] + u[i:i+2][::-1] + u[i+2:])
def sign(self): r""" EXAMPLES:: sage: from EkEkstar import kFace sage: kFace((0,0,0,0,0),(1,2,3,4,5)).sign() 1 sage: kFace((0,0,0,0,0),(1,2,3,4,4)).sign() 0 sage: kFace((0,0,0,0,0),(1,2,3,5,4)).sign() -1 """ sorted_type = self.sorted_type() if all(sorted_type[i] < sorted_type[i + 1] for i in range(len(sorted_type) - 1)): p = argsort(self._type) + 1 return Permutation(p).sign() else: return 0
def _element_constructor_(self, x): """ Coerce x into self. EXAMPLES:: sage: X = SchubertPolynomialRing(QQ) sage: X._element_constructor_([2,1,3]) X[2, 1] sage: X._element_constructor_(Permutation([2,1,3])) X[2, 1] sage: R.<x1, x2, x3> = QQ[] sage: X(x1^2*x2) X[3, 2, 1] TESTS: We check that :trac:`12924` is fixed:: sage: X = SchubertPolynomialRing(QQ) sage: X._element_constructor_([1,2,1]) Traceback (most recent call last): ... ValueError: The input [1, 2, 1] is not a valid permutation """ if isinstance(x, list): #checking the input to avoid symmetrica crashing Sage, see trac 12924 if not x in Permutations(): raise ValueError("The input %s is not a valid permutation" % (x)) perm = Permutation(x).remove_extra_fixed_points() return self._from_dict({perm: self.base_ring().one()}) elif isinstance(x, Permutation): if not list(x) in Permutations(): raise ValueError("The input %s is not a valid permutation" % (x)) perm = x.remove_extra_fixed_points() return self._from_dict({perm: self.base_ring().one()}) elif is_MPolynomial(x): return symmetrica.t_POLYNOM_SCHUBERT(x) else: raise TypeError
def __getitem__(self, p): """ Return the basis element indexed by ``p``. INPUT: - ``p`` -- a permutation EXAMPLES:: sage: R = algebras.FQSym(QQ).F() sage: R[[1, 3, 2]] F[1, 3, 2] sage: R[Permutation([1, 3, 2])] F[1, 3, 2] sage: R[SymmetricGroup(4)(Permutation([1,3,4,2]))] F[1, 3, 4, 2] """ return self.monomial(Permutation(p))
def to_permutation(self): r""" Returns the permutation corresponding to the perfect matching. OUTPUT: The realization of ``self`` as a permutation. EXAMPLES:: sage: PerfectMatching([[1,3], [4,2]]).to_permutation() [3, 4, 1, 2] sage: PerfectMatching([[1,4], [3,2]]).to_permutation() [4, 3, 2, 1] sage: PerfectMatching([]).to_permutation() [] """ from sage.combinat.permutation import Permutation return Permutation(self.__dict__['value'])
def _an_element_(self): r""" Returns an element of ``self``. EXAMPLES:: sage: F.<a> = GF(4) sage: SemimonomialTransformationGroup(F, 3).an_element() # indirect doctest ((a, 1, 1); (1,3,2), Ring endomorphism of Finite Field in a of size 2^2 Defn: a |--> a + 1) """ R = self.base_ring() v = [R.primitive_element()] + [R.one()] * (self.degree() - 1) p = Permutation([self.degree()] + [i for i in range(1, self.degree())]) if not R.is_prime_field(): f = R.hom([R.gen()**R.characteristic()]) else: f = R.Hom(R).identity() return self(0, v, p, f)
def to_permutation(self): """ Return the permutation corresponding to ``self``. EXAMPLES:: sage: D = Derangements(4) sage: p = D([4,3,2,1]).to_permutation(); p [4, 3, 2, 1] sage: type(p) <class 'sage.combinat.permutation.StandardPermutations_all_with_category.element_class'> sage: D = Derangements([1, 3, 3, 4]) sage: D[0].to_permutation() Traceback (most recent call last): ... ValueError: Can only convert to a permutation for derangements of [1, 2, ..., n] """ if self.parent()._set != tuple(range(1, len(self)+1)): raise ValueError("Can only convert to a permutation for derangements of [1, 2, ..., n]") return Permutation(list(self))
def cars_permutation( self): # indices are parking spaces, entries are car labels r""" Return the sequence of cars that take parking spots 1 through `n` and corresponding to the parking function. For example, ``cars_permutation(PF) = [2, 4, 5, 6, 3, 1, 7]`` means that car 2 takes spots 1, car 4 takes spot 2, ..., car 1 takes spot 6 and car 7 takes spot 7. INPUT: - ``self`` -- parking function word OUTPUT: - the permutation of cars corresponding to the parking function and which is the same size as parking function EXAMPLES:: sage: PF = ParkingFunction([6, 1, 5, 2, 2, 1, 5]) sage: PF.cars_permutation() [2, 4, 5, 6, 3, 1, 7] :: sage: ParkingFunction([3,1,1,4]).cars_permutation() [2, 3, 1, 4] sage: ParkingFunction([4,1,1,1]).cars_permutation() [2, 3, 4, 1] sage: ParkingFunction([2,1,4,1]).cars_permutation() [2, 1, 4, 3] """ out = {} for i in range(len(self)): j = 0 while self[i] + j in out: j += 1 out[self[i] + j] = i return Permutation([out[i + 1] + 1 for i in range(len(self))])
def scalar_product(self, u, v): r""" Return ``0`` if ``u+v`` is not a permutation, and the signature of the permutation otherwise. This is the scalar product of a vertex ``u`` of the underlying Yang-Baxter graph with the vertex ``v`` in the 'dual' Yang-Baxter graph. EXAMPLES:: sage: spc = SymmetricGroupRepresentation([3,2], 'specht') sage: spc.scalar_product((1,0,2,1,0),(0,3,0,3,0)) -1 sage: spc.scalar_product((1,0,2,1,0),(3,0,0,3,0)) 0 """ uv = [a + v[i] + 1 for (i, a) in enumerate(u)] if uv not in Permutations(): return 0 else: return Permutation(uv).signature()
def gens(self): r""" Return a tuple of generators of ``self``. EXAMPLES:: sage: F.<a> = GF(4) sage: SemimonomialTransformationGroup(F, 3).gens() [((a, 1, 1); (), Ring endomorphism of Finite Field in a of size 2^2 Defn: a |--> a), ((1, 1, 1); (1,2,3), Ring endomorphism of Finite Field in a of size 2^2 Defn: a |--> a), ((1, 1, 1); (1,2), Ring endomorphism of Finite Field in a of size 2^2 Defn: a |--> a), ((1, 1, 1); (), Ring endomorphism of Finite Field in a of size 2^2 Defn: a |--> a + 1)] """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup R = self.base_ring() l = [self(v=([R.primitive_element()] + [R.one()]*(self.degree() - 1)))] for g in SymmetricGroup(self.degree()).gens(): l.append(self(perm=Permutation(g))) if R.is_field() and not R.is_prime_field(): l.append(self(autom=R.hom([R.primitive_element()**R.characteristic()]))) return l
def __mul__(self, other): """ Product of two symmetric ideals. Since the generators of a symmetric ideal are subject to a permutation action, they in fact stand for a set of polynomials. Hence, when multiplying two symmetric ideals, it does not suffice to simply multiply the respective generators. EXAMPLES:: sage: X.<x> = InfinitePolynomialRing(QQ) sage: I=X*(x[1]) sage: I*I # indirect doctest Symmetric Ideal (x_1^2, x_2*x_1) of Infinite polynomial ring in x over Rational Field """ # determine maximal generator index PARENT = self.ring() if (not isinstance(other, self.__class__)) or self.ring() != other.ring(): if hasattr(other, 'gens'): other = SymmetricIdeal(PARENT, other.gens(), coerce=True) other = other.symmetrisation() sN = max([X.max_index() for X in self.gens()] + [1]) oN = max([X.max_index() for X in other.gens()] + [1]) from sage.combinat.permutation import Permutation P = Permutation(list(range(2, sN + oN + 1)) + [1]) oGen = list(other.gens()) SymL = oGen for i in range(sN): oGen = [X**P for X in oGen] SymL = SymL + oGen # Now, SymL contains all necessary permutations of the second factor OUT = [] for X in self.gens(): OUT.extend([X * Y for Y in SymL]) return SymmetricIdeal(PARENT, OUT, coerce=False).interreduction()
def to_permutation(self): """ Return the corresponding permutation if ``self`` is a permutation matrix. EXAMPLES:: sage: A = AlternatingSignMatrices(3) sage: asm = A([[0,1,0],[1,0,0],[0,0,1]]) sage: p = asm.to_permutation(); p [2, 1, 3] sage: parent(p) Standard permutations sage: asm = A([[0,1,0],[1,-1,1],[0,1,0]]) sage: asm.to_permutation() Traceback (most recent call last): ... ValueError: Not a permutation matrix """ if not self.is_permutation(): raise ValueError('Not a permutation matrix') asm_matrix = self.to_matrix() return Permutation([ j+1 for (i,j) in asm_matrix.nonzero_positions() ])
def galois_action_on_embeddings(G_K): K = G_K.number_field() Kgal = G_K.splitting_field() embeddings = K.embeddings(Kgal) # first a shortcut in the case where G_K is normal in S_d since then it doesn't # matter for our application since we only care about the image # of the galois group in S_d d = K.absolute_degree() G_K_roots = TransitiveGroup(d, G_K.transitive_number()) if G_K_roots.is_normal(SymmetricGroup(d)): id_G_K = G_K.Hom(G_K).identity() return G_K, id_G_K, id_G_K, Kgal, embeddings # now for the actual computation permutations = [] for g in G_K.gens(): phi = g.as_hom() g_perm = Permutation( [embeddings.index(phi * emb) + 1 for emb in embeddings]).inverse() permutations.append(g_perm) G_K_emb = PermutationGroup(permutations, canonicalize=False) to_emb = G_K.hom(G_K_emb.gens()) from_emb = G_K_emb.hom(G_K.gens()) return G_K_emb, to_emb, from_emb, Kgal, embeddings
def braid_from_piecewise(strands): r""" Compute the braid corresponding to the piecewise linear curves strands. INPUT: - ``strands`` -- a list of lists of tuples ``(t, c)``, where ``t`` is a number bewteen 0 and 1, and ``c`` is a complex number OUTPUT: The braid formed by the piecewise linear strands. EXAMPLES:: sage: from sage.schemes.curves.zariski_vankampen import braid_from_piecewise # optional - sirocco sage: paths = [[(0, I), (0.2, -1 - 0.5*I), (0.8, -1), (1, -I)], ....: [(0, -1), (0.5, -I), (1, 1)], ....: [(0, 1), (0.5, 1 + I), (1, I)]] sage: braid_from_piecewise(paths) # optional - sirocco s0*s1 """ L = strands i = min(val[1][0] for val in L) totalpoints = [[[a[0][1].real(), a[0][1].imag()]] for a in L] indices = [1 for a in range(len(L))] while i < 1: for j, val in enumerate(L): if val[indices[j]][0] > i: xaux = val[indices[j] - 1][1] yaux = val[indices[j]][1] aaux = val[indices[j] - 1][0] baux = val[indices[j]][0] interpola = xaux + (yaux - xaux)*(i - aaux)/(baux - aaux) totalpoints[j].append([interpola.real(), interpola.imag()]) else: totalpoints[j].append([val[indices[j]][1].real(), val[indices[j]][1].imag()]) indices[j] = indices[j] + 1 i = min(val[indices[k]][0] for k,val in enumerate(L)) for j, val in enumerate(L): totalpoints[j].append([val[-1][1].real(), val[-1][1].imag()]) braid = [] G = SymmetricGroup(len(totalpoints)) def sgn(x, y): # Opposite sign of cmp if x < y: return 1 if x > y: return -1 return 0 for i in range(len(totalpoints[0]) - 1): l1 = [totalpoints[j][i] for j in range(len(L))] l2 = [totalpoints[j][i+1] for j in range(len(L))] M = [[l1[s], l2[s]] for s in range(len(l1))] M.sort() l1 = [a[0] for a in M] l2 = [a[1] for a in M] cruces = [] for j in range(len(l2)): for k in range(j): if l2[j] < l2[k]: t = (l1[j][0] - l1[k][0])/(l2[k][0] - l1[k][0] + l1[j][0] - l2[j][0]) s = sgn(l1[k][1]*(1 - t) + t*l2[k][1], l1[j][1]*(1 - t) + t*l2[j][1]) cruces.append([t, k, j, s]) if cruces: cruces.sort() P = G(Permutation([])) while cruces: # we select the crosses in the same t crucesl = [c for c in cruces if c[0]==cruces[0][0]] crossesl = [(P(c[2]+1) - P(c[1]+1),c[1],c[2],c[3]) for c in crucesl] cruces = cruces[len(crucesl):] while crossesl: crossesl.sort() c = crossesl.pop(0) braid.append(c[3]*min(map(P, [c[1] + 1, c[2] + 1]))) P = G(Permutation([(c[1] + 1, c[2] + 1)]))*P crossesl = [(P(c[2]+1) - P(c[1]+1),c[1],c[2],c[3]) for c in crossesl] B = BraidGroup(len(L)) return B(braid)
def __classcall_private__(cls, parts): """ Create a perfect matching from ``parts`` with the appropriate parent. This function tries to recognize the input (it can be either a list or a tuple of pairs, or a fix-point free involution given as a list or as a permutation), constructs the parent (enumerated set of PerfectMatchings of the ground set) and calls the __init__ function to construct our object. EXAMPLES:: sage: m = PerfectMatching([('a','e'),('b','c'),('d','f')]);m [('a', 'e'), ('b', 'c'), ('d', 'f')] sage: isinstance(m, PerfectMatching) True sage: n = PerfectMatching([3, 8, 1, 7, 6, 5, 4, 2]);n [(1, 3), (2, 8), (4, 7), (5, 6)] sage: n.parent() Perfect matchings of {1, 2, 3, 4, 5, 6, 7, 8} sage: PerfectMatching([(1, 4), (2, 3), (5, 6)]).is_noncrossing() True The function checks that the given list or permutation is a valid perfect matching (i.e. a list of pairs with pairwise disjoint elements or a fix point free involution) and raises a ``ValueError`` otherwise:: sage: PerfectMatching([(1, 2, 3), (4, 5)]) Traceback (most recent call last): ... ValueError: [(1, 2, 3), (4, 5)] is not an element of Perfect matchings of {1, 2, 3, 4, 5} TESTS:: sage: m = PerfectMatching([('a','e'),('b','c'),('d','f')]) sage: TestSuite(m).run() sage: m = PerfectMatching([]) sage: TestSuite(m).run() sage: PerfectMatching(6) Traceback (most recent call last): ... TypeError: 'sage.rings.integer.Integer' object is not iterable sage: PerfectMatching([(1,2,3)]) Traceback (most recent call last): ... ValueError: [(1, 2, 3)] is not an element of Perfect matchings of {1, 2, 3} sage: PerfectMatching([(1,1)]) Traceback (most recent call last): ... ValueError: [(1)] is not an element of Perfect matchings of {1} sage: PerfectMatching(Permutation([4,2,1,3])) Traceback (most recent call last): ... ValueError: permutation p (= [4, 2, 1, 3]) is not a fixed point free involution """ if ((isinstance(parts, list) and all((isinstance(x, (int, Integer)) for x in parts))) or isinstance(parts, Permutation)): s = Permutation(parts) if not all(e == 2 for e in s.cycle_type()): raise ValueError("permutation p (= {}) is not a " "fixed point free involution".format(s)) parts = s.to_cycles() base_set = frozenset(e for p in parts for e in p) P = PerfectMatchings(base_set) return P(parts)
def verschiebung(self, n): r""" Return the image of the symmetric function ``self`` under the `n`-th Verschiebung operator. The `n`-th Verschiebung operator `\mathbf{V}_n` is defined to be the unique algebra endomorphism `V` of the ring of symmetric functions that satisfies `V(h_r) = h_{r/n}` for every positive integer `r` divisible by `n`, and satisfies `V(h_r) = 0` for every positive integer `r` not divisible by `n`. This operator `\mathbf{V}_n` is a Hopf algebra endomorphism. For every nonnegative integer `r` with `n \mid r`, it satisfies .. MATH:: \mathbf{V}_n(h_r) = h_{r/n}, \quad \mathbf{V}_n(p_r) = n p_{r/n}, \quad \mathbf{V}_n(e_r) = (-1)^{r - r/n} e_{r/n} (where `h` is the complete homogeneous basis, `p` is the powersum basis, and `e` is the elementary basis). For every nonnegative integer `r` with `n \nmid r`, it satisfes .. MATH:: \mathbf{V}_n(h_r) = \mathbf{V}_n(p_r) = \mathbf{V}_n(e_r) = 0. The `n`-th Verschiebung operator is also called the `n`-th Verschiebung endomorphism. Its name derives from the Verschiebung (German for "shift") endomorphism of the Witt vectors. The `n`-th Verschiebung operator is adjoint to the `n`-th Frobenius operator (see :meth:`frobenius` for its definition) with respect to the Hall scalar product (:meth:`scalar`). The action of the `n`-th Verschiebung operator on the Schur basis can also be computed explicitly. The following (probably clumsier than necessary) description can be obtained by solving exercise 7.61 in Stanley's [STA]_. Let `\lambda` be a partition. Let `n` be a positive integer. If the `n`-core of `\lambda` is nonempty, then `\mathbf{V}_n(s_\lambda) = 0`. Otherwise, the following method computes `\mathbf{V}_n(s_\lambda)`: Write the partition `\lambda` in the form `(\lambda_1, \lambda_2, \ldots, \lambda_{ns})` for some nonnegative integer `s`. (If `n` does not divide the length of `\lambda`, then this is achieved by adding trailing zeroes to `\lambda`.) Set `\beta_i = \lambda_i + ns - i` for every `s \in \{ 1, 2, \ldots, ns \}`. Then, `(\beta_1, \beta_2, \ldots, \beta_{ns})` is a strictly decreasing sequence of nonnegative integers. Stably sort the list `(1, 2, \ldots, ns)` in order of (weakly) increasing remainder of `-1 - \beta_i` modulo `n`. Let `\xi` be the sign of the permutation that is used for this sorting. Let `\psi` be the sign of the permutation that is used to stably sort the list `(1, 2, \ldots, ns)` in order of (weakly) increasing remainder of `i - 1` modulo `n`. (Notice that `\psi = (-1)^{n(n-1)s(s-1)/4}`.) Then, `\mathbf{V}_n(s_\lambda) = \xi \psi \prod_{i = 0}^{n - 1} s_{\lambda^{(i)}}`, where `(\lambda^{(0)}, \lambda^{(1)}, \ldots, \lambda^{(n - 1)})` is the `n`-quotient of `\lambda`. INPUT: - ``n`` -- a positive integer OUTPUT: The result of applying the `n`-th Verschiebung operator (on the ring of symmetric functions) to ``self``. EXAMPLES:: sage: Sym = SymmetricFunctions(ZZ) sage: s = Sym.s() sage: s[5].verschiebung(2) 0 sage: s[6].verschiebung(6) s[1] sage: s[6,3].verschiebung(3) s[2, 1] + s[3] sage: s[6,3,1].verschiebung(2) -s[3, 2] sage: s[3,2,1].verschiebung(1) s[3, 2, 1] sage: s([]).verschiebung(1) s[] sage: s([]).verschiebung(4) s[] TESTS: Let us check that this method on the powersum basis gives the same result as the implementation in sfa.py on the monomial basis:: sage: Sym = SymmetricFunctions(QQ) sage: s = Sym.s(); h = Sym.h() sage: all( h(s(lam)).verschiebung(3) == h(s(lam).verschiebung(3)) ....: for lam in Partitions(6) ) True sage: all( s(h(lam)).verschiebung(2) == s(h(lam).verschiebung(2)) ....: for lam in Partitions(4) ) True sage: all( s(h(lam)).verschiebung(5) == s(h(lam).verschiebung(5)) ....: for lam in Partitions(10) ) True sage: all( s(h(lam)).verschiebung(2) == s(h(lam).verschiebung(2)) ....: for lam in Partitions(8) ) True sage: all( s(h(lam)).verschiebung(3) == s(h(lam).verschiebung(3)) ....: for lam in Partitions(12) ) True sage: all( s(h(lam)).verschiebung(3) == s(h(lam).verschiebung(3)) ....: for lam in Partitions(9) ) True """ # Extra hack for the n == 1 case, since lam.quotient(1) # (for lam being a partition) returns a partition rather than # a partition tuple. if n == 1: return self parent = self.parent() s_coords_of_self = self.monomial_coefficients().items() result = parent.zero() from sage.combinat.permutation import Permutation for (lam, coeff) in s_coords_of_self: if len(lam.core(n)) == 0: quotient = lam.quotient(n) quotient_prod = parent.prod([parent(part) for part in quotient]) # Now, compute the sign of quotient_prod in the # n-th Verschiebung of lam. len_lam = len(lam) ns = len_lam + ((- len_lam) % n) s = ns // n # This is actually ns / n, as we have n | ns. beta_list = lam.beta_numbers(ns) zipped_beta_list = zip(beta_list, range(1, ns + 1)) zipped_beta_list.sort(key = lambda a: (- 1 - a[0]) % n) # We are using the fact that sort is a stable sort. perm_list = [a[1] for a in zipped_beta_list] if Permutation(perm_list).sign() == 1: minus_sign = False else: minus_sign = True if (n * s * (n-1) * (s-1)) % 8 == 4: minus_sign = not minus_sign if minus_sign: result -= coeff * quotient_prod else: result += coeff * quotient_prod return result
def divided_difference(self, i, algorithm="sage"): r""" Return the ``i``-th divided difference operator, applied to ``self``. Here, ``i`` can be either a permutation or a positive integer. INPUT: - ``i`` -- permutation or positive integer - ``algorithm`` -- (default: ``'sage'``) either ``'sage'`` or ``'symmetrica'``; this determines which software is called for the computation OUTPUT: The result of applying the ``i``-th divided difference operator to ``self``. If `i` is a positive integer, then the `i`-th divided difference operator `\delta_i` is the linear operator sending each polynomial `f = f(x_1, x_2, \ldots, x_n)` (in `n \geq i+1` variables) to the polynomial .. MATH:: \frac{f - f_i}{x_i - x_{i+1}}, \qquad \text{ where } f_i = f(x_1, x_2, ..., x_{i-1}, x_{i+1}, x_i, x_{i+1}, ..., x_n) . If `\sigma` is a permutation in the `n`-th symmetric group, then the `\sigma`-th divided difference operator `\delta_\sigma` is the composition `\delta_{i_1} \delta_{i_2} \cdots \delta_{i_k}`, where `\sigma = s_{i_1} \circ s_{i_2} \circ \cdots \circ s_{i_k}` is any reduced expression for `\sigma` (the precise choice of reduced expression is immaterial). .. NOTE:: The :meth:`expand` method results in a polynomial in `n` variables named ``x0, x1, ..., x(n-1)`` rather than `x_1, x_2, \ldots, x_n`. The variable named ``xi`` corresponds to `x_{i+1}`. Thus, ``self.divided_difference(i)`` involves the variables ``x(i-1)`` and ``xi`` getting switched (in the numerator). EXAMPLES:: sage: X = SchubertPolynomialRing(ZZ) sage: a = X([3,2,1]) sage: a.divided_difference(1) X[2, 3, 1] sage: a.divided_difference([3,2,1]) X[1] sage: a.divided_difference(5) 0 Any divided difference of `0` is `0`:: sage: X.zero().divided_difference(2) 0 This is compatible when a permutation is given as input:: sage: a = X([3,2,4,1]) sage: a.divided_difference([2,3,1]) 0 sage: a.divided_difference(1).divided_difference(2) 0 :: sage: a = X([4,3,2,1]) sage: a.divided_difference([2,3,1]) X[3, 2, 4, 1] sage: a.divided_difference(1).divided_difference(2) X[3, 2, 4, 1] sage: a.divided_difference([4,1,3,2]) X[1, 4, 2, 3] sage: b = X([4, 1, 3, 2]) sage: b.divided_difference(1).divided_difference(2) X[1, 3, 4, 2] sage: b.divided_difference(1).divided_difference(2).divided_difference(3) X[1, 3, 2] sage: b.divided_difference(1).divided_difference(2).divided_difference(3).divided_difference(2) X[1] sage: b.divided_difference(1).divided_difference(2).divided_difference(3).divided_difference(3) 0 sage: b.divided_difference(1).divided_difference(2).divided_difference(1) 0 TESTS: Check that :trac:`23403` is fixed:: sage: X = SchubertPolynomialRing(ZZ) sage: a = X([3,2,4,1]) sage: a.divided_difference(2) 0 sage: a.divided_difference([3,2,1]) 0 sage: a.divided_difference(0) Traceback (most recent call last): ... ValueError: cannot apply \delta_{0} to a (= X[3, 2, 4, 1]) """ if not self: # if self is 0 return self Perms = Permutations() if i in ZZ: if algorithm == "sage": if i <= 0: raise ValueError(r"cannot apply \delta_{%s} to a (= %s)" % (i, self)) # The operator `\delta_i` sends the Schubert # polynomial `X_\pi` (where `\pi` is a finitely supported # permutation of `\{1, 2, 3, \ldots\}`) to: # - the Schubert polynomial X_\sigma`, where `\sigma` is # obtained from `\pi` by switching the values at `i` and `i+1`, # if `i` is a descent of `\pi` (that is, `\pi(i) > \pi(i+1)`); # - `0` otherwise. # Notice that distinct `\pi`s lead to distinct `\sigma`s, # so we can use `_from_dict` here. res_dict = {} for pi, coeff in self: pi = pi[:] n = len(pi) if n <= i: continue if pi[i-1] < pi[i]: continue pi[i-1], pi[i] = pi[i], pi[i-1] pi = Perms(pi).remove_extra_fixed_points() res_dict[pi] = coeff return self.parent()._from_dict(res_dict) else: # if algorithm == "symmetrica": return symmetrica.divdiff_schubert(i, self) elif i in Perms: if algorithm == "sage": i = Permutation(i) redw = i.reduced_word() res_dict = {} for pi, coeff in self: next_pi = False pi = pi[:] n = len(pi) for j in redw: if n <= j: next_pi = True break if pi[j-1] < pi[j]: next_pi = True break pi[j-1], pi[j] = pi[j], pi[j-1] if next_pi: continue pi = Perms(pi).remove_extra_fixed_points() res_dict[pi] = coeff return self.parent()._from_dict(res_dict) else: # if algorithm == "symmetrica": return symmetrica.divdiff_perm_schubert(i, self) else: raise TypeError("i must either be an integer or permutation")
def PermutationGraph(second_permutation, first_permutation=None): r""" Build a permutation graph from one permutation or from two lists. Definition: If `\sigma` is a permutation of `\{ 1, 2, \ldots, n \}`, then the permutation graph of `\sigma` is the graph on vertex set `\{ 1, 2, \ldots, n \}` in which two vertices `i` and `j` satisfying `i < j` are connected by an edge if and only if `\sigma^{-1}(i) > \sigma^{-1}(j)`. A visual way to construct this graph is as follows: Take two horizontal lines in the euclidean plane, and mark points `1, ..., n` from left to right on the first of them. On the second one, still from left to right, mark `n` points `\sigma(1), \sigma(2), \ldots, \sigma(n)`. Now, link by a segment the two points marked with `1`, then link together the points marked with `2`, and so on. The permutation graph of `\sigma` is the intersection graph of those segments: there exists a vertex in this graph for each element from `1` to `n`, two vertices `i, j` being adjacent if the segments `i` and `j` cross each other. The set of edges of the permutation graph can thus be identified with the set of inversions of the inverse of the given permutation `\sigma`. A more general notion of permutation graph can be defined as follows: If `S` is a set, and `(a_1, a_2, \ldots, a_n)` and `(b_1, b_2, \ldots, b_n)` are two lists of elements of `S`, each of which lists contains every element of `S` exactly once, then the permutation graph defined by these two lists is the graph on the vertex set `S` in which two vertices `i` and `j` are connected by an edge if and only if the order in which these vertices appear in the list `(a_1, a_2, \ldots, a_n)` is the opposite of the order in which they appear in the list `(b_1, b_2, \ldots, b_n)`. When `(a_1, a_2, \ldots, a_n) = (1, 2, \ldots, n)`, this graph is the permutation graph of the permutation `(b_1, b_2, \ldots, b_n) \in S_n`. Notice that `S` does not have to be a set of integers here, but can be a set of strings, tuples, or anything else. We can still use the above visual description to construct the permutation graph, but now we have to mark points `a_1, a_2, \ldots, a_n` from left to right on the first horizontal line and points `b_1, b_2, \ldots, b_n` from left to right on the second horizontal line. INPUT: - ``second_permutation`` -- the unique permutation/list defining the graph, or the second of the two (if the graph is to be built from two permutations/lists). - ``first_permutation`` (optional) -- the first of the two permutations/lists from which the graph should be built, if it is to be built from two permutations/lists. When ``first_permutation is None`` (default), it is set to be equal to ``sorted(second_permutation)``, which yields the expected ordering when the elements of the graph are integers. .. SEEALSO:: - Recognition of Permutation graphs in the :mod:`comparability module <sage.graphs.comparability>`. - Drawings of permutation graphs as intersection graphs of segments is possible through the :meth:`~sage.combinat.permutation.Permutation.show` method of :class:`~sage.combinat.permutation.Permutation` objects. The correct argument to use in this case is ``show(representation = "braid")``. - :meth:`~sage.combinat.permutation.Permutation.inversions` EXAMPLES:: sage: p = Permutations(5).random_element() sage: PG = graphs.PermutationGraph(p) sage: edges = PG.edges(labels=False) sage: set(edges) == set(p.inverse().inversions()) True sage: PG = graphs.PermutationGraph([3,4,5,1,2]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 4, None), (2, 5, None)] sage: PG = graphs.PermutationGraph([3,4,5,1,2], [1,4,2,5,3]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None), (3, 4, None), (3, 5, None)] sage: PG = graphs.PermutationGraph([1,4,2,5,3], [3,4,5,1,2]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None), (3, 4, None), (3, 5, None)] sage: PG = graphs.PermutationGraph(Permutation([1,3,2]), Permutation([1,2,3])) sage: sorted(PG.edges()) [(2, 3, None)] sage: graphs.PermutationGraph([]).edges() [] sage: graphs.PermutationGraph([], []).edges() [] sage: PG = graphs.PermutationGraph("graph", "phrag") sage: sorted(PG.edges()) [('a', 'g', None), ('a', 'h', None), ('a', 'p', None), ('g', 'h', None), ('g', 'p', None), ('g', 'r', None), ('h', 'r', None), ('p', 'r', None)] TESTS:: sage: graphs.PermutationGraph([1, 2, 3], [4, 5, 6]) Traceback (most recent call last): ... ValueError: The two permutations do not contain the same set of elements ... """ if first_permutation is None: first_permutation = sorted(second_permutation) else: if set(second_permutation) != set(first_permutation): raise ValueError("The two permutations do not contain the same " + "set of elements ! It is going to be pretty " + "hard to define a permutation graph from that !") vertex_to_index = {} for i, v in enumerate(first_permutation): vertex_to_index[v] = i + 1 from sage.combinat.permutation import Permutation p2 = Permutation([vertex_to_index[x] for x in second_permutation]) p2 = p2.inverse() g = Graph(name="Permutation graph for " + str(second_permutation)) g.add_vertices(second_permutation) for u, v in p2.inversions(): g.add_edge(first_permutation[u - 1], first_permutation[v - 1]) return g
def PermutationGraph(second_permutation, first_permutation = None): r""" Build a permutation graph from one permutation or from two lists. Definition: If `\sigma` is a permutation of `\{ 1, 2, \ldots, n \}`, then the permutation graph of `\sigma` is the graph on vertex set `\{ 1, 2, \ldots, n \}` in which two vertices `i` and `j` satisfying `i < j` are connected by an edge if and only if `\sigma^{-1}(i) > \sigma^{-1}(j)`. A visual way to construct this graph is as follows: Take two horizontal lines in the euclidean plane, and mark points `1, ..., n` from left to right on the first of them. On the second one, still from left to right, mark `n` points `\sigma(1), \sigma(2), \ldots, \sigma(n)`. Now, link by a segment the two points marked with `1`, then link together the points marked with `2`, and so on. The permutation graph of `\sigma` is the intersection graph of those segments: there exists a vertex in this graph for each element from `1` to `n`, two vertices `i, j` being adjacent if the segments `i` and `j` cross each other. The set of edges of the permutation graph can thus be identified with the set of inversions of the inverse of the given permutation `\sigma`. A more general notion of permutation graph can be defined as follows: If `S` is a set, and `(a_1, a_2, \ldots, a_n)` and `(b_1, b_2, \ldots, b_n)` are two lists of elements of `S`, each of which lists contains every element of `S` exactly once, then the permutation graph defined by these two lists is the graph on the vertex set `S` in which two vertices `i` and `j` are connected by an edge if and only if the order in which these vertices appear in the list `(a_1, a_2, \ldots, a_n)` is the opposite of the order in which they appear in the list `(b_1, b_2, \ldots, b_n)`. When `(a_1, a_2, \ldots, a_n) = (1, 2, \ldots, n)`, this graph is the permutation graph of the permutation `(b_1, b_2, \ldots, b_n) \in S_n`. Notice that `S` does not have to be a set of integers here, but can be a set of strings, tuples, or anything else. We can still use the above visual description to construct the permutation graph, but now we have to mark points `a_1, a_2, \ldots, a_n` from left to right on the first horizontal line and points `b_1, b_2, \ldots, b_n` from left to right on the second horizontal line. INPUT: - ``second_permutation`` -- the unique permutation/list defining the graph, or the second of the two (if the graph is to be built from two permutations/lists). - ``first_permutation`` (optional) -- the first of the two permutations/lists from which the graph should be built, if it is to be built from two permutations/lists. When ``first_permutation is None`` (default), it is set to be equal to ``sorted(second_permutation)``, which yields the expected ordering when the elements of the graph are integers. .. SEEALSO: - Recognition of Permutation graphs in the :mod:`comparability module <sage.graphs.comparability>`. - Drawings of permutation graphs as intersection graphs of segments is possible through the :meth:`~sage.combinat.permutation.Permutation.show` method of :class:`~sage.combinat.permutation.Permutation` objects. The correct argument to use in this case is ``show(representation = "braid")``. - :meth:`~sage.combinat.permutation.Permutation.inversions` EXAMPLES:: sage: p = Permutations(5).random_element() sage: PG = graphs.PermutationGraph(p) sage: edges = PG.edges(labels=False) sage: set(edges) == set(p.inverse().inversions()) True sage: PG = graphs.PermutationGraph([3,4,5,1,2]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 4, None), (2, 5, None)] sage: PG = graphs.PermutationGraph([3,4,5,1,2], [1,4,2,5,3]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None), (3, 4, None), (3, 5, None)] sage: PG = graphs.PermutationGraph([1,4,2,5,3], [3,4,5,1,2]) sage: sorted(PG.edges()) [(1, 3, None), (1, 4, None), (1, 5, None), (2, 3, None), (2, 5, None), (3, 4, None), (3, 5, None)] sage: PG = graphs.PermutationGraph(Permutation([1,3,2]), Permutation([1,2,3])) sage: sorted(PG.edges()) [(2, 3, None)] sage: graphs.PermutationGraph([]).edges() [] sage: graphs.PermutationGraph([], []).edges() [] sage: PG = graphs.PermutationGraph("graph", "phrag") sage: sorted(PG.edges()) [('a', 'g', None), ('a', 'h', None), ('a', 'p', None), ('g', 'h', None), ('g', 'p', None), ('g', 'r', None), ('h', 'r', None), ('p', 'r', None)] TESTS:: sage: graphs.PermutationGraph([1, 2, 3], [4, 5, 6]) Traceback (most recent call last): ... ValueError: The two permutations do not contain the same set of elements ... """ if first_permutation is None: first_permutation = sorted(second_permutation) else: if set(second_permutation) != set(first_permutation): raise ValueError("The two permutations do not contain the same "+ "set of elements ! It is going to be pretty "+ "hard to define a permutation graph from that !") vertex_to_index = {} for i, v in enumerate(first_permutation): vertex_to_index[v] = i+1 from sage.combinat.permutation import Permutation p2 = Permutation([vertex_to_index[x] for x in second_permutation]) p2 = p2.inverse() g = Graph(name="Permutation graph for "+str(second_permutation)) g.add_vertices(second_permutation) for u,v in p2.inversions(): g.add_edge(first_permutation[u-1], first_permutation[v-1]) return g
def gale_ryser_theorem(p1, p2, algorithm="gale"): r""" Returns the binary matrix given by the Gale-Ryser theorem. The Gale Ryser theorem asserts that if `p_1,p_2` are two partitions of `n` of respective lengths `k_1,k_2`, then there is a binary `k_1\times k_2` matrix `M` such that `p_1` is the vector of row sums and `p_2` is the vector of column sums of `M`, if and only if the conjugate of `p_2` dominates `p_1`. INPUT: - ``p1, p2``-- list of integers representing the vectors of row/column sums - ``algorithm`` -- two possible string values : - ``"ryser"`` implements the construction due to Ryser [Ryser63]_. - ``"gale"`` (default) implements the construction due to Gale [Gale57]_. OUTPUT: - A binary matrix if it exists, ``None`` otherwise. Gale's Algorithm: (Gale [Gale57]_): A matrix satisfying the constraints of its sums can be defined as the solution of the following Linear Program, which Sage knows how to solve. .. MATH:: \forall i&\sum_{j=1}^{k_2} b_{i,j}=p_{1,j}\\ \forall i&\sum_{j=1}^{k_1} b_{j,i}=p_{2,j}\\ &b_{i,j}\mbox{ is a binary variable} Ryser's Algorithm: (Ryser [Ryser63]_): The construction of an `m\times n` matrix `A=A_{r,s}`, due to Ryser, is described as follows. The construction works if and only if have `s\preceq r^*`. * Construct the `m\times n` matrix `B` from `r` by defining the `i`-th row of `B` to be the vector whose first `r_i` entries are `1`, and the remainder are 0's, `1\leq i\leq m`. This maximal matrix `B` with row sum `r` and ones left justified has column sum `r^{*}`. * Shift the last `1` in certain rows of `B` to column `n` in order to achieve the sum `s_n`. Call this `B` again. * The `1`'s in column n are to appear in those rows in which `A` has the largest row sums, giving preference to the bottom-most positions in case of ties. * Note: When this step automatically "fixes" other columns, one must skip ahead to the first column index with a wrong sum in the step below. * Proceed inductively to construct columns `n-1`, ..., `2`, `1`. * Set `A = B`. Return `A`. EXAMPLES: Computing the matrix for `p_1=p_2=2+2+1` :: sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: p1 = [2,2,1] sage: p2 = [2,2,1] sage: print gale_ryser_theorem(p1, p2) # not tested [1 1 0] [1 0 1] [0 1 0] sage: A = gale_ryser_theorem(p1, p2) sage: rs = [sum(x) for x in A.rows()] sage: cs = [sum(x) for x in A.columns()] sage: p1 == rs; p2 == cs True True Or for a non-square matrix with `p_1=3+3+2+1` and `p_2=3+2+2+1+1`, using Ryser's algorithm :: sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: p1 = [3,3,1,1] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 0 1] [1 1 1 0] [0 1 0 0] [1 0 0 0] sage: p1 = [4,2,2] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 1] [1 1 0 0] [1 1 0 0] sage: p1 = [4,2,2,0] sage: p2 = [3,3,1,1,0,0] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 1 0 0] [1 1 0 0 0 0] [1 1 0 0 0 0] [0 0 0 0 0 0] sage: p1 = [3,3,2,1] sage: p2 = [3,2,2,1,1] sage: print gale_ryser_theorem(p1, p2, algorithm="gale") # not tested [1 1 1 0 0] [1 1 0 0 1] [1 0 1 0 0] [0 0 0 1 0] With `0` in the sequences, and with unordered inputs :: sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: gale_ryser_theorem([3,3,0,1,1,0], [3,1,3,1,0], algorithm = "ryser") [1 0 1 1 0] [1 1 1 0 0] [0 0 0 0 0] [0 0 1 0 0] [1 0 0 0 0] [0 0 0 0 0] sage: p1 = [3,1,1,1,1]; p2 = [3,2,2,0] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 0] [0 0 1 0] [0 1 0 0] [1 0 0 0] [1 0 0 0] TESTS: This test created a random bipartite graph on `n+m` vertices. Its adjacency matrix is binary, and it is used to create some "random-looking" sequences which correspond to an existing matrix. The ``gale_ryser_theorem`` is then called on these sequences, and the output checked for correction.:: sage: def test_algorithm(algorithm, low = 10, high = 50): ... n,m = randint(low,high), randint(low,high) ... g = graphs.RandomBipartite(n, m, .3) ... s1 = sorted(g.degree([(0,i) for i in range(n)]), reverse = True) ... s2 = sorted(g.degree([(1,i) for i in range(m)]), reverse = True) ... m = gale_ryser_theorem(s1, s2, algorithm = algorithm) ... ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) ... ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) ... if ((ss1 == s1) and (ss2 == s2)): ... return True ... return False sage: for algorithm in ["gale", "ryser"]: # long time ... for i in range(50): # long time ... if not test_algorithm(algorithm, 3, 10): # long time ... print "Something wrong with algorithm ", algorithm # long time ... break # long time Null matrix:: sage: gale_ryser_theorem([0,0,0],[0,0,0,0], algorithm="gale") [0 0 0 0] [0 0 0 0] [0 0 0 0] sage: gale_ryser_theorem([0,0,0],[0,0,0,0], algorithm="ryser") [0 0 0 0] [0 0 0 0] [0 0 0 0] REFERENCES: .. [Ryser63] H. J. Ryser, Combinatorial Mathematics, Carus Monographs, MAA, 1963. .. [Gale57] D. Gale, A theorem on flows in networks, Pacific J. Math. 7(1957)1073-1082. """ from sage.combinat.partition import Partition from sage.matrix.constructor import matrix if not (is_gale_ryser(p1, p2)): return False if algorithm == "ryser": # ryser's algorithm from sage.combinat.permutation import Permutation # Sorts the sequences if they are not, and remembers the permutation # applied tmp = sorted(enumerate(p1), reverse=True, key=lambda x: x[1]) r = [x[1] for x in tmp if x[1] > 0] r_permutation = [ x - 1 for x in Permutation([x[0] + 1 for x in tmp]).inverse() ] m = len(r) tmp = sorted(enumerate(p2), reverse=True, key=lambda x: x[1]) s = [x[1] for x in tmp if x[1] > 0] s_permutation = [ x - 1 for x in Permutation([x[0] + 1 for x in tmp]).inverse() ] n = len(s) A0 = matrix([[1] * r[j] + [0] * (n - r[j]) for j in range(m)]) for k in range(1, n + 1): goodcols = [i for i in range(n) if s[i] == sum(A0.column(i))] if sum(A0.column(n - k)) != s[n - k]: A0 = _slider01(A0, s[n - k], n - k, p1, p2, goodcols) # If we need to add empty rows/columns if len(p1) != m: A0 = A0.stack(matrix([[0] * n] * (len(p1) - m))) if len(p2) != n: A0 = A0.transpose().stack(matrix([[0] * len(p1)] * (len(p2) - n))).transpose() # Applying the permutations to get a matrix satisfying the # order given by the input A0 = A0.matrix_from_rows_and_columns(r_permutation, s_permutation) return A0 elif algorithm == "gale": from sage.numerical.mip import MixedIntegerLinearProgram k1, k2 = len(p1), len(p2) p = MixedIntegerLinearProgram() b = p.new_variable(binary=True) for (i, c) in enumerate(p1): p.add_constraint(p.sum([b[i, j] for j in xrange(k2)]) == c) for (i, c) in enumerate(p2): p.add_constraint(p.sum([b[j, i] for j in xrange(k1)]) == c) p.set_objective(None) p.solve() b = p.get_values(b) M = [[0] * k2 for i in xrange(k1)] for i in xrange(k1): for j in xrange(k2): M[i][j] = int(b[i, j]) return matrix(M) else: raise ValueError( "The only two algorithms available are \"gale\" and \"ryser\"")
def cycle_string(lis): from sage.combinat.permutation import Permutation return Permutation(lis).cycle_string()
def __classcall_private__(cls, p): r""" This function tries to recognize the input (it can be either a list or a tuple of pairs, or a fix-point free involution given as a list or as a permutation), constructs the parent (enumerated set of PerfectMatchings of the ground set) and calls the __init__ function to construct our object. EXAMPLES:: sage: m = PerfectMatching([('a','e'),('b','c'),('d','f')]);m [('a', 'e'), ('b', 'c'), ('d', 'f')] sage: isinstance(m,PerfectMatching) True sage: n = PerfectMatching([3, 8, 1, 7, 6, 5, 4, 2]);n [(1, 3), (2, 8), (4, 7), (5, 6)] sage: n.parent() Set of perfect matchings of {1, 2, 3, 4, 5, 6, 7, 8} sage: PerfectMatching([(1, 4), (2, 3), (5, 6)]).is_non_crossing() True The function checks that the given list or permutation is a valid perfect matching (i.e. a list of pairs with pairwise disjoint elements or a fixpoint-free involution) and raises a ValueError otherwise: sage: PerfectMatching([(1, 2, 3), (4, 5)]) Traceback (most recent call last): ... ValueError: [(1, 2, 3), (4, 5)] is not a valid perfect matching: all elements of the list must be pairs If you know your datas are in a good format, use directly ``PerfectMatchings(objects)(data)``. TESTS:: sage: m = PerfectMatching([('a','e'),('b','c'),('d','f')]) sage: TestSuite(m).run() sage: m = PerfectMatching([]) sage: TestSuite(m).run() sage: PerfectMatching(6) Traceback (most recent call last): ... ValueError: cannot convert p (= 6) to a PerfectMatching sage: PerfectMatching([(1,2,3)]) Traceback (most recent call last): ... ValueError: [(1, 2, 3)] is not a valid perfect matching: all elements of the list must be pairs sage: PerfectMatching([(1,1)]) Traceback (most recent call last): ... ValueError: [(1, 1)] is not a valid perfect matching: there are some repetitions sage: PerfectMatching(Permutation([4,2,1,3])) Traceback (most recent call last): ... ValueError: The permutation p (= [4, 2, 1, 3]) is not a fixed point free involution """ # we have to extract from the argument p the set of objects of the # matching and the list of pairs. # First case: p is a list (resp tuple) of lists (resp tuple). if (isinstance(p, list) or isinstance(p, tuple)) and ( all([isinstance(x, list) or isinstance(x, tuple) for x in p])): objects = Set(flatten(p)) data = (map(tuple, p)) #check if the data are correct if not all([len(t) == 2 for t in data]): raise ValueError("%s is not a valid perfect matching:\n" "all elements of the list must be pairs" % p) if len(objects) < 2*len(data): raise ValueError("%s is not a valid perfect matching:\n" "there are some repetitions" % p) # Second case: p is a permutation or a list of integers, we have to # check if it is a fix-point-free involution. elif ((isinstance(p, list) and all(map(lambda x: (isinstance(x, Integer) or isinstance(x, int)), p))) or isinstance(p, Permutation)): p = Permutation(p) n = len(p) if not(p.cycle_type() == [2 for i in range(n//2)]): raise ValueError("The permutation p (= %s) is not a " "fixed point free involution" % p) objects = Set(range(1, n+1)) data = p.to_cycles() # Third case: p is already a perfect matching, we return p directly elif isinstance(p, PerfectMatching): return p else: raise ValueError("cannot convert p (= %s) to a PerfectMatching" % p) # Finally, we create the parent and the element using the element # class of the parent. Note: as this function is private, when we # create an object via parent.element_class(...), __init__ is directly # executed and we do not have an infinite loop. return PerfectMatchings(objects)(data)
def PermutationGraph(second_permutation, first_permutation = None): r""" Builds a permutation graph from one (or two) permutations. General definition A Permutation Graph can be encoded by a permutation `\sigma` of `1, ..., n`. It is then built in the following way : Take two horizontal lines in the euclidean plane, and mark points `1, ..., n` from left to right on the first of them. On the second one, still from left to right, mark point in the order in which they appear in `\sigma`. Now, link by a segment the two points marked with 1, then link together the points marked with 2, and so on. The permutation graph defined by the permutation is the intersection graph of those segments : there exists a point in this graph for each element from `1` to `n`, two vertices `i, j` being adjacent if the segments `i` and `j` cross each other. The set of edges of the resulting graph is equal to the set of inversions of the inverse of the given permutation. INPUT: - ``second_permutation`` -- the permutation from which the graph should be built. It corresponds to the ordering of the elements on the second line (see previous definition) - ``first_permutation`` (optional) -- the ordering of the elements on the *first* line. This is useful when the elements have no natural ordering, for instance when they are strings, or tuples, or anything else. When ``first_permutation == None`` (default), it is set to be equal to ``sorted(second_permutation)``, which just yields the expected ordering when the elements of the graph are integers. .. SEEALSO: - Recognition of Permutation graphs in the :mod:`comparability module <sage.graphs.comparability>`. - Drawings of permutation graphs as intersection graphs of segments is possible through the :meth:`~sage.combinat.permutation.Permutation.show` method of :class:`~sage.combinat.permutation.Permutation` objects. The correct argument to use in this case is ``show(representation = "braid")``. - :meth:`~sage.combinat.permutation.Permutation.inversions` EXAMPLE:: sage: p = Permutations(5).random_element() sage: edges = graphs.PermutationGraph(p).edges(labels =False) sage: set(edges) == set(p.inverse().inversions()) True TESTS:: sage: graphs.PermutationGraph([1, 2, 3], [4, 5, 6]) Traceback (most recent call last): ... ValueError: The two permutations do not contain the same set of elements ... """ if first_permutation == None: first_permutation = sorted(second_permutation) else: if set(second_permutation) != set(first_permutation): raise ValueError("The two permutations do not contain the same "+ "set of elements ! It is going to be pretty "+ "hard to define a permutation graph from that !") vertex_to_index = {} for i, v in enumerate(first_permutation): vertex_to_index[v] = i+1 from sage.combinat.permutation import Permutation p2 = Permutation(map(lambda x:vertex_to_index[x], second_permutation)) p1 = Permutation(map(lambda x:vertex_to_index[x], first_permutation)) p2 = p2 * p1.inverse() p2 = p2.inverse() g = Graph(name="Permutation graph for "+str(second_permutation)) g.add_vertices(second_permutation) for u,v in p2.inversions(): g.add_edge(first_permutation[u-1], first_permutation[v-1]) return g
def cycle_index(self, parent = None): r""" INPUT: - ``self`` - a permutation group `G` - ``parent`` -- a free module with basis indexed by partitions, or behave as such, with a ``term`` and ``sum`` method (default: the symmetric functions over the rational field in the p basis) Returns the *cycle index* of `G`, which is a gadget counting the elements of `G` by cycle type, averaged over the group: .. math:: P = \frac{1}{|G|} \sum_{g\in G} p_{ \operatorname{cycle\ type}(g) } EXAMPLES: Among the permutations of the symmetric group `S_4`, there is the identity, 6 cycles of length 2, 3 products of two cycles of length 2, 8 cycles of length 3, and 6 cycles of length 4:: sage: S4 = SymmetricGroup(4) sage: P = S4.cycle_index() sage: 24 * P p[1, 1, 1, 1] + 6*p[2, 1, 1] + 3*p[2, 2] + 8*p[3, 1] + 6*p[4] If `l = (l_1,\dots,l_k)` is a partition, ``|G| P[l]`` is the number of elements of `G` with cycles of length `(p_1,\dots,p_k)`:: sage: 24 * P[ Partition([3,1]) ] 8 The cycle index plays an important role in the enumeration of objects modulo the action of a group (Polya enumeration), via the use of symmetric functions and plethysms. It is therefore encoded as a symmetric function, expressed in the powersum basis:: sage: P.parent() Symmetric Functions over Rational Field in the powersum basis This symmetric function can have some nice properties; for example, for the symmetric group `S_n`, we get the complete symmetric function `h_n`:: sage: S = SymmetricFunctions(QQ); h = S.h() sage: h( P ) h[4] TODO: add some simple examples of Polya enumeration, once it will be easy to expand symmetric functions on any alphabet. Here are the cycle indices of some permutation groups:: sage: 6 * CyclicPermutationGroup(6).cycle_index() p[1, 1, 1, 1, 1, 1] + p[2, 2, 2] + 2*p[3, 3] + 2*p[6] sage: 60 * AlternatingGroup(5).cycle_index() p[1, 1, 1, 1, 1] + 15*p[2, 2, 1] + 20*p[3, 1, 1] + 24*p[5] sage: for G in TransitiveGroups(5): # optional - database_gap # long time ... G.cardinality() * G.cycle_index() p[1, 1, 1, 1, 1] + 4*p[5] p[1, 1, 1, 1, 1] + 5*p[2, 2, 1] + 4*p[5] p[1, 1, 1, 1, 1] + 5*p[2, 2, 1] + 10*p[4, 1] + 4*p[5] p[1, 1, 1, 1, 1] + 15*p[2, 2, 1] + 20*p[3, 1, 1] + 24*p[5] p[1, 1, 1, 1, 1] + 10*p[2, 1, 1, 1] + 15*p[2, 2, 1] + 20*p[3, 1, 1] + 20*p[3, 2] + 30*p[4, 1] + 24*p[5] One may specify another parent for the result:: sage: F = CombinatorialFreeModule(QQ, Partitions()) sage: P = CyclicPermutationGroup(6).cycle_index(parent = F) sage: 6 * P B[[1, 1, 1, 1, 1, 1]] + B[[2, 2, 2]] + 2*B[[3, 3]] + 2*B[[6]] sage: P.parent() is F True This parent should have a ``term`` and ``sum`` method:: sage: CyclicPermutationGroup(6).cycle_index(parent = QQ) Traceback (most recent call last): ... AssertionError: `parent` should be (or behave as) a free module with basis indexed by partitions REFERENCES: .. [Ker1991] A. Kerber. Algebraic combinatorics via finite group actions, 2.2 p. 70. BI-Wissenschaftsverlag, Mannheim, 1991. AUTHORS: - Nicolas Borie and Nicolas M. Thiery TESTS:: sage: P = PermutationGroup([]); P Permutation Group with generators [()] sage: P.cycle_index() p[1] sage: P = PermutationGroup([[(1)]]); P Permutation Group with generators [()] sage: P.cycle_index() p[1] """ from sage.combinat.permutation import Permutation if parent is None: from sage.rings.rational_field import QQ from sage.combinat.sf.sf import SymmetricFunctions parent = SymmetricFunctions(QQ).powersum() else: assert hasattr(parent, "term") and hasattr(parent, "sum"), \ "`parent` should be (or behave as) a free module with basis indexed by partitions" base_ring = parent.base_ring() # TODO: use self.conjugacy_classes() once available from sage.interfaces.gap import gap CC = ([Permutation(self(C.Representative())).cycle_type(), base_ring(C.Size())] for C in gap(self).ConjugacyClasses()) return parent.sum( parent.term( partition, coeff ) for (partition, coeff) in CC)/self.cardinality()
def RSK_inverse(p, q, output='array', insertion='RSK'): r""" Return the generalized permutation corresponding to the pair of tableaux `(p,q)` under the inverse of the Robinson-Schensted-Knuth algorithm. For more information on the bijection, see :func:`RSK`. INPUT: - ``p``, ``q`` -- Two semi-standard tableaux of the same shape, or (in the case when Hecke insertion is used) an increasing tableau and a set-valued tableau of the same shape (see the note below for the format of the set-valued tableau) - ``output`` -- (Default: ``'array'``) if ``q`` is semi-standard: - ``'array'`` -- as a two-line array (i.e. generalized permutation or biword) - ``'matrix'`` -- as an integer matrix and if ``q`` is standard, we can have the output: - ``'word'`` -- as a word and additionally if ``p`` is standard, we can also have the output: - ``'permutation'`` -- as a permutation - ``insertion`` -- (Default: ``RSK``) The insertion algorithm used in the bijection. Currently the following are supported: - ``'RSK'`` -- Robinson-Schensted-Knuth insertion - ``'EG'`` -- Edelman-Greene insertion - ``'hecke'`` -- Hecke insertion .. NOTE:: In the case of Hecke insertion, the input variable ``q`` should be a set-valued tableau, encoded as a tableau whose entries are strictly increasing tuples of positive integers. Each such tuple encodes the set of its entries. EXAMPLES: If both ``p`` and ``q`` are standard:: sage: t1 = Tableau([[1, 2, 5], [3], [4]]) sage: t2 = Tableau([[1, 2, 3], [4], [5]]) sage: RSK_inverse(t1, t2) [[1, 2, 3, 4, 5], [1, 4, 5, 3, 2]] sage: RSK_inverse(t1, t2, 'word') word: 14532 sage: RSK_inverse(t1, t2, 'matrix') [1 0 0 0 0] [0 0 0 1 0] [0 0 0 0 1] [0 0 1 0 0] [0 1 0 0 0] sage: RSK_inverse(t1, t2, 'permutation') [1, 4, 5, 3, 2] sage: RSK_inverse(t1, t1, 'permutation') [1, 4, 3, 2, 5] sage: RSK_inverse(t2, t2, 'permutation') [1, 2, 5, 4, 3] sage: RSK_inverse(t2, t1, 'permutation') [1, 5, 4, 2, 3] If the first tableau is semistandard:: sage: p = Tableau([[1,2,2],[3]]); q = Tableau([[1,2,4],[3]]) sage: ret = RSK_inverse(p, q); ret [[1, 2, 3, 4], [1, 3, 2, 2]] sage: RSK_inverse(p, q, 'word') word: 1322 In general:: sage: p = Tableau([[1,2,2],[2]]); q = Tableau([[1,3,3],[2]]) sage: RSK_inverse(p, q) [[1, 2, 3, 3], [2, 1, 2, 2]] sage: RSK_inverse(p, q, 'matrix') [0 1] [1 0] [0 2] Using Edelman-Greene insertion:: sage: pq = RSK([2,1,2,3,2], insertion='EG'); pq [[[1, 2, 3], [2, 3]], [[1, 3, 4], [2, 5]]] sage: RSK_inverse(*pq, insertion='EG') [2, 1, 2, 3, 2] Using Hecke insertion:: sage: w = [5, 4, 1, 3, 4, 2, 5, 1, 2, 1, 4, 2, 4] sage: pq = RSK(w, insertion='hecke') sage: RSK_inverse(*pq, insertion='hecke', output='list') [5, 4, 1, 3, 4, 2, 5, 1, 2, 1, 4, 2, 4] .. NOTE:: The constructor of ``Tableau`` accepts not only semistandard tableaux, but also arbitrary lists that are fillings of a partition diagram. (And such lists are used, e.g., for the set-valued tableau ``q`` that is passed to ``RSK_inverse(p, q, insertion='hecke')``.) The user is responsible for ensuring that the tableaux passed to ``RSK_inverse`` are of the right types (semistandard, standard, increasing, set-valued as needed). TESTS: From empty tableaux:: sage: RSK_inverse(Tableau([]), Tableau([])) [[], []] Check that :func:`RSK_inverse` is the inverse of :func:`RSK` on the different types of inputs/outputs:: sage: f = lambda p: RSK_inverse(*RSK(p), output='permutation') sage: all(p == f(p) for n in range(7) for p in Permutations(n)) True sage: all(RSK_inverse(*RSK(w), output='word') == w for n in range(4) for w in Words(5, n)) True sage: from sage.combinat.integer_matrices import IntegerMatrices sage: M = IntegerMatrices([1,2,2,1], [3,1,1,1]) sage: all(RSK_inverse(*RSK(m), output='matrix') == m for m in M) True sage: n = ZZ.random_element(200) sage: p = Permutations(n).random_element() sage: is_fine = True if p == f(p) else p ; is_fine True Same for Edelman-Greene (but we are checking only the reduced words that can be obtained using the ``reduced_word()`` method from permutations):: sage: g = lambda w: RSK_inverse(*RSK(w, insertion='EG'), insertion='EG', output='permutation') sage: all(p.reduced_word() == g(p.reduced_word()) for n in range(7) for p in Permutations(n)) True sage: n = ZZ.random_element(200) sage: p = Permutations(n).random_element() sage: is_fine = True if p == f(p) else p ; is_fine True Both tableaux must be of the same shape:: sage: RSK_inverse(Tableau([[1,2,3]]), Tableau([[1,2]])) Traceback (most recent call last): ... ValueError: p(=[[1, 2, 3]]) and q(=[[1, 2]]) must have the same shape """ if insertion == 'hecke': return hecke_insertion_reverse(p, q, output) if p.shape() != q.shape(): raise ValueError("p(=%s) and q(=%s) must have the same shape"%(p, q)) from sage.combinat.tableau import SemistandardTableaux if p not in SemistandardTableaux(): raise ValueError("p(=%s) must be a semistandard tableau"%p) from bisect import bisect_left # Make a copy of p since this is destructive to it p_copy = [list(row) for row in p] if q.is_standard(): rev_word = [] # This will be our word in reverse d = dict((qij,i) for i, Li in enumerate(q) for qij in Li) # d is now a dictionary which assigns to each integer k the # number of the row of q containing k. use_EG = (insertion == 'EG') for i in reversed(d.values()): # Delete last entry from i-th row of p_copy x = p_copy[i].pop() # Always the right-most entry for row in reversed(p_copy[:i]): y_pos = bisect_left(row,x) - 1 if use_EG and row[y_pos] == x - 1 and y_pos < len(row)-1 and row[y_pos+1] == x: # Nothing to do except decrement x by 1. # (Case 1 on p. 74 of Edelman-Greene [EG1987]_.) x -= 1 else: # switch x and y x, row[y_pos] = row[y_pos], x rev_word.append(x) if use_EG: return list(reversed(rev_word)) if output == 'word': from sage.combinat.words.word import Word return Word(reversed(rev_word)) if output == 'matrix': return to_matrix(list(range(1, len(rev_word)+1)), list(reversed(rev_word))) if output == 'array': return [list(range(1, len(rev_word)+1)), list(reversed(rev_word))] if output == 'permutation': if not p.is_standard(): raise TypeError("p must be standard to have a valid permutation as output") from sage.combinat.permutation import Permutation return Permutation(reversed(rev_word)) raise ValueError("invalid output option") # Checks if insertion != 'RSK': raise NotImplementedError("only RSK is implemented for non-standard q") if q not in SemistandardTableaux(): raise ValueError("q(=%s) must be a semistandard tableau"%q) upper_row = [] lower_row = [] #upper_row and lower_row will be the upper and lower rows of the #generalized permutation we get as a result, but both reversed. d = {} for row, Li in enumerate(q): for col, val in enumerate(Li): if val in d: d[val][col] = row else: d[val] = {col: row} #d is now a double family such that for every integers k and j, #the value d[k][j] is the row i such that the (i, j)-th cell of #q is filled with k. for value, row_dict in reversed(d.items()): for i in reversed(row_dict.values()): x = p_copy[i].pop() # Always the right-most entry for row in reversed(p_copy[:i]): y = bisect_left(row,x) - 1 x, row[y] = row[y], x upper_row.append(value) lower_row.append(x) if output == 'matrix': return to_matrix(list(reversed(upper_row)), list(reversed(lower_row))) if output == 'array': return [list(reversed(upper_row)), list(reversed(lower_row))] if output in ['permutation', 'word']: raise TypeError("q must be standard to have a %s as valid output"%output) raise ValueError("invalid output option")
def _element_constructor_(self, arg1, v=None, perm=None, autom=None, check=True): r""" Coerce ``arg1`` into this permutation group, if ``arg1`` is 0, then we will try to coerce ``(v, perm, autom)``. INPUT: - ``arg1`` (optional) -- either the integers 0, 1 or an element of ``self`` - ``v`` (optional) -- a vector of length ``self.degree()`` - ``perm`` (optional) -- a permutaton of degree ``self.degree()`` - ``autom`` (optional) -- an automorphism of the ring EXAMPLES:: sage: F.<a> = GF(9) sage: S = SemimonomialTransformationGroup(F, 4) sage: S(1) ((1, 1, 1, 1); (), Ring endomorphism of Finite Field in a of size 3^2 Defn: a |--> a) sage: g = S(v=[1,1,1,a]) sage: S(g) ((1, 1, 1, a); (), Ring endomorphism of Finite Field in a of size 3^2 Defn: a |--> a) sage: S(perm=Permutation('(1,2)(3,4)')) ((1, 1, 1, 1); (1,2)(3,4), Ring endomorphism of Finite Field in a of size 3^2 Defn: a |--> a) sage: S(autom=F.hom([a**3])) ((1, 1, 1, 1); (), Ring endomorphism of Finite Field in a of size 3^2 Defn: a |--> 2*a + 1) """ from sage.categories.homset import End R = self.base_ring() if arg1 == 0: if v is None: v = [R.one()] * self.degree() if perm is None: perm = Permutation(range(1, self.degree() + 1)) if autom is None: autom = R.hom(R.gens()) if check: try: v = [R(x) for x in v] except TypeError: raise TypeError('the vector attribute %s ' % v + 'should be iterable') if len(v) != self.degree(): raise ValueError('the length of the vector is %s,' % len(v) + ' should be %s' % self.degree()) if not all(x.parent() is R and x.is_unit() for x in v): raise ValueError('there is at least one element in the ' + 'list %s not lying in %s ' % (v, R) + 'or which is not invertible') try: perm = Permutation(perm) except TypeError: raise TypeError('the permutation attribute %s ' % perm + 'could not be converted to a permutation') if len(perm) != self.degree(): raise ValueError('the permutation length is %s,' % len(perm) + ' should be %s' % self.degree()) try: if autom.parent() != End(R): autom = End(R)(autom) except TypeError: raise TypeError('%s of type %s' % (autom, type(autom)) + ' is not coerceable to an automorphism') return self.Element(self, v, perm, autom) else: try: if arg1.parent() is self: return arg1 except AttributeError: pass try: from sage.rings.integer import Integer if Integer(arg1) == 1: return self() except TypeError: pass raise TypeError('the first argument must be an integer' + ' or an element of this group')