AplK/aplK.py

813 lines
28 KiB
Python

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 <sum(t_i)-1, sum(y_j)>
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<t_1,..t_n>.
(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<t_1,..,t_n>
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(k<i):
tImages.append(codomain.gens()[k])
yImages.append(codomain.gens()[n-1+k])
elif(k==i):
tImages.append(0)
yImages.append(0)
elif(k>i):
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(k<j):
tImages.append(codomain.gens()[k])
yImages.append(codomain.gens()[n+1+k])
elif (k==j):
tImages.append(codomain.gens()[k]+codomain.gens()[k+1])
yImages.append(codomain.gens()[n+1+k]+codomain.gens()[n+1+k+1])
elif (k>j):
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
#############################################