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
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
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)
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 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
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
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
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
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)
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)
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
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
def monomial_list(deg): return reversed(list(WeightedIntegerVectors(deg, weights)))