Example #1
0
def enumerate_hypergeometric_data(d, weight=None):
    r"""
    Return an iterator over parameters of hypergeometric motives (up to swapping).

    INPUT:

    - ``d`` -- the degree

    - ``weight`` -- optional integer, to specify the motivic weight

    EXAMPLES::

        sage: from sage.modular.hypergeometric_motive import enumerate_hypergeometric_data as enum
        sage: l = [H for H in enum(6, weight=2) if H.hodge_numbers()[0] == 1]
        sage: len(l)
        112
    """
    bound = 2 * d * d  # to make sure that phi(n) <= d
    possible = [(i, euler_phi(i)) for i in range(1, bound + 1)
                if euler_phi(i) <= d]
    poids = [z[1] for z in possible]
    N = len(poids)
    vectors = WeightedIntegerVectors(d, poids)

    def formule(u):
        return [possible[j][0] for j in range(N) for _ in range(u[j])]

    for a, b in combinations(vectors, 2):
        if not any(a[j] and b[j] for j in range(N)):
            H = HypergeometricData(cyclotomic=(formule(a), formule(b)))
            if weight is None or H.weight() == weight:
                yield H
Example #2
0
    def WeightedIntegerVectors(T, E, maxexpos):
        """
        a variant of Sage's WeightedIntegerVectors that can cope with negative weights as long as
        there are limits on them

        Currently the limits on the negative exponents must be one.

        TESTS::
            sage: from yacop.modules.algebra_base import SteenrodAlgebraBasis
            sage: wiv = SteenrodAlgebraBasis.WeightedIntegerVectors
            sage: sorted(wiv(7,[2,-1,5],[Infinity,1,Infinity]))
            [(1, 0, 1), (4, 1, 0)]
            sage: sorted(wiv(30,(-1,36,-5,48,-17,52),(1,20,1,20,1,20)))
            [(0, 0, 1, 0, 1, 1), (1, 0, 0, 1, 1, 0), (1, 1, 1, 0, 0, 0)]

        """
        from sage.modules.free_module_element import vector
        from sage.combinat.subset import Subsets

        # print "WeightedIntegerVectors", (T,E,maxexpos)
        l = len(E)
        negidx = [i for i in range(l) if E[i] < 0 and maxexpos[i] > 0]
        posidx = [i for i in range(l) if E[i] > 0]
        posvecs = [vector([1 if i == j else 0 for j in range(l)]) for i in posidx]
        maxneg = sum(E[i] * maxexpos[i] for i in negidx)
        posweights = [E[i] for i in posidx]
        for negbits in Subsets(negidx):
            negsum = sum(E[i] for i in negbits)
            negvec = vector([1 if i in negbits else 0 for i in range(l)])
            for w in WeightedIntegerVectors(T - negsum, posweights):
                posvec = sum(a * b for (a, b) in zip(w, posvecs))
                ans = negvec + posvec
                if all(i <= j for (i, j) in zip(ans, maxexpos)):
                    yield ans
Example #3
0
 def _enumerate_row(self, ans, col, dia, rownum):
     if rownum == self.nrows - 1:
         loop = [
             (self.one, ans, col, dia),
         ].__iter__()
     else:
         loop = self._enumerate_row(ans, col, dia, rownum + 1)
     for (cf, a, c, d) in loop:
         # fill in row #rownum
         for sol in WeightedIntegerVectors(self.exp[rownum], self.powers):
             cf2 = cf
             isbad = False
             for (i, s) in enumerate(sol):
                 if s > 0 and rownum + i >= self.maxn:
                     isbad = True
                     break
                 a[rownum, i] = s
                 if rownum + 1 < self.nrows:
                     cprev = c[rownum + 1, i]
                     dprev = 0 if i == 0 else d[rownum + 1, i - 1]
                 else:
                     cprev = 0
                     dprev = 0
                 c[rownum, i] = cprev + s
                 d[rownum, i] = dprev + s
                 cf2 *= binom_modp(self.p, dprev + s, s)
                 if cf2.is_zero():
                     isbad = True
                     break
             if isbad:
                 continue
             yield (cf2, a, c, d)
Example #4
0
    def __init__(self,
                 base,
                 names,
                 degrees,
                 max_degree,
                 category=None,
                 **kwargs):
        r"""
        Construct a commutative graded algebra with finite degree.

        TESTS::

            sage: A.<x,y,z,t> = GradedCommutativeAlgebra(QQ, max_degree=6)
            sage: TestSuite(A).run()
            sage: A = GradedCommutativeAlgebra(QQ, ('x','y','z'), [2,3,4], max_degree=8)
            sage: TestSuite(A).run()
            sage: A = GradedCommutativeAlgebra(QQ, ('x','y','z','t'), [1,2,3,4], max_degree=10)
            sage: TestSuite(A).run()

        """
        from sage.arith.misc import gcd

        if max_degree not in ZZ:
            raise TypeError('max_degree must be an integer')
        if max_degree < max(degrees):
            raise ValueError(f'max_degree must not deceed {max(degrees)}')
        self._names = names
        self.__ngens = len(self._names)
        self._degrees = degrees
        self._max_deg = max_degree
        self._weighted_vectors = WeightedIntegerVectors(degrees)
        self._mul_symbol = kwargs.pop('mul_symbol', '*')
        self._mul_latex_symbol = kwargs.pop('mul_latex_symbol', '')
        step = gcd(degrees)
        universe = DisjointUnionEnumeratedSets(
            self._weighted_vectors.subset(k)
            for k in range(0, max_degree, step))
        base_cat = Algebras(
            base).WithBasis().Super().Supercommutative().FiniteDimensional()
        category = base_cat.or_subcategory(category, join=True)
        indices = ConditionSet(universe, self._valid_index)
        sorting_key = self._weighted_vectors.grading
        CombinatorialFreeModule.__init__(self,
                                         base,
                                         indices,
                                         sorting_key=sorting_key,
                                         category=category)
