from sage.algebras.commutative_dga import * from sage.structure.parent import Parent from sage.structure.element import Element from sage.structure.richcmp import rich_to_bool, op_EQ, op_NE, op_LT, op_LE, op_GT, op_GE import copy class AplN(DifferentialGCAlgebra_multigraded, UniqueRepresentation): """ A differential multi-graded commutative algebra This class extends the DifferentialGCAlgebra_multigraded class to create the algebra of polynomial differential forms with coefficients in QQ INPUT: - ``A`` -- a multi-graded commutative algebra - ``differential`` -- the differential rules """ def __init__(self, A, differential): """ Initialize ``self``. INPUT: - ``A`` -- a multi-graded commutative algebra - ``differential`` -- a differential """ self.n = len(A._names)//2-1 DifferentialGCAlgebra_multigraded.__init__(self, A, differential) self.t_relation = sum(self.gens()[:self.n+1])-1 self.y_relation = sum(self.gens()[self.n+1:]) def _base_repr(self): """ Return the base string representation of ``self``. """ s = DifferentialGCAlgebra._base_repr(self) old = '{}'.format(self._degrees) new = '{}'.format([i[0] for i in self._degrees_multi]) return s.replace(old, new) def integrand(self, value): """ Returns a polynomial whose definite integral is equal to 'value' OUTPUT: - A polynomial """ if (value in QQ) or ((value in A) and (value.degree().is_zero())): return value*factorial(self.n)*prod(self.gens()[self.n+2:]) else: raise ValueError("only field elements can be integrated.") class Element(GCAlgebra_multigraded.Element, DifferentialGCAlgebra.Element): """ Element class of the commutative differential multi-graded algebra (Apl)n. """ def is_homogeneous(self): """ Return "True" if "self" is homogeneous and "False" otherwise. """ degrees = set([k[0] for k in self.homogeneous_parts()]) return len(degrees) == 1 def total_degree(self): """ Return the total degree of this element. """ degrees = set([k[0] for k in self.homogeneous_parts()]) return max(degrees) def homogeneous_part(self,degree): """ Returns the homogeneous part of the element for a given degree. """ if (degree<0): raise ValueError('`degree` must be a non-negative integer') parts = [self.homogeneous_parts()[k] for k in self.homogeneous_parts() if list(k)[0]==degree] return self.parent()(sum(parts)) def reduce(self, relations=[]): """" The method reduces the polynomial by the Ideal INPUT:: - p: polynomial in variables t_0,..t_n,y_0,...,y_n - relations (optional): if relations are given, the polynomial is reduced following this rules. If no relations are given, the polynomial is reduced taking into account the relations of the parent algebra. OUTPUT:: - The polynomial after applying the corresponding relations """ apl = self.parent() if relations: return DifferentialGCAlgebra.Element.reduce(self, relations) else: return DifferentialGCAlgebra.Element.reduce(self, [apl.t_relation, apl.y_relation]) def _integrable_part(self): """ The method returns the coefficient in Q[t_1,...,t_n] of the term of degree `n` OUTPUT: - A polynomial in the ring Q. (After reducing the variables t_{0},y_{0} by the corresponding relations, the algorithm removes the variables y_{1},..,y_{n}). """ apl = self.parent() pol = apl(self.reduce()) #needed in case reduce() returns an integer relations = [] for i in range(apl.n+1): relations.append(apl.gens()[apl.n+1+i]-1) pol = apl(pol.homogeneous_part(degree=apl.n)) return apl(pol.reduce(relations=relations)) def integral(self): """ Returns the definite integral of the term of degree `n` OUTPUT: - The rational number obtained by the definite integral of the term of degree `n` """ apl = self.parent() pol = self._integrable_part() result = 0 for k in pol.dict().keys(): # k is a monomial numerator = 1 denominator = apl.n for e in k: # e is an exponents numerator *= factorial(e) denominator += e coef = pol.dict()[k] result += coef*numerator/factorial(denominator) #return ((-1)**(apl.n*(apl.n-1)/2))*result return result def _get_term(self, coef, exp, gens): """" Returns the term specified by its coefficient, exponents and generators INPUT:: - coef: The coefficient of the term - exp: A tuple with the exponents of each variable - gens: The generators OUTPUT:: - A polynomial """ return coef*prod(list(map(lambda exp, gen: gen**exp, exp, gens))) def _get_terms(self): """" Returns self seen as a collection of terms in the variables y_j with coefficients in QQ OUTPUT:: - A dictionary with all terms """ a = self.parent() n = len(a.gens())//2 ts_only_gens = a.gens()[:n]+tuple(n*[1]) ys_only_gens = tuple(n*[1])+a.gens()[n:] terms = {} pol = self.dict() for exp in pol.keys(): key = self._get_term(1, exp,ys_only_gens) coef = self._get_term(pol[exp],exp,ts_only_gens) if key not in terms.keys(): terms[key] = coef else: terms[key] += coef return terms def _get_leading_pol(self): """" Returns the leading polynomial OUTPUT:: - A polynomial """ terms = self._get_terms() keys = list(terms.keys()) keys.sort(reverse=True) return terms[keys[0]]*keys[0] def _get_idx(self, exp): """" Returns the idx 'i' of the first generator 'y_i' that appears in exp INPUT:: - exp: A tuple with the exponents OUTPUT:: - A non-negative number lower or equal to 'n' """ n = len(exp)//2 filter_ts = n*[0]+n*[1] diff_forms = list(map(lambda v, f: v*f, exp, filter_ts)) return next((i for i, x in enumerate(list(diff_forms)) if x), None)-n def _monomial_primitive(self, monomial, idx): """" Returns the primitive of the given monomial INPUT:: - monomial: A monomial - idx: the index of the variable y_j that will be used to calculate the pseudo-primitive OUTPUT:: - A polynomial """ exp = list(monomial.dict().keys())[0] coef = monomial.dict()[exp] n = len(exp)//2 if (exp[n+idx]==0): raise IndexError("It is not possible to get a primitive using the given idx.") primitive = list(copy.deepcopy(exp)) primitive[n+idx] = 0 primitive[idx] += 1 new_coef = coef/primitive[idx] a = monomial.parent() return self._get_term(new_coef, primitive, a.gens()) def _leading_primitive(self): """" Returns the primitive of the leading term of self OUTPUT:: - A polynomial """ polynomial = self._get_leading_pol() # necessary condition: all monomials have apl.gens[n+idx] factor exp = list(polynomial.dict().keys())[0] idx = self._get_idx(exp) aGens = polynomial.parent().gens() primitive = 0 for exp in polynomial.dict().keys(): coef = polynomial.dict()[exp] monomial = self._get_term(coef, exp, aGens) primitive += self._monomial_primitive(monomial, idx) terms = primitive._get_terms() result = sum([terms[key]*key for key in terms.keys()]) return result def primitive(self): """" Retuns a polynomial whose differential is self OUTPUT:: - A polynomial """ if self.differential()!=0 : raise ValueError("This polynomial is not closed.") w = self._leading_primitive() theta = self - w.differential() if (theta == 0): return w else: return w+theta.primitive() def AlgebraPL(n): """ The graded commutative differential cochain algebra Apl of dimension ``n`` INPUT: - ``n`` -- A non-negative integer corresponding to the number of generators of degree 0. """ if (n<0): raise ValueError('`n` must be a non-negative integer') ts = ['t'+str(t) for t in range(n+1)] ys = ['y'+str(t) for t in range(n+1)] degrees = tuple((n+1)*[(0,2)])+tuple((n+1)*[(1,2)]) gca = GradedCommutativeAlgebra(QQ, names=ts+ys, degrees=degrees) differentials = {gca.gens_dict()[ts[i]]:gca.gens_dict()[ys[i]] for i in range(n+1)} return AplN(gca,differentials) ############################################# # TEST # print("Test AlgebraPL") #A = AlgebraPL(5) #A.inject_variables() #p = t0**2 + t1 + y3 + y1*y2 #q = p.differential() ############################################# class SimplicialAplN(AplN, UniqueRepresentation): """ The simplicial commutative cochain algebra Apl """ def _base_repr(self): """ Return the base string representation of ``self``. """ s = DifferentialGCAlgebra._base_repr(self) old = '{}'.format(self._degrees) new = '{}'.format([i[0] for i in self._degrees_multi]) return "Simplicial " + s.replace(old, new) class Element(AplN.Element): """ Element class of the simplicial commutative differential algebra (Apl)n. """ def face_morphism(self,i): """" Returns the morphism face 'i' of self INPUT:: - i: A non-negative integer lower or equal to 'n'. Represents the face index OUTPUT:: - A morphism of cochain algebras """ n = self.parent().n if(n==0): return 0 domain = self.parent() codomain = SimplicialAlgebraplN(n-1) tImages = [] yImages = [] n = len(domain.gens())//2 for k in range(n): if(ki): tImages.append(codomain.gens()[k-1]) yImages.append(codomain.gens()[n+k-2]) images = tImages+yImages return domain.hom(codomain=codomain,im_gens=images) def face(self,i): """" Returns the face 'i' of self INPUT:: - i: A non-negative integer lower or equal to 'n'. Represents the face index OUTPUT:: - A polynomial that is the face 'i' of self """ return self.face_morphism(i)(self) def degeneration_morphism(self,j): """" Returns the morphism degeneration 'j' of self INPUT:: - j: A non-negative integer lower or equal to 'n'. Represents the degeneration index OUTPUT:: - A morphism of cochain algebras """ n = self.parent().n domain = self.parent() codomain = SimplicialAlgebraplN(n+1) if(j>n): print("the degeneration index must be a non-negative number lower or equal to "+str(n)) return False elif(j<0): print("the degeneration index must be non-negative") return False else: tImages = [] yImages = [] n = len(domain.gens())//2 for k in range(n): if(kj): tImages.append(codomain.gens()[k+1]) yImages.append(codomain.gens()[n+1+k+1]) images = tImages+yImages return domain.hom(codomain=codomain,im_gens=images) def degeneration(self,j): """" Returns the degeneration 'j' of self INPUT:: - j: A non-negative integer lower or equal to 'n'. Represents the degeneration index OUTPUT:: - A polynomial that is the degeneration 'j' of self """ return self.degeneration_morphism(j)(self) def propagate(self, face_list, dest_dim): """ Returns a polynomial in dimension 'dest_dim' that has 'self' as a face INPUT: - face_list: indexes of the elements that form the face of the simplex we want to propagate - dest_dim: Dimension of the codomain cochain algebra - EXAMPLE: s = SimplicialAlgebraplN(2) s.inject_variables() p = (t1^2+t2^2)*y1*y2 p.propagate2([0,1,2],3) t1^2*t2*y0*y1 + t2^3*y0*y1 - t1^3*y0*y2 - t1*t2^2*y0*y2 + t0*t1^2*y1*y2 + t0*t2^2*y1*y2 """ if not (self.is_homogeneous() and self.total_degree()==self.parent().n): raise ValueError("The polynomial is not homogeneous of maximum degree.") self_dim = self.parent().n indexes = list(range(self_dim+1)) apl = SimplicialAlgebraplN(dest_dim) y_gens = [apl.gens()[dest_dim+1+i] for i in face_list] t_gens = [apl.gens()[i] for i in face_list] graded_part = apl(0) for i in reversed(indexes): t = t_gens[i] y_prod = copy.deepcopy(y_gens) del y_prod[i] y_prod = prod(y_prod) graded_part += ((-1)**i)*t*y_prod#((-1)**(simplex_dim+i))*t*y_prod terms = self.dict() coeff_in_t = apl(0) for exp in terms.keys(): coef = terms[exp] ts_exp = exp[:self_dim+1] coeff_in_t += coef*prod(list(map(lambda t, e: t**e, t_gens, ts_exp))) return coeff_in_t*graded_part def SimplicialAlgebraplN(n): """ Returns the graded commutative differential cochain algebra (Apl)_n as part of the simplicial cochain algebra A_{PL} INPUT: - ``n`` -- A non-negative integer corresponding to the number of generators of degree 0. """ if (n<0): raise ValueError('`n` must be a non-negative integer') ts = ['t'+str(t) for t in range(n+1)] ys = ['y'+str(t) for t in range(n+1)] degrees = tuple((n+1)*[(0,2)])+tuple((n+1)*[(1,2)]) gca = GradedCommutativeAlgebra(QQ, names=ts+ys, degrees=degrees) differentials = {gca.gens_dict()[ts[i]]:gca.gens_dict()[ys[i]] for i in range(n+1)} return SimplicialAplN(gca,differentials) ############################################# # TEST #print("TEST SimplicialAlgebraN") # s = SimplicialAlgebraplN(5) # s.inject_variables() # ## face and degeneration maps # print("Test face and degeneration maps") # p = t1+y2 # p.degeneration(0).face(1)==p # m1 = p.degeneration(1).face_morphism(1) # m2 = p.degeneration_morphism(1) # print((m1*m2).is_identity()) # true # ## Propagation # print("Test progatation") # s = SimplicialAlgebraplN(2) # s.inject_variables() # p = (t1**2+t2**2)*y1*y2 # q = p.propagate([1,5,7],10) # print(q) # print(q.face(10).face(9).face(8).face(6).face(4).face(3).face(2).face(0).reduce() == p) ############################################# class AlgebraAplK(UniqueRepresentation, Parent): """ The graded commutative differential cochain algebra Apl(K) """ def __init__(self,simplicial_complex, category=None): """ The constructor of Apl(K) INPUT: - simplicial_complex: The simplicial complex used to build the algebra """ self.K = simplicial_complex self.n = simplicial_complex.dimension() Parent.__init__(self,category=category) f = MorphismFromQQToAplK(self) self.register_coercion(f) def _repr_(self): """ Return the base string representation of ``self``. """ return "The graded commutative differential cochain algebra Apl(K) of %s" % self.K def _element_constructor_(self,values): """ The constructor of an element of this class """ return self.element_class(self,values) def dimension(self): """ The dimension of the algebra. It is equivalent to the maximum degree possible that a non-null element of this algebra can take. """ return self.n def simplicialComplex(self): """ The simplicial complex of this algebra. """ return self.K def _n_cells_sorted(self,n): """ Returns all simplices of dimension 'n', as a sorted list. """ return self.K._n_cells_sorted(n) def _faces_of_length(self, simplex, length): """ Returns all faces of dimension 'length' for a given simplex """ return [tuple(e) for e in Set(simplex).subsets(length)] def null_element(self): """ Returns the null element of the algebra respect to the addition operation """ values = {} for i in range(self.K.dimension()+1): s = SimplicialAlgebraplN(i) for f in self.K._n_cells_sorted(i): values[tuple(f)]=s(0) return self._element_constructor_(values) class Element(Element): """ Element class of the simplicial commutative differential algebra (Apl)n. """ def __init__(self, parent, values): Element.__init__(self,parent) self.elements = self._reduce(values) self.check() def _repr_(self): """ Return the base string representation of ``self``. """ return str(self.elements) def dimension(self): """ Returns the dimension where 'self' lives """ return self.parent().dimension() def _add_(self,other): """ Addition operation """ C = self.__class__ if(self.elements.keys()!=other.elements.keys()): raise ValueError("error: keys do not match") newValues = {} for k in self.elements.keys(): newValues[k] = self.elements[k]+other.elements[k] return C(self.parent(),newValues) def _sub_(self,other): """ Substration operation """ C = self.__class__ if(self.elements.keys()!=other.elements.keys()): raise ValueError("error: keys do not match") newValues = {} for k in self.elements.keys(): newValues[k] = self.elements[k]-other.elements[k] return C(self.parent(),newValues) def _mul_(self,other): """ Multiplication operation """ C = self.__class__ if(self.elements.keys()!=other.elements.keys()): raise ValueError("error: keys do not match") newValues = {} for k in self.elements.keys(): newValues[k] = self.elements[k]*other.elements[k] return C(self.parent(),newValues) def __pow__(self, n): """ Return ``self`` raised to the power ``n``. """ C = self.__class__ newValues = {} for k in self.elements.keys(): newValues[k] = self.elements[k]**n return C(self.parent(),newValues) def _richcmp_(self, other, op): """ Comparison operations. Only implemented equality operations (EQ and NE) """ if(op==op_EQ): if(self.elements.keys()!=other.elements.keys()): return False for k in self.elements.keys(): if(self.elements[k]!=other.elements[k]): return False return True elif(op==op_NE): return not self._richcmp_(other,op_EQ) else: raise NotImplementedError("operation not implemented for this element class") def _reduce(self, values): """ Returns the polynomials given as input, after applying the corresponding relations of the algebra where each polynomial belongs. INPUT: - values: a dictionary where the values are polynomials in (Apl)n """ reduced_values = {} for k in values.keys(): n = len(k)-1 reduced_values[k] = values[k].reduce() return reduced_values def check(self): """ Checks if the initial assignation respects the simplicial structure. For every simplex S, the algorithm checks if the polynomial assigned to each face of S is equal to the same face of the polynomial assigned to S. """ S = self.parent().simplicialComplex() dim = S.dimension() for n in range(1,dim+1): #print("Checking dimension " + str(n)) for simplex in S._n_cells_sorted(n): for f in range(n+1): sFace = tuple(S.face(simplex,f)) aFace = self.elements[simplex.tuple()].face(f).reduce() #print("face: " + str(f),"simplex: " + str(simplex),"sFace: " + str(self.elements[sFace]), "aFace: " + str(aFace)) if(self.elements[sFace] != aFace): raise ValueError("the assignation is not valid") def differential(self): """ Returns the differential of 'self' """ C = self.__class__ newValues = {} for k in self.elements.keys(): newValues[k] = self.elements[k].differential() return C(self.parent(),newValues) def is_closed(self): """ Returns 'True' if 'self' is closed, i.e., its differential is zero. """ diff = self.differential() if(diff == self.parent().null_element()): return True else: return False def homogeneous_part(self,degree): """ Returns the homogeneous component of grade 'degree' of 'self' """ C = self.__class__ part = {} for k in self.elements.keys(): s = SimplicialAlgebraplN(len(k)-1) part[k] = s(self.elements[k].homogeneous_part(degree=degree)) return C(self.parent(),part) def homogeneous_parts(self): """ Returns all graded components of 'self' """ parts = {} for n in range(self.dimension()+1): hom_part = self.homogeneous_part(degree=n) if hom_part != self.parent().null_element(): parts[n] = hom_part return parts def is_homogeneous(self): """ Returns 'True' if self is composed by homogeneous polynomials of the same degree """ isHomogeneous = True S = self.parent().simplicialComplex() dim = S.dimension() for n in range(dim+1): for simplex in S._n_cells_sorted(n): pol = self.elements[simplex.tuple()] if pol: isHomogeneous = isHomogeneous and pol.is_homogeneous() if not isHomogeneous: break if not isHomogeneous: break return isHomogeneous def degree(self): if not self.is_homogeneous(): return -1 else: degree = -1 for e in self.elements.values(): if e: degree = e.degree()[0] break return degree def get_skeleton(self, dim): values = {} facets = self._get_elements_of_dimension(dim) for i in range(dim+1): values.update(self._get_elements_of_dimension(i)) S = SimplicialComplex(list(facets.keys()),is_mutable=False) A = AlgebraAplK(S) return A._element_constructor_(values) class MorphismFromQQToAplK(Morphism): def __init__(self, codomain): self.simplicialComplex = codomain.K Morphism.__init__(self, QQ, codomain) def _call_(self, x): S = self.simplicialComplex params = {} for d in range(S.dimension()+1): s = SimplicialAlgebraplN(d) for c in S._n_cells_sorted(d): params[tuple(c)]=s(x) A = AlgebraAplK(S) return AlgebraAplK.Element(A, params) ############################################# # TEST print("TEST AlgebraAplK") S = SimplicialComplex([[0,1,2]],is_mutable=False) A = AlgebraAplK(S) print("Test check if the assignation is valid") s0 = SimplicialAlgebraplN(0) s0.inject_variables() p0 = s0(2) p1 = s0(1) p2 = s0(3) s1 = SimplicialAlgebraplN(1) s1.inject_variables() p01 = 2*t0+t1 p02 = 2*t0+3*t1 p12 = t0+3*t1 s2 = SimplicialAlgebraplN(2) s2.inject_variables() p012 = 2-t1+t2 values = {(0,): p0, (1,): p1, (2,): p2, (0,1): p01, (0,2): p02, (1,2): p12, (0,1,2): p012} e1 = A._element_constructor_(values) e1 #############################################