def sum_of_partitions(self, la): r""" Return the sum over all sets partitions whose shape is ``la``, scaled by `\prod_i m_i!` where `m_i` is the multiplicity of `i` in ``la``. INPUT: - ``la`` -- an integer partition OUTPUT: - an element of ``self`` EXAMPLES:: sage: w = SymmetricFunctionsNonCommutingVariables(QQ).dual().w() sage: w.sum_of_partitions([2,1,1]) 2*w{{1}, {2}, {3, 4}} + 2*w{{1}, {2, 3}, {4}} + 2*w{{1}, {2, 4}, {3}} + 2*w{{1, 2}, {3}, {4}} + 2*w{{1, 3}, {2}, {4}} + 2*w{{1, 4}, {2}, {3}} """ la = Partition(la) c = prod([factorial(_) for _ in la.to_exp()]) P = SetPartitions() return self.sum_of_terms([(P(m), c) for m in SetPartitions(sum(la), la)], distinct=True)
def is_symmetric(self): r""" Determine if a `NCSym^*` function, expressed in the `\mathbf{w}` basis, is symmetric. A function `f` in the `\mathbf{w}` basis is a symmetric function if it is in the image of `\chi^*`. That is to say we have .. MATH:: f = \sum_{\lambda} c_{\lambda} \prod_i m_i(\lambda)! \sum_{\lambda(A) = \lambda} \mathbf{w}_A where the second sum is over all set partitions `A` whose shape `\lambda(A)` is equal to `\lambda` and `m_i(\mu)` is the multiplicity of `i` in the partition `\mu`. OUTPUT: - ``True`` if `\lambda(A)=\lambda(B)` implies the coefficients of `\mathbf{w}_A` and `\mathbf{w}_B` are equal, ``False`` otherwise EXAMPLES:: sage: w = SymmetricFunctionsNonCommutingVariables(QQ).dual().w() sage: elt = w.sum_of_partitions([2,1,1]) sage: elt.is_symmetric() True sage: elt -= 3*w.sum_of_partitions([1,1]) sage: elt.is_symmetric() True sage: w = SymmetricFunctionsNonCommutingVariables(ZZ).dual().w() sage: elt = w.sum_of_partitions([2,1,1]) / 2 sage: elt.is_symmetric() False sage: elt = w[[1,3],[2]] sage: elt.is_symmetric() False sage: elt = w[[1],[2,3]] + w[[1,2],[3]] + 2*w[[1,3],[2]] sage: elt.is_symmetric() False """ d = {} R = self.base_ring() for A, coeff in self: la = A.shape() exp = prod(map(factorial, la.to_exp())) if la not in d: if coeff / exp not in R: return False d[la] = [coeff, 1] else: if d[la][0] != coeff: return False d[la][1] += 1 # Make sure we've seen each set partition of the shape return all( d[la][1] == SetPartitions(la.size(), la).cardinality() for la in d)
def __init__(self, NCSymD): """ EXAMPLES:: sage: w = SymmetricFunctionsNonCommutingVariables(QQ).dual().w() sage: TestSuite(w).run() """ def key_func_set_part(A): return sorted(map(sorted, A)) CombinatorialFreeModule.__init__(self, NCSymD.base_ring(), SetPartitions(), prefix='w', bracket=False, sorting_key=key_func_set_part, category=NCSymDualBases(NCSymD))
def __iter__(self): r""" EXAMPLES:: sage: from sage_semigroups.monoids.set_partitions_monoid import SetPartitionsMonoid sage: [SetPartitionsMonoid(n).cardinality() for n in range(8)] [1, 1, 2, 5, 15, 52, 203, 877] sage: S = SetPartitionsMonoid(4); S Monoid of set partitions of {1, 2, 3, 4} sage: TestSuite(S).run() """ for sp in SetPartitions(self._underlying_set): yield self(Set_object_enumerated(sp))
def SetPartitions(n): r""" Return the lattice of set partitions of the set `\{1,\ldots,n\}` ordered by refinement. INPUT: - ``n`` -- a positive integer EXAMPLES:: sage: Posets.SetPartitions(4) Finite lattice containing 15 elements """ from sage.rings.semirings.non_negative_integer_semiring import NN if n not in NN: raise ValueError('n must be an integer') from sage.combinat.set_partition import SetPartitions S = SetPartitions(n) return LatticePoset((S, S.is_less_than))
def __init__(self, NCSymD): """ EXAMPLES:: sage: w = SymmetricFunctionsNonCommutingVariables(QQ).dual().w() sage: TestSuite(w).run() """ def lt_set_part(A, B): A = sorted(map(sorted, A)) B = sorted(map(sorted, B)) for i in range(len(A)): if A[i] > B[i]: return 1 elif A[i] < B[i]: return -1 return 0 CombinatorialFreeModule.__init__(self, NCSymD.base_ring(), SetPartitions(), prefix='w', bracket=False, monomial_cmp=lt_set_part, category=NCSymDualBases(NCSymD))
def dcrossvec_setp(n): """ Return a list with the distribution of k-dcrossings on set partitions of [1..n]. INPUT: n -- a nonnegative integer. OUTPUT: A list whose k'th entry is the number of set partitions p for which dcrossing(p) = k. For example, let L = dcrossvec_setp(3). We have L = [1, 0, 4]. L[0] is 1 because there's 1 partition of [1..3] that has 0-dcrossing: [(1, 2, 3)]. One tricky bit is that noncrossing matchings get put at the end, because L[-1] is the last element of the list. Above, we have L[-1] = 4 because the other four set partitions are all d-noncrossing. Because of this, you should not think of the last element of the list as having index n-1, but rather -1. EXAMPLES:: sage: from sage.tests.arxiv_0812_2725 import * sage: dcrossvec_setp(3) [1, 0, 4] sage: dcrossvec_setp(4) [5, 1, 0, 9] The one set partition of 1 element is noncrossing, so the last element of the list is 1:: sage: dcrossvec_setp(1) [1] """ vec = [0] * n for p in SetPartitions(n): vec[dcrossing(setp_to_edges(p))] += 1 return vec
def expand(self, n, letter='x'): r""" Expand ``self`` written in the `\mathbf{w}` basis in `n^2` commuting variables which satisfy the relation `x_{ij} x_{ik} = 0` for all `i`, `j`, and `k`. The expansion of an element of the `\mathbf{w}` basis is given by equations (26) and (55) in [HNT06]_. INPUT: - ``n`` -- an integer - ``letter`` -- (default: ``'x'``) a string OUTPUT: - The symmetric function of ``self`` expressed in the ``n*n`` non-commuting variables described by ``letter``. REFERENCES: .. [HNT06] \F. Hivert, J.-C. Novelli, J.-Y. Thibon. *Commutative combinatorial Hopf algebras*. (2006). :arxiv:`0605262v1`. EXAMPLES:: sage: w = SymmetricFunctionsNonCommutingVariables(QQ).dual().w() sage: w[[1,3],[2]].expand(4) x02*x11*x20 + x03*x11*x30 + x03*x22*x30 + x13*x22*x31 One can use a different set of variable by using the optional argument ``letter``:: sage: w[[1,3],[2]].expand(3, letter='y') y02*y11*y20 """ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.combinat.permutation import Permutations m = self.parent() names = ['{}{}{}'.format(letter, i, j) for i in range(n) for j in range(n)] R = PolynomialRing(m.base_ring(), n*n, names) x = [[R.gens()[i*n+j] for j in range(n)] for i in range(n)] I = R.ideal([x[i][j]*x[i][k] for j in range(n) for k in range(n) for i in range(n)]) Q = R.quotient(I, names) x = [[Q.gens()[i*n+j] for j in range(n)] for i in range(n)] P = SetPartitions() def on_basis(A): k = A.size() ret = R.zero() if n < k: return ret for p in Permutations(k): if P(p.to_cycles()) == A: # -1 for indexing ret += R.sum(prod(x[I[i]][I[p[i]-1]] for i in range(k)) for I in Subsets(range(n), k)) return ret return m._apply_module_morphism(self, on_basis, codomain=R)
def product_on_basis(self, A, B): r""" The product on `\mathbf{w}` basis elements. The product on the `\mathbf{w}` is the dual to the coproduct on the `\mathbf{m}` basis. On the basis `\mathbf{w}` it is defined as .. MATH:: \mathbf{w}_A \mathbf{w}_B = \sum_{S \subseteq [n]} \mathbf{w}_{A\uparrow_S \cup B\uparrow_{S^c}} where the sum is over all possible subsets `S` of `[n]` such that `|S| = |A|` with a term indexed the union of `A \uparrow_S` and `B \uparrow_{S^c}`. The notation `A \uparrow_S` represents the unique set partition of the set `S` such that the standardization is `A`. This product is commutative. INPUT: - ``A``, ``B`` -- set partitions OUTPUT: - an element of the `\mathbf{w}` basis EXAMPLES:: sage: w = SymmetricFunctionsNonCommutingVariables(QQ).dual().w() sage: A = SetPartition([[1], [2,3]]) sage: B = SetPartition([[1, 2, 3]]) sage: w.product_on_basis(A, B) w{{1}, {2, 3}, {4, 5, 6}} + w{{1}, {2, 3, 4}, {5, 6}} + w{{1}, {2, 3, 5}, {4, 6}} + w{{1}, {2, 3, 6}, {4, 5}} + w{{1}, {2, 4}, {3, 5, 6}} + w{{1}, {2, 4, 5}, {3, 6}} + w{{1}, {2, 4, 6}, {3, 5}} + w{{1}, {2, 5}, {3, 4, 6}} + w{{1}, {2, 5, 6}, {3, 4}} + w{{1}, {2, 6}, {3, 4, 5}} + w{{1, 2, 3}, {4}, {5, 6}} + w{{1, 2, 4}, {3}, {5, 6}} + w{{1, 2, 5}, {3}, {4, 6}} + w{{1, 2, 6}, {3}, {4, 5}} + w{{1, 3, 4}, {2}, {5, 6}} + w{{1, 3, 5}, {2}, {4, 6}} + w{{1, 3, 6}, {2}, {4, 5}} + w{{1, 4, 5}, {2}, {3, 6}} + w{{1, 4, 6}, {2}, {3, 5}} + w{{1, 5, 6}, {2}, {3, 4}} sage: B = SetPartition([[1], [2]]) sage: w.product_on_basis(A, B) 3*w{{1}, {2}, {3}, {4, 5}} + 2*w{{1}, {2}, {3, 4}, {5}} + 2*w{{1}, {2}, {3, 5}, {4}} + w{{1}, {2, 3}, {4}, {5}} + w{{1}, {2, 4}, {3}, {5}} + w{{1}, {2, 5}, {3}, {4}} sage: w.product_on_basis(A, SetPartition([])) w{{1}, {2, 3}} """ if len(A) == 0: return self.monomial(B) if len(B) == 0: return self.monomial(A) P = SetPartitions() n = A.size() k = B.size() def unions(s): a = sorted(s) b = sorted(Set(range(1, n+k+1)).difference(s)) # -1 for indexing ret = [[a[i-1] for i in sorted(part)] for part in A] ret += [[b[i-1] for i in sorted(part)] for part in B] return P(ret) return self.sum_of_terms([(unions(s), 1) for s in Subsets(n+k, n)])
def duality_pairing_matrix(self, basis, degree): r""" The matrix of scalar products between elements of `NCSym` and elements of `NCSym^*`. INPUT: - ``basis`` -- a basis of the dual Hopf algebra - ``degree`` -- a non-negative integer OUTPUT: - the matrix of scalar products between the basis ``self`` and the basis ``basis`` in the dual Hopf algebra of degree ``degree`` EXAMPLES: The matrix between the `\mathbf{m}` basis and the `\mathbf{w}` basis:: sage: NCSym = SymmetricFunctionsNonCommutingVariables(QQ) sage: m = NCSym.m() sage: w = NCSym.dual().w() sage: m.duality_pairing_matrix(w, 3) [1 0 0 0 0] [0 1 0 0 0] [0 0 1 0 0] [0 0 0 1 0] [0 0 0 0 1] Similarly for some of the other basis of `NCSym` and the `\mathbf{w}` basis:: sage: e = NCSym.e() sage: e.duality_pairing_matrix(w, 3) [0 0 0 0 1] [0 0 1 1 1] [0 1 0 1 1] [0 1 1 0 1] [1 1 1 1 1] sage: p = NCSym.p() sage: p.duality_pairing_matrix(w, 3) [1 0 0 0 0] [1 1 0 0 0] [1 0 1 0 0] [1 0 0 1 0] [1 1 1 1 1] sage: q = NCSym.q() sage: q.duality_pairing_matrix(w, 3) [1 0 0 0 0] [1 1 0 0 0] [0 0 1 0 0] [1 0 0 1 0] [1 1 1 1 1] sage: x = NCSym.x() sage: w.duality_pairing_matrix(x, 3) [ 0 0 0 0 1] [ 1 0 -1 -1 1] [ 1 -1 0 -1 1] [ 1 -1 -1 0 1] [ 2 -1 -1 -1 1] A base case test:: sage: m.duality_pairing_matrix(w, 0) [1] """ from sage.matrix.constructor import matrix # TODO: generalize to keys indexing the basis of the graded component return matrix(self.base_ring(), [[self.duality_pairing(self[I], basis[J]) \ for J in SetPartitions(degree)] \ for I in SetPartitions(degree)])
def sieve(p, n, r=0, accept=None): ''' Given p, n and r, find the smallest integer m such that there exists an element of multiplicative order r in ℤ/m. This is equivalent to the condition: for each prime power r'|r, there exists a prime power m'|m such that r'|φ(m'). One immediate consequence is that r|φ(m). The output is a list of pairs of integers (m_i, r_i, o_i), such that - m_i is a prime power; - the m_i are pairwise coprime and m = ∏ m_i; - the r_i are pairwise coprime and r = ∏ r_i; - r_i · o_i = φ(m_i). The optional parameter `accept` can be passed in order to put additional constraints on m. If it is given, it must be a function satisfying: - it takes six arguments (p, n, r, l, e, s), where p, n, r, l and e are integers and s is the factorization of an integer (a `Factorization` object); - it returns a boolean; - let s and t be coprime, accept(p, n, r, l, e, s·t) returns `True` iff accept(p, n, r, l, e, s) and accept(p, n, r, l, e, t) also return `True`. Then, assuming m_i = l_i^e_i, with l_i prime, the output also satisfies - accept(p, n, r, l_i, e_i, r_i) returns `True` for any i. The obvious use case for `accept` is to ensure that a specific element of ℤ/m has order r (or divisible by r), instead of any element, e.g. p in the case of Rains' algorithm. ## Algorithm The algorithm starts by factoring r into prime powers r_i. For each r_i, it finds the smallest prime power m_i = l_i^e_i such that r_i|φ(m_i) and `accept(p, n, r, l_i, e_i, r_i)` returns `True` (if `accept` is provided). At this point, the lcm of all the m_i is an acceptable value for m, although not necessarily the smallest one. More generally, let r_{1,...,s} = r₁ · r₂ ··· r_s for some subset of the r_i (up to renumbering), then the lcm of m₁, ..., m_s is an acceptable value for r_{1,...,s}, although not necessarily the smallest one. Call m_{1,...,s} this optimal value. The algorithm goes on by computing the optimal values m_X for larger and larger subsets X ⊂ {r_i}, until r is reached. This is done by testing all the possible prime powers between the largest m_Y already computed for any proper subset Y ⊂ X, and the smallest lcm(m_W, m_Z) for any proper partition X = W ∪ Z with W ∩ Z = ∅. For any given r' = r_{1,...,s}, the algorithm looks for primes of the form k·r' + 1. If r_s = l_s^e_s is the largest prime factor of r' and if r₁ · r₂ ··· r_{s-1} | l_s - 1 the algorithm also looks for powers of l_s. It is easy to show that the algorithm needs consider no other integer. ### Example Let r = 60 = 4·3·5 The algorithm starts by computing m_{4} = 1·4 + 1 = 5, m_{3} = 2·3 + 1 = 7, m_{5} = 2·5 + 1 = 11. Then it considers all possible pairs of factors. For {4,3}, one possible value is 5·7 = 35. 2 divides 3-1, hence the algorithm looks for primes and powers of 3 comprised between 7 and 35. It finds that 13 is a suitable value, hence m_{4,3} = 1·12 + 1 = 13, and similarly m_{4,5} = 25, m_{3,5} = 31. Finally, it considers all three factors. It is useless to test prime powers below 31, because 3 and 5 must divide them. There are three possible partitions of {4,3,5} into two disjoints sets: lcm(m_{4}, m_{3,5}) = 155 lcm(m_{3}, m_{4,5}) = 175 lcm(m_{5}, m_{4,3}) = 143 hence 143 is an acceptable value, and the algorithm needs to test no further. The first value tried is 1·60 + 1 = 61, and it turns out it is a prime, hence the algorithm returns [(61, 60)]. ### Complexity The complexity is obviously polynomial in r. Heuristically, it should be sublinear, the dominating step being the factorization of r, but it is not so easy to prove it. If c is the number of primary factors of r, the main loop is executed 2^c times, which is clearly sublinear in r. At each iteration, all partitions of the current subset into two disjoint subsets must be considered, hence a very crude lower bound for this combinatorial step is ∑_{i=1}^{c} binom(c,i) (2^{i-1} - 1) < 3^c << e^{o(log r)} The most expensive operation of each cycle is the primality testing (and, eventually, the `accept` function). Heuristically, at each iteration O(log r) primality tests are needed, each with a polynomial cost in log r. As noted in `find_root_order`, the best bounds under GRH give O(r^{1.4 + ε}) primality tests, instead. Whatever the provable complexity is, this algorithm is extremely fast in practice, and can handle sizes which are way beyond the tractability of the other steps of Rains' algorithm. ''' # Degrees of the the ambient fields. ngcd, nlcm = n # Actual extension degree within the ambient fields if r == 0: r = ngcd # If accept is not given, always accept if accept is None: accept = lambda p, n, r, l, e, s: True class factorization: ''' This class represents the factorization of an integer, carrying one more piece piece of information along each primary factor: factors are pairs (p^e, o), with o an integer. ''' def __init__(self, f): ''' f must be a dictionary with entries of the form l : (e, o) ''' self.factors = f def lcm(self, other): ''' Compute the lcm of two factorizations. The auxiliary information is multiplied together: lcm ( (l^e, s), (l^d, t) ) = (l^max(e,d), s·t) ''' lcm = self.factors.copy() for (l, (e, s)) in other.factors.iteritems(): try: E, S = lcm[l] lcm[l] = (max(E, e), S * s) except KeyError: lcm[l] = (e, s) return factorization(lcm) @cached_method def expand(self): 'Return the integer represented by this factorization' return prod(map(lambda (l, (e, _)): l**e, self.factors.items())) def __str__(self): return ' * '.join('%d^%d<--%d' % (l, e, s) for (l, (e, s)) in self.factors.iteritems()) def __repr__(self): return 'factorization(%s)' % repr(self.factors) # Represent the factorization of r as a set of primary factors fact = Set(list(r.factor())) # This dictionary holds the values r_X for each subset of `fact` optima = {} # Main loop, execute for each subset `S` ⊂ `fact` # It assumes subsets are enumerated by growing size for S in fact.subsets(): # ignore the empty set if S.is_empty(): continue # A Factorization object corresponding to S Sfact = Factorization(S.list()) # find `L` the greatest prime in `S` L, E = max(S, key=lambda (l, e): l) # the product of the remaining prime powers c = prod(l**e for l, e in S if l != L) # a boolean, True only if it is worth testing powers of `L` in # the sequel powers = (L - 1) % c == 0 # the product of all the factors of `S` s = c * L**E # For singletons, we don't have an upper bound if S.cardinality() == 1: start = 1 end = None # For larger subsets, we compute the minimum and maximum value # to test else: parts = SetPartitions(S, 2) # Start from the largest m_X already computed for any # strict subset of `S` start = max(optima[T].expand() for T in S.subsets() if (not T.is_empty()) and (T != S)) # Stop at the smallest lcm of any 2-partition of `S` end = min((optima[T0].lcm(optima[T1]) for T0, T1 in parts), key=lambda x: x.expand()) # We only consider primes of the form # k·s + 1 # and powers of `L` of the form # k·s + L^E # if `powers` is true. # We determine the starting `k` k = start // s or 1 while True: m = k * s + 1 # Once the upper bound, is reached, it is used with no # further test if end is not None and m >= end.expand(): optima[S] = end break # Test primes elif m.is_prime() and accept(p, n, r, m, 1, Sfact): optima[S] = factorization({m: (1, s)}) break # Test powers of `L` # notice the correction for L = 2 on the second line d = k * c + 1 if (powers and d.is_power_of(L) and (L != 2 or E == 1 or k > 1) and accept(p, n, r, L, E + d.valuation(L), Sfact)): optima[S] = factorization({L: (E + d.valuation(L), s)}) break k += 1 # the last computed m_X is the optimum for r return [(l**e, s, (l - 1) * l**(e - 1) // s) for (l, (e, s)) in optima[fact].factors.iteritems()]
def semigroup_generators(self): from sage.sets.family import Family return Family([ self(Set_object_enumerated(X)) for X in SetPartitions(self._underlying_set, 2) ])
def conjugacy_class_iterator(part, S=None): r""" Return an iterator over the conjugacy class associated to the partition ``part``. The elements are given as a list of tuples, each tuple being a cycle. INPUT: - ``part`` -- partition - ``S`` -- (optional, default: `\{ 1, 2, \ldots, n \}`, where `n` is the size of ``part``) a set OUTPUT: An iterator over the conjugacy class consisting of all permutations of the set ``S`` whose cycle type is ``part``. EXAMPLES:: sage: from sage.groups.perm_gps.symgp_conjugacy_class import conjugacy_class_iterator sage: for p in conjugacy_class_iterator([2,2]): print p [(1, 2), (3, 4)] [(1, 3), (2, 4)] [(1, 4), (2, 3)] In order to get permutations, one can use ``imap`` from the Python module ``itertools``:: sage: from itertools import imap sage: S = SymmetricGroup(5) sage: for p in imap(S, conjugacy_class_iterator([3,2])): print p (1,2)(3,4,5) (1,2)(3,5,4) (1,3)(2,4,5) (1,3)(2,5,4) ... (1,4,2)(3,5) (1,2,3)(4,5) (1,3,2)(4,5) Check that the number of elements is the number of elements in the conjugacy class:: sage: s = lambda p: sum(1 for _ in conjugacy_class_iterator(p)) sage: all(s(p) == p.conjugacy_class_size() for p in Partitions(5)) True It is also possible to specify any underlying set:: sage: it = conjugacy_class_iterator([2,2,2], 'abcdef') sage: next(it) [('a', 'c'), ('b', 'e'), ('d', 'f')] sage: next(it) [('a', 'c'), ('b', 'd'), ('e', 'f')] """ n = sum(part) if part not in _Partitions: raise ValueError("invalid partition") if S is None: S = range(1, n+1) else: S = list(S) if n != len(S): raise ValueError("the sum of the partition %s does not match the size of %s"%(part,S)) m = len(part) for s in SetPartitions(S, part): firsts = [t[0] for t in s] rests = [t[1:] for t in s] iterator = tuple(itertools.permutations(r) for r in rests) for r in itertools.product(*iterator): yield [(firsts[i],)+r[i] for i in xrange(m)]