Example #5
0
def multiply_forms_to_weight(forms, weight, stop_dim=None):
    r"""
    Given a list of pairs ``(k,f)``, where `k` is an integer and `f` is a power
    series, and a weight l, return all weight l forms obtained by multiplying
    together the given forms. 

    INPUT:

    - forms -- list of pairs (k, f) with k an integer and f a power series
    - weight -- an integer
    - stop_dim -- integer (optional): if set to an integer and we find that the
      series so far span a space of at least this dimension, then stop
      multiplying more forms together.

    EXAMPLES::

        sage: import sage.modular.modform.find_generators as f
        sage: forms = [(4, 240*eisenstein_series_qexp(4,5)), (6,504*eisenstein_series_qexp(6,5))]
        sage: f.multiply_forms_to_weight(forms, 12)
        [(12, 1 - 1008*q + 220752*q^2 + 16519104*q^3 + 399517776*q^4 + O(q^5)), (12, 1 + 720*q + 179280*q^2 + 16954560*q^3 + 396974160*q^4 + O(q^5))]
        sage: f.multiply_forms_to_weight(forms, 24)
        [(24, 1 - 2016*q + 1457568*q^2 - 411997824*q^3 + 16227967392*q^4 + O(q^5)), (24, 1 - 288*q - 325728*q^2 + 11700864*q^3 + 35176468896*q^4 + O(q^5)), (24, 1 + 1440*q + 876960*q^2 + 292072320*q^3 + 57349833120*q^4 + O(q^5))]
        sage: dimension_modular_forms(SL2Z,24)
        3
    """
    verbose('multiplying forms up to weight %s'%weight)
    # Algorithm: run through the subsets of forms and for each check
    # whether or not the sum of the weights (with coefficients -- i.e.,
    # account for multiplicities) of the forms equals weight.
    # If so, multiply those together and append them to the output
    # list v

    # The answer list
    v = []
    n = len(forms)

    # List of weights
    from sage.combinat.integer_vector_weighted import WeightedIntegerVectors
    wts = WeightedIntegerVectors(weight, [f[0] for f in forms])
    
    for c in wts:
        if sum(c[i]*forms[i][0] for i in xrange(n) if c[i]) != weight:
            raise ArithmeticError, "Can't get here!"
        g = prod(forms[i][1]**c[i] for i in xrange(n))
        v.append((weight, g))
        if stop_dim and len(v) >= stop_dim:
            z = span_of_series([f for _, f in v]).dimension()                
            if z >= stop_dim:
                return v
    return v
Example #6
0
def abnormal_polynomials(L, r, m, s):
    r"""
    Return the highest degree coefficients for abnormal polynomials in step `s`
    of layer `m` in the Lie algebra `L`.

    INPUT:

    - ``L`` -- the Lie algebra to do the computations in
    - ``r`` -- the rank of the free Lie algebra
    - ``m`` -- the layer whose abnormal polynomials are computed
    - ``s`` -- the step of coefficients to compute

    OUTPUT:

    A dictionary {X: {mon: coeff}, where

    - ``X`` -- a Hall basis element of layer `m`
    - ``mon`` -- a tuple `(i_1,...,i_n)` describing the monomial
      `x_1^{i_1}...x_n^{i_n}`
    - ``coeff`` -- an element of a :class:`HallQuotient` Lie algebra whose dual
      element would be the coefficient of the monomial in the polynomial `P_X`
    """
    elems = sum((list(L.graded_basis(k)) for k in range(1, m)), [])
    weights = sum(([k] * len(L.graded_basis(k)) for k in range(1, m)), [])
    P = {}
    mons = list(reversed(list(WeightedIntegerVectors(s - m, weights))))
    verbose("computing abnormal polynomials:")
    for X in L.graded_basis(m):
        PX = {}
        pname = freebasis_element_to_short_word(X).replace("X", "P")
        verbose("  %s deg %d coefficients" % (pname, s - m))
        # compute coefficients for the abnormal polynomial P_X
        for mon in mons:
            # compute coefficient of the monomial mon
            adx = X
            mult = QQ(1)
            for i in mon:
                mult = mult / factorial(i)
            for rep, Xi in zip(mon, elems):
                for k in range(rep):
                    adx = Xi.bracket(adx)
            PX[tuple(mon)] = mult * adx
        P[X] = PX

    return P
Example #7
0
def gen_list(max_degree, R):
    ideal = ring_defining_ideal(R)
    gens = R.gens()
    monomials = [[R(1)]]

    for i in range(1, max_degree + 1):
        degs = WeightedIntegerVectors(i, [1] * len(gens))
        degree_ideal = R.ideal(0)
        monomials.append([])
        for part in degs:
            prod = R(1)
            for j in range(len(gens)):
                prod *= gens[j]**part[j]
            prod = ideal.reduce(prod)
            if prod != 0 and prod not in degree_ideal:
                monomials[i].append(prod)
                degree_ideal = R.ideal(monomials[i])

    return monomials
Example #8
0
def gen_list_piece(i,R,ideal_idx,ideal,gens):
    output = []
    partition_list = WeightedIntegerVectors(i,[1]*len(gens))
    degs = []
    for part in partition_list:
        degs.append(part)

    degree_ideal = R.ideal(0)

    for m, part in enumerate(degs):
        print('gen_list for '+str(ideal_idx)+': degree '+str(i)+', index '+str(m)+'/'+str(len(degs)))
        prod = R(1)
        for j in range(len(gens)):
            prod *= gens[j]**part[j]

        p2 = ideal.reduce(prod)
        test = is_elt_in_ideal(p2,degree_ideal)
        if not test:
            output.append(p2)
            degree_ideal += R.ideal(p2)
            print p2

    return output
Example #9
0
def atomic_basis_odd(n, basis, p, **kwds):
    r"""
    `P^s_t`-bases and commutator basis in dimension `n` at odd primes.

    This function is called ``atomic_basis_odd`` in analogy with
    :func:`atomic_basis`.

    INPUT:

    - ``n`` - non-negative integer
    - ``basis`` - string, the name of the basis
    - ``p`` - positive prime number

    - ``profile`` - profile function (optional, default None).
      Together with ``truncation_type``, specify the profile function
      to be used; None means the profile function for the entire
      Steenrod algebra.  See
      :mod:`sage.algebras.steenrod.steenrod_algebra` and
      :func:`SteenrodAlgebra` for information on profile functions.

    - ``truncation_type`` - truncation type, either 0 or Infinity
      (optional, default Infinity if no profile function is specified,
      0 otherwise).

    OUTPUT: tuple of basis elements in dimension n

    The only possible difference in the implementations for `P^s_t`
    bases and commutator bases is that the former make sense, and
    require filtering, if there is a nontrivial profile function.
    This function is called by :func:`steenrod_algebra_basis`, and it
    will not be called for commutator bases if there is a profile
    function, so we treat the two bases exactly the same.

    EXAMPLES::

        sage: from sage.algebras.steenrod.steenrod_algebra_bases import atomic_basis_odd
        sage: atomic_basis_odd(8, 'pst_rlex', 3)
        (((), (((0, 1), 2),)),)

        sage: atomic_basis_odd(18, 'pst_rlex', 3)
        (((0, 2), ()), ((0, 1), (((1, 1), 1),)))
        sage: atomic_basis_odd(18, 'pst_rlex', 3, profile=((), (2,2,2)))
        (((0, 2), ()),)
    """
    def sorting_pair(s, t, basis):  # pair used for sorting the basis
        if basis.find('rlex') >= 0:
            return (t, s)
        elif basis.find('llex') >= 0:
            return (s, t)
        elif basis.find('deg') >= 0:
            return (s + t, t)
        elif basis.find('revz') >= 0:
            return (s + t, s)

    generic = kwds.get('generic', False if p == 2 else True)
    if n == 0:
        if not generic:
            return ((), )
        else:
            return (((), ()), )

    from sage.rings.all import Integer
    from sage.rings.infinity import Infinity
    from sage.combinat.integer_vector_weighted import WeightedIntegerVectors
    profile = kwds.get("profile", None)
    trunc = kwds.get("truncation_type", 0)

    result = []
    for dim in range(n // (2 * p - 2) + 1):
        P_result = []
        for v in WeightedIntegerVectors(dim, xi_degrees(dim,
                                                        p=p,
                                                        reverse=False)):
            mono = []
            for t, a in enumerate(v):
                for s, pow in enumerate(Integer(a).digits(p)):
                    if pow > 0:
                        mono.append(((s, t + 1), pow))
            P_result.append(mono)
        for p_mono in P_result:
            p_mono.sort(key=lambda x: sorting_pair(x[0][0], x[0][1], basis))
            deg = n - 2 * dim * (p - 1)
            q_degrees = [
                1 + 2 * (p - 1) * d
                for d in xi_degrees((deg - 1) // (2 * (p - 1)), p)
            ] + [1]
            q_degrees_decrease = q_degrees
            q_degrees.reverse()
            if deg % (2 * (p - 1)) <= len(q_degrees):
                # if this inequality fails, no way to have a partition
                # with distinct parts.
                for sigma in restricted_partitions(deg,
                                                   q_degrees_decrease,
                                                   no_repeats=True):
                    index = 0
                    q_mono = []
                    for q in q_degrees:
                        if q in sigma:
                            q_mono.append(index)
                        index += 1
                    # check profile:
                    okay = True
                    if profile is not None and profile != ((), ()):
                        # check profile function for q_mono
                        for i in q_mono:
                            if ((len(profile[1]) > i and profile[1][i] == 1)
                                    or (len(profile[1]) <= i and trunc == 0)):
                                okay = False
                                break

                        for ((s, t), exp) in p_mono:
                            if ((len(profile[0]) > t - 1
                                 and profile[0][t - 1] <= s)
                                    or (len(profile[0]) <= t - 1
                                        and trunc < Infinity)):
                                okay = False
                                break

                    if okay:
                        if list(p_mono) == [0]:
                            p_mono = []
                        result.append((tuple(q_mono), tuple(p_mono)))
    return tuple(result)
Example #10
0
def milnor_basis(n, p=2, **kwds):
    r"""
    Milnor basis in dimension `n` with profile function ``profile``.

    INPUT:

    - ``n`` - non-negative integer

    - ``p`` - positive prime number (optional, default 2)

    - ``profile`` - profile function (optional, default None).
      Together with ``truncation_type``, specify the profile function
      to be used; None means the profile function for the entire
      Steenrod algebra.  See
      :mod:`sage.algebras.steenrod.steenrod_algebra` and
      :func:`SteenrodAlgebra <sage.algebras.steenrod.steenrod_algebra.SteenrodAlgebra>`
      for information on profile functions.

    - ``truncation_type`` - truncation type, either 0 or Infinity
      (optional, default Infinity if no profile function is specified,
      0 otherwise)

    OUTPUT: tuple of mod p Milnor basis elements in dimension n

    At the prime 2, the Milnor basis consists of symbols of the form
    `\text{Sq}(m_1, m_2, ..., m_t)`, where each
    `m_i` is a non-negative integer and if `t>1`, then
    `m_t \neq 0`. At odd primes, it consists of symbols of the
    form `Q_{e_1} Q_{e_2} ... P(m_1, m_2, ..., m_t)`,
    where `0 \leq e_1 < e_2 < ...`, each `m_i` is a
    non-negative integer, and if `t>1`, then
    `m_t \neq 0`.

    EXAMPLES::

        sage: from sage.algebras.steenrod.steenrod_algebra_bases import milnor_basis
        sage: milnor_basis(7)
        ((0, 0, 1), (1, 2), (4, 1), (7,))
        sage: milnor_basis(7, 2)
        ((0, 0, 1), (1, 2), (4, 1), (7,))
        sage: milnor_basis(4, 2)
        ((1, 1), (4,))
        sage: milnor_basis(4, 2, profile=[2,1])
        ((1, 1),)
        sage: milnor_basis(4, 2, profile=(), truncation_type=0)
        ()
        sage: milnor_basis(4, 2, profile=(), truncation_type=Infinity)
        ((1, 1), (4,))
        sage: milnor_basis(9, 3)
        (((1,), (1,)), ((0,), (2,)))
        sage: milnor_basis(17, 3)
        (((2,), ()), ((1,), (3,)), ((0,), (0, 1)), ((0,), (4,)))
        sage: milnor_basis(48, p=5)
        (((), (0, 1)), ((), (6,)))
        sage: len(milnor_basis(100,3))
        13
        sage: len(milnor_basis(200,7))
        0
        sage: len(milnor_basis(240,7))
        3
        sage: len(milnor_basis(240,7, profile=((),()), truncation_type=Infinity))
        3
        sage: len(milnor_basis(240,7, profile=((),()), truncation_type=0))
        0
    """
    generic = kwds.get('generic', False if p == 2 else True)

    if n == 0:
        if not generic:
            return ((), )
        else:
            return (((), ()), )

    from sage.rings.infinity import Infinity
    from sage.combinat.integer_vector_weighted import WeightedIntegerVectors
    profile = kwds.get("profile", None)
    trunc = kwds.get("truncation_type", None)
    if trunc is None:
        if profile is not None:
            trunc = 0
        else:
            trunc = Infinity

    result = []
    if not generic:
        for mono in WeightedIntegerVectors(n, xi_degrees(n, reverse=False)):
            exponents = list(mono)
            while exponents and exponents[-1] == 0:
                exponents.pop(-1)
            # check profile:
            okay = True
            if profile is not None and len(profile) > 0:
                for i in range(len(exponents)):
                    if ((len(profile) > i and exponents[i] >= 2**profile[i])
                            or (len(profile) <= i and trunc < Infinity
                                and exponents[i] >= 2**trunc)):
                        okay = False
                        break
            else:
                # profile is empty
                okay = (trunc == Infinity)
            if okay:
                result.append(tuple(exponents))
    else:  # p odd
        # first find the P part of each basis element.
        # in this part of the code (the P part), all dimensions are
        # divided by 2(p-1).
        for dim in range(n // (2 * (p - 1)) + 1):
            if dim == 0:
                P_result = [[0]]
            else:
                P_result = []
            for mono in WeightedIntegerVectors(
                    dim, xi_degrees(dim, p=p, reverse=False)):
                p_mono = list(mono)
                while p_mono and p_mono[-1] == 0:
                    p_mono.pop(-1)
                if p_mono:
                    P_result.append(p_mono)
            # now find the Q part of the basis element.
            # dimensions here are back to normal.
            for p_mono in P_result:
                deg = n - 2 * dim * (p - 1)
                q_degrees = [
                    1 + 2 * (p - 1) * d
                    for d in xi_degrees(int((deg - 1) // (2 * (p - 1))), p)
                ] + [1]
                q_degrees_decrease = q_degrees
                q_degrees.reverse()
                if deg % (2 * (p - 1)) <= len(q_degrees):
                    # if this inequality fails, no way to have a partition
                    # with distinct parts.
                    for sigma in restricted_partitions(deg,
                                                       q_degrees_decrease,
                                                       no_repeats=True):
                        index = 0
                        q_mono = []
                        for q in q_degrees:
                            if q in sigma:
                                q_mono.append(index)
                            index += 1
                        # check profile:
                        okay = True
                        if profile is not None and (len(profile[0]) > 0
                                                    or len(profile[1]) > 0):
                            # check profile function for q_mono
                            for i in q_mono:
                                if ((len(profile[1]) > i
                                     and profile[1][i] == 1) or
                                    (len(profile[1]) <= i and trunc == 0)):
                                    okay = False
                                    break
                            # check profile function for p_mono
                            for i in range(len(p_mono)):
                                if okay and (
                                    (len(profile[0]) > i
                                     and p_mono[i] >= p**profile[0][i]) or
                                    (len(profile[0]) <= i and trunc < Infinity
                                     and p_mono[i] >= p**trunc)):
                                    okay = False
                                    break
                        else:
                            # profile is empty
                            okay = (trunc == Infinity)
                        if okay:
                            if list(p_mono) == [0]:
                                p_mono = []
                            result.append((tuple(q_mono), tuple(p_mono)))
    return tuple(result)
Example #11
0
def _span_of_forms_in_weight(forms, weight, prec, stop_dim=None, use_random=False):
    r"""
    Utility function. Given a nonempty list of pairs ``(k,f)``, where `k` is an
    integer and `f` is a power series, and a weight l, return all weight l
    forms obtained by multiplying together the given forms.

    INPUT:

    - ``forms`` -- list of pairs `(k, f)` with k an integer and f a power
      series (all over the same base ring)
    - ``weight`` -- an integer
    - ``prec`` -- an integer (less than or equal to the precision of all the
      forms in ``forms``) -- precision to use in power series computations.
    - ``stop_dim`` -- an integer: stop as soon as we have enough forms to span
      a submodule of this rank (a saturated one if the base ring is `\ZZ`).
      Ignored if ``use_random`` is False.
    - ``use_random`` -- which algorithm to use. If True, tries random products
      of the generators of the appropriate weight until a large enough
      submodule is found (determined by ``stop_dim``). If False, just tries
      everything.

    Note that if the given forms do generate the whole space, then
    ``use_random=True`` will often be quicker (particularly if the weight is
    large); but if the forms don't generate, the randomized algorithm is no
    help and will actually be substantially slower, because it needs to do
    repeated echelon form calls to check if vectors are in a submodule, while
    the non-randomized algorithm just echelonizes one enormous matrix at the
    end.

    EXAMPLES::

        sage: import sage.modular.modform.find_generators as f
        sage: forms = [(4, 240*eisenstein_series_qexp(4,5)), (6,504*eisenstein_series_qexp(6,5))]
        sage: f._span_of_forms_in_weight(forms, 12, prec=5)
        Vector space of degree 5 and dimension 2 over Rational Field
        Basis matrix:
        [        1         0    196560  16773120 398034000]
        [        0         1       -24       252     -1472]
        sage: f._span_of_forms_in_weight(forms, 24, prec=5)
        Vector space of degree 5 and dimension 3 over Rational Field
        Basis matrix:
        [          1           0           0    52416000 39007332000]
        [          0           1           0      195660    12080128]
        [          0           0           1         -48        1080]
        sage: ModularForms(1, 24).q_echelon_basis(prec=5)
        [
        1 + 52416000*q^3 + 39007332000*q^4 + O(q^5),
        q + 195660*q^3 + 12080128*q^4 + O(q^5),
        q^2 - 48*q^3 + 1080*q^4 + O(q^5)
        ]

    Test the alternative randomized algorithm::

        sage: f._span_of_forms_in_weight(forms, 24, prec=5, use_random=True, stop_dim=3)
        Vector space of degree 5 and dimension 3 over Rational Field
        Basis matrix:
        [          1           0           0    52416000 39007332000]
        [          0           1           0      195660    12080128]
        [          0           0           1         -48        1080]
    """
    t = verbose('multiplying forms up to weight %s'%weight)
    # Algorithm: run through the monomials of the appropriate weight, and build
    # up the vector space they span.

    n = len(forms)
    R = forms[0][1].base_ring()
    V = R ** prec
    W = V.zero_submodule()
    shortforms = [f[1].truncate_powerseries(prec) for f in forms]

    # List of weights
    from sage.combinat.integer_vector_weighted import WeightedIntegerVectors
    wts = list(WeightedIntegerVectors(weight, [f[0] for f in forms]))
    t = verbose("calculated weight list", t)
    N = len(wts)

    if use_random:
        if stop_dim is None:
            raise ValueError("stop_dim must be provided if use_random is True")
        shuffle(wts)

        for c in xrange(N):
            w = V(prod(shortforms[i]**wts[c][i] for i in xrange(n)).padded_list(prec))
            if w in W: continue
            W = V.span(list(W.gens()) + [w])
            if stop_dim and W.rank() == stop_dim:
                if R != ZZ or W.index_in_saturation() == 1:
                    verbose("Succeeded after %s of %s" % (c, N), t)
                    return W
        verbose("Nothing worked", t)
        return W
    else:
        G = [V(prod(forms[i][1]**c[i] for i in xrange(n)).padded_list(prec)) for c in wts]
        t = verbose('found %s candidates' % N, t)
        W = V.span(G)
        verbose('span has dimension %s' % W.rank(), t)
        return W
Example #12
0
class FiniteGCAlgebra(CombinatorialFreeModule, Algebra):
    r"""
    Finite dimensional graded commutative algebras.

    A finite dimensional graded commutative algebra `A` is an integer-graded
    algebra satisfying the super-algebra relation w.r.t. the degree modulo 2.
    More precisely, `A` has a graded ring structure

    .. MATH::

        A = \bigoplus_{i=0}^n A_i,

    where `n \in \NN` is the finite maximal degree, and the multiplication
    satisfies

    .. MATH::

        A_i \cdot A_j \subset \begin{cases}A_{i+j} & \text{if $i+j\leq n$}, \\
                                             0 & \text{if $i+j > n$},\end{cases}

    as well as the super-algebra relation

    .. MATH::

        x y = (-1)^{ij} y x

    for all homogeneous elements `x \in A_i` and `y \in A_j`.

    Such an algebra is multiplicatively generated by a set of single monomials
    `\{ x_1, \ldots, x_k \}`, where each `x_i` is given a certain degree
    `\mathrm{deg}(x_i)`. To that end, this algebra can be given a vector
    space basis, and the basis vectors are of the form `x_1^{w_1} \cdots x_n^{
    w_k}`, where `\sum_{i=1}^k \mathrm{deg}(x_i) \, w_i \leq n` and

    .. MATH::

        w_i \in \begin{cases} \ZZ_2 & \text{if $\mathrm{deg}(x_i)$ is odd}, \\
        \NN & \text{if $\mathrm{deg}(x_i)$ is even}. \end{cases}

    Typical examples of finite dimensional graded commutative algebras are
    cohomology rings over finite dimensional CW-complexes.

    INPUT:

    - ``base`` -- the base field
    - ``names`` -- (optional) names of the generators: a list of
      strings or a single string with the names separated by
      commas. If not specified, the generators are named "x0", "x1",...
    - ``degrees`` -- (optional) a tuple or list specifying the degrees
      of the generators; if omitted, each generator is given degree
      1, and if both ``names`` and ``degrees`` are omitted, an error is
      raised.
    - ``max_degree`` -- the maximal degree of the graded algebra.
    - ``mul_symbol`` -- (optional) symbol used for multiplication. If omitted,
      the string "*" is used.
    - ``mul_latex_symbol`` -- (optional) latex symbol used for multiplication.
      If omitted, the empty string is used.

    EXAMPLES::

        sage: A.<x,y,z,t> = GradedCommutativeAlgebra(QQ, degrees=(1,2,2,3), max_degree=6)
        sage: A
        Graded commutative algebra with generators ('x', 'y', 'z', 't') in degrees (1, 2, 2, 3) with maximal degree 6
        sage: t*x + x*t
        0
        sage: x^2
        0
        sage: x*t^2
        0
        sage: x*y^2+z*t
        x*y^2 + z*t

    The generators can be returned with :meth:`algebra_generators`::

        sage: F = A.algebra_generators(); F
        Family (x, y, z, t)
        sage: [g.degree() for g in F]
        [1, 2, 2, 3]

    We can also return the basis::

        sage: list(A.basis())
        [1, x, z, y, t, x*z, x*y, x*t, z^2, y*z, y^2, z*t, y*t, x*z^2, x*y*z, x*y^2]

    Depending on the context, the multiplication can be given a different
    symbol::

        sage: A.<x,y,z,t> = GradedCommutativeAlgebra(QQ, degrees=(1,2,6,6), max_degree=10, mul_symbol='⌣', mul_latex_symbol=r'\smile')
        sage: x*y^2 + x*t
        x⌣y^2 + x⌣t
        sage: latex(x*y^2 - z*x)
        x\smile y^{2} - x\smile z

    .. NOTE::

        Notice, when the argument ``max_degree`` in the global namespace is
        omitted, an instance of the class
        :class:`sage.algebras.commutative_dga.GCAlgebra` is created instead::

            sage: A.<x,y,z,t> = GradedCommutativeAlgebra(QQ, degrees=(1,2,6,6))
            sage: type(A)
            <class 'sage.algebras.commutative_dga.GCAlgebra_with_category'>

    """
    @staticmethod
    def __classcall_private__(cls,
                              base,
                              names=None,
                              degrees=None,
                              max_degree=None,
                              category=None,
                              **kwargs):
        r"""
        Normalize the input for the :meth:`__init__` method and the
        unique representation.

        INPUT:

        - ``base`` -- the base ring of the algebra
        - ``max_degree`` -- the maximal degree of the algebra
        - ``names`` -- the names of the variables; by default, set to ``x1``,
          ``x2``, etc.
        - ``degrees`` -- the degrees of the generators; by default, set to 1

        TESTS::

            sage: A1 = GradedCommutativeAlgebra(GF(2), 'x,y', (3, 6), max_degree=12)
            sage: A2 = GradedCommutativeAlgebra(GF(2), ['x', 'y'], [3, 6], max_degree=12)
            sage: A1 is A2
            True

        """
        if max_degree is None:
            raise TypeError("max_degree must be specified")
        if names is None:
            if degrees is None:
                raise ValueError("You must specify names or degrees")
            else:
                n = len(degrees)
            names = tuple('x{}'.format(i) for i in range(n))
        elif isinstance(names, str):
            names = tuple(names.split(','))
            n = len(names)
        else:
            n = len(names)
            names = tuple(names)
        if degrees is None:
            degrees = tuple([1 for _ in range(n)])
        else:
            degrees = tuple(degrees)

        return super().__classcall__(cls,
                                     base=base,
                                     names=names,
                                     degrees=degrees,
                                     max_degree=max_degree,
                                     category=category,
                                     **kwargs)

    def __init__(self,
                 base,
                 names,
                 degrees,
                 max_degree,
                 category=None,
                 **kwargs):
        r"""
        Construct a commutative graded algebra with finite degree.

        TESTS::

            sage: A.<x,y,z,t> = GradedCommutativeAlgebra(QQ, max_degree=6)
            sage: TestSuite(A).run()
            sage: A = GradedCommutativeAlgebra(QQ, ('x','y','z'), [2,3,4], max_degree=8)
            sage: TestSuite(A).run()
            sage: A = GradedCommutativeAlgebra(QQ, ('x','y','z','t'), [1,2,3,4], max_degree=10)
            sage: TestSuite(A).run()

        """
        from sage.arith.misc import gcd

        if max_degree not in ZZ:
            raise TypeError('max_degree must be an integer')
        if max_degree < max(degrees):
            raise ValueError(f'max_degree must not deceed {max(degrees)}')
        self._names = names
        self.__ngens = len(self._names)
        self._degrees = degrees
        self._max_deg = max_degree
        self._weighted_vectors = WeightedIntegerVectors(degrees)
        self._mul_symbol = kwargs.pop('mul_symbol', '*')
        self._mul_latex_symbol = kwargs.pop('mul_latex_symbol', '')
        step = gcd(degrees)
        universe = DisjointUnionEnumeratedSets(
            self._weighted_vectors.subset(k)
            for k in range(0, max_degree, step))
        base_cat = Algebras(
            base).WithBasis().Super().Supercommutative().FiniteDimensional()
        category = base_cat.or_subcategory(category, join=True)
        indices = ConditionSet(universe, self._valid_index)
        sorting_key = self._weighted_vectors.grading
        CombinatorialFreeModule.__init__(self,
                                         base,
                                         indices,
                                         sorting_key=sorting_key,
                                         category=category)

    def _valid_index(self, w):
        r"""
        Return whether ``w`` is a valid index; no multiple powers in odd
        degrees.

        TESTS::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(1,2,3), max_degree=8)
            sage: w1 = A._weighted_vectors([1,2,1])
            sage: w2 = A._weighted_vectors([1,2,2])
            sage: A._valid_index(w1)
            True
            sage: A._valid_index(w2)
            False

        """
        return not any(i > 1 for i, d in zip(w, self._degrees) if is_odd(d))

    def _repr_(self):
        """
        Return the string representation of ``self``.

        TESTS::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(1,2,3), max_degree=8)
            sage: A._repr_()
            "Graded commutative algebra with generators ('x', 'y', 'z') in degrees (1, 2, 3) with maximal degree 8"
            sage: A  # indirect doctest
            Graded commutative algebra with generators ('x', 'y', 'z') in degrees (1, 2, 3) with maximal degree 8

        """
        desc = f'Graded commutative algebra with generators {self._names} in '
        desc += f'degrees {self._degrees} with maximal degree {self._max_deg}'
        return desc

    def ngens(self):
        r"""
        Return the number of generators of ``self``.

        EXAMPLES::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(4,8,2), max_degree=10)
            sage: A.ngens()
            3

        """
        return self.__ngens

    @cached_method
    def product_on_basis(self, w1, w2):
        r"""
        Return the product of two indices within the algebra.

        EXAMPLES::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(4,8,2), max_degree=10)
            sage: z*x
            x*z
            sage: x^3
            0
            sage: 5*z + 4*z*x
            5*z + 4*x*z

        ::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(1,2,3), max_degree=5)
            sage: 2*x*y
            2*x*y
            sage: x^2
            0
            sage: x*z
            x*z
            sage: z*x
            -x*z
            sage: x*y*z
            0

        TESTS::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(4,8,2), max_degree=10)
            sage: weighted_vectors = A._weighted_vectors
            sage: w1 = A._weighted_vectors([1,0,1])
            sage: w2 = A._weighted_vectors([0,0,0])
            sage: A.product_on_basis(w1, w2)
            x*z

        ::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(1,2,3), max_degree=5)
            sage: weighted_vectors = A._weighted_vectors
            sage: w1 = A._weighted_vectors([1,0,0])
            sage: w2 = A._weighted_vectors([0,0,1])
            sage: A.product_on_basis(w1, w2)
            x*z
            sage: A.product_on_basis(w2, w1)
            -x*z

        ::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(1,2,3), max_degree=10)
            sage: weighted_vectors = A._weighted_vectors
            sage: w1 = A._weighted_vectors([1,1,0])
            sage: w2 = A._weighted_vectors([0,1,1])
            sage: A.product_on_basis(w1, w2)
            x*y^2*z
            sage: A.product_on_basis(w2, w1)
            -x*y^2*z

        """
        grading = self._weighted_vectors.grading
        deg_left = grading(w1)
        deg_right = grading(w2)
        deg_tot = deg_left + deg_right
        if deg_tot > self._max_deg:
            return self.zero()
        w_tot = self._weighted_vectors([sum(w) for w in zip(w1, w2)])
        if not self._valid_index(w_tot):
            return self.zero()
        # determine sign
        n = self.__ngens
        c = 0
        for p, i, d in zip(reversed(range(n)), reversed(w1),
                           reversed(self._degrees)):
            if is_even(d) or i == 0:
                continue
            for q, j, b in zip(range(n), w2, self._degrees):
                if q == p:
                    break
                if j == 0 or is_even(b):
                    continue
                c += 1
        return (-1)**c * self.monomial(w_tot)

    def degree_on_basis(self, i):
        r"""
        Return the degree of a homogeneous element with index `i`.

        EXAMPLES::

            sage: A.<a,b,c> = GradedCommutativeAlgebra(QQ, degrees=(2,4,6), max_degree=7)
            sage: a.degree()
            2
            sage: (2*a*b).degree()
            6
            sage: (a+b).degree()
            Traceback (most recent call last):
            ...
            ValueError: element is not homogeneous

        TESTS::

            sage: A.<a,b,c> = GradedCommutativeAlgebra(QQ, degrees=(2,4,6), max_degree=7)
            sage: weighted_vectors = A._weighted_vectors
            sage: i = A._weighted_vectors([1,1,0])
            sage: A.degree_on_basis(i)
            6

        """
        return self._weighted_vectors.grading(i)

    def _repr_term(self, w):
        r"""
        Return the string representation of basis with index ``w``.

        TESTS::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(1,2,3), max_degree=8)
            sage: w = A._weighted_vectors([1,2,1])
            sage: A._repr_term(w)
            'x*y^2*z'
            sage: x*y^2*z  # indirect doctest
            x*y^2*z

        ::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(1,2,3), max_degree=8, mul_symbol='⌣')
            sage: w = A._weighted_vectors([1,2,1])
            sage: A._repr_term(w)
            'x⌣y^2⌣z'
            sage: x*y^2*z  # indirect doctest
            x⌣y^2⌣z

        """
        # Trivial case:
        if sum(w) == 0:
            return '1'
        # Non-trivial case:
        terms = []
        for i in range(len(w)):
            if w[i] == 0:
                continue
            elif w[i] == 1:
                terms.append(self._names[i])
            else:
                terms.append(self._names[i] + f'^{w[i]}')
        return self._mul_symbol.join(terms)

    def _latex_term(self, w):
        r"""
        Return the LaTeX representation of basis with index ``w``.

        TESTS::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(1,2,3), max_degree=8)
            sage: w = A._weighted_vectors([1,2,1])
            sage: A._latex_term(w)
            'x y^{2} z'
            sage: latex(x*y^2*z)  # indirect doctest
            x y^{2} z

        ::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(1,2,3), max_degree=8, mul_latex_symbol=r'\smile')
            sage: A._latex_term(w)
            'x\\smile y^{2}\\smile z'
            sage: latex(x*y^2*z)  # indirect doctest
            x\smile y^{2}\smile z

        """
        # Trivial case:
        if sum(w) == 0:
            return '1'
        # Non-trivial case:
        terms = []
        for i in range(len(w)):
            if w[i] == 0:
                continue
            elif w[i] == 1:
                terms.append(self._names[i])
            else:
                terms.append(self._names[i] + '^{' + str(w[i]) + '}')
        latex_mul = self._mul_latex_symbol + ' '  # add whitespace
        return latex_mul.join(terms)

    def algebra_generators(self):
        r"""
        Return the generators of ``self`` as a
        :class:`sage.sets.family.TrivialFamily`.

        EXAMPLES::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(4,8,2), max_degree=10)
            sage: A.algebra_generators()
            Family (x, y, z)

        """
        from sage.sets.family import Family

        return Family(self.gens())

    @cached_method
    def one_basis(self):
        r"""
        Return the index of the one element of ``self``.

        EXAMPLES::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(4,8,2), max_degree=10)
            sage: ind = A.one_basis(); ind
            [0, 0, 0]
            sage: A.monomial(ind)
            1
            sage: A.one()  # indirect doctest
            1

        """
        n = len(self._degrees)
        return self._weighted_vectors([0 for _ in range(n)])

    def gens(self):
        r"""
        Return the generators of ``self`` as a list.

        EXAMPLES::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(4,8,2), max_degree=10)
            sage: A.gens()
            [x, y, z]

        """
        n = len(self._degrees)
        zero = [0 for _ in range(n)]
        indices = []
        for k in range(n):
            ind = list(zero)
            ind[k] = 1
            indices.append(self._weighted_vectors(ind))
        return [self.monomial(ind) for ind in indices]

    @cached_method
    def gen(self, i):
        r"""
        Return the `i`-th generator of ``self``.

        EXAMPLES::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(4,8,2), max_degree=10)
            sage: A.gen(0)
            x
            sage: A.gen(1)
            y
            sage: A.gen(2)
            z

        """
        return self.gens()[i]

    def maximal_degree(self):
        r"""
        Return the maximal degree of ``self``.

        EXAMPLES::

            sage: A.<x,y,z> = GradedCommutativeAlgebra(QQ, degrees=(1,2,3), max_degree=8)
            sage: A.maximal_degree()
            8

        """
        return self._max_deg

    max_degree = maximal_degree
Example #13
0
 def monomial_list(deg):
     return reversed(list(WeightedIntegerVectors(deg, weights)))