AplK/notebook/tutorial AplK.ipynb

179 KiB

About the organization of the notebook

In this document, the implementation of different mathematical objects is explained. For each one, the information is structured in the following way:

  1. First section: Definition. The object is defined using mathematical language.
  2. Second section: Implementation.
    1. Ideas and decisions made
    2. Initial declaration of the main class
    3. Implementation of a new method and examples

Each class is declared iteratively, i.e., each method is added in a different cell in order to explain it properly and to show examples. This is done in that way for a didactic purpose. For that reason, the subsection 2.c) is repeated for each new method that is added to the main class.


The cochain algebra $(A_{PL})_{n}$

In this section it is implemented the graded commutative differential cochain algebra $(A_{PL})_{n}$. For more information, see [Rational homotopy theory, Felix, Halperin, Thomas, chapter 10.c)].

Definition

First, consider the free graded commutative algebra $\Lambda(t_0,\dots,t_n,y_0,\dots,y_n)$, where the generators $t_i$ have degree $0$, and the generators $y_j$ have degree $1$. The differential, $d$, is given by

  • $d(t_i)=y_i$
  • $d(y_j)=0$

The cochain algebra $(A_{PL})_n$ is defined as the quotient of the previous algebra by the relations:

$$(A_{PL})_n=\frac{\Lambda(t_0,\dots,t_n,y_0,\dots,y_n)}{<\sum {t_i}-1,\sum{y_j}>}$$

Implementation

$(A_{PL})_{n}$ is a graded commutatite differential algebra, GCDA. In order to work with GCDA's that are finitely generated by elements of $degree \geq 1$, there is defined in SAGE a class called "DifferentialGCAlgebra". Our first problem is that our GCDA has generators of $degree=0$ (the generators $t_i$) and this class doesn't allow us to create such object.

As a first approximation, in order to solve this problem, we will define a new class of GCDA's that internally will work with multigraded generators, but this feature will be transparent for the user. For that reason, we will extend the class "DifferentialGCAlgebra_multigraded" in order to add new methods that will be useful later.

Initial declaration of the main class

In [ ]:
from sage.algebras.commutative_dga import *
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)

    class Element(GCAlgebra_multigraded.Element, DifferentialGCAlgebra.Element):
        """
        Element class of the commutative differential multi-graded algebra (Apl)n.
        """
        pass
        
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)

Example: creating an element of $(Apl)_{n}$

In [ ]:
apl = AlgebraPL(1)
print(apl)
apl.inject_variables()
Commutative Differential Graded Algebra with generators ('t0', 't1', 'y0', 'y1') in degrees [0, 0, 1, 1] over Rational Field with differential:
   t0 --> y0
   t1 --> y1
   y0 --> 0
   y1 --> 0
Defining t0, t1, y0, y1
In [ ]:
p = t0 + y0 + y1
p
Out[ ]:
y0 + y1 + t0

Adding new methods: homogeneity and total degree of a polynomial

As we mentioned before, we use a multigraded algebra internally. Specifically, we use a bigraded algebra, where the generators have either degree $(0,2)$ or degree $(1,2)$ The first component of the bigrade represents the actual degree we want to use, and the second component is ignored (it is only needed to be an even number $>0$). In particular, the generators $t_i$ have internally a degree $(0,2)$ and the generators $y_j$ have degree $(1,2)$. In order to make it transparent for the user, we implement a several methods to get, for example, the homogeneous part of a polynomial, ignoring the mentioned bigrade, and taking into account only the first compoment of the multigrade.

In [ ]:
class AplN(AplN,UniqueRepresentation):
    class Element(AplN.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 sum(parts)

Example: checking the homogeneity of a given polynomial

In [ ]:
apl = AlgebraPL(1)
apl.inject_variables()
Defining t0, t1, y0, y1
In [ ]:
p = t0 + y0 + y1
p.is_homogeneous()
Out[ ]:
False

Example: getting the homogeneous part of a polynomial for a given degree

In [ ]:
p = t0 + y0 + y1
deg = 1
print("Original polynomial:", p)
print("Homogeneous part of degree", deg, "of p: ", p.homogeneous_part(degree=deg))
Original polynomial: y0 + y1 + t0
Homogeneous part of degree 1 of p:  y0 + y1

Example: getting the total degree of a polynomial

In [ ]:
q = y0*y1 + t0*y1 + 1
q.total_degree()
Out[ ]:
2

Adding new method: reduce a polynomial by relations

As seen in the definition, the algebra $(A_{PL})_{n}$ is the quiotient algebra

$$(A_{PL})_n=\frac{\Lambda(t_0,\dots,t_n,y_0,\dots,y_n)}{<\sum {t_i}-1,\sum{y_j}>}$$

Notice that there is an isomorphism between this algebra and the free algebra:

$$ (A_{PL})_n \cong \Lambda(t_1,\dots,t_n,y_1,\dots,y_n) $$

In order to obtain a representative of a particular element on the free algebra, a method called "reduce" is added to the elements of our class. This method reduces the variables $t_0,y_0$ applying the following rules:

$$t_0 = 1 - \sum_{i=1}^{n} t_i$$ $$y_0 = -\sum_{j=1}^{n} y_j$$

Thus, after applying this method, an element of $(A_{PL})_{n}$ will be represented as an element of the algebra $\Lambda(t_1,\dots,t_n,y_1,\dots,y_n)$.

In [ ]:
class AplN(AplN,UniqueRepresentation):
    class Element(AplN.Element):
        """
        Element class of the commutative differential multi-graded algebra (Apl)n.
        """
        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()
            dim = len(apl.gens())//2-1
            if relations:
                return DifferentialGCAlgebra.Element.reduce(self, relations)
            else:
                return DifferentialGCAlgebra.Element.reduce(self, [apl.t_relation, apl.y_relation])

Example: getting the reduced polynomial of a given element

In [ ]:
apl = AlgebraPL(1)
apl.inject_variables()
Defining t0, t1, y0, y1
In [ ]:
p=t0+y0+y1
print("unreduced polynomial: ", p)
print("reduced polynomial: ", p.reduce())
unreduced polynomial:  y0 + y1 + t0
reduced polynomial:  -t1 + 1

Adding new methods: The definite integral map

We define a linear map:

$$ \int_{n}: (A_{PL})_{n}^{n} \rightarrow \mathbb{Q} $$

by setting

$$ \int_{n}t_1^{k_1}t_2^{k_2}\dots t_n^{k_n}dt_1\wedge \dots \wedge dt_n = \int_{0}^{1} dt_1 \int_{0}^{1-t_1}dt_2 \dots \int_{0}^{1-\sum_{1}^{n-1}t_i}t_1^{k_1}t_2^{k_2}\dots t_n^{k_n}dt_n = \frac{k_1!k_2! \dots k_n!}{(k_1+ \dots +k_n+n)!} $$

In [ ]:
class AplN(AplN,UniqueRepresentation):
    class Element(AplN.Element):
        """
        Element class of the commutative differential multi-graded algebra (Apl)n.
        """
        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

Example

In [ ]:
apl = AlgebraPL(1)
apl.inject_variables()
Defining t0, t1, y0, y1
In [ ]:
p=t0+y1*(t0+1)
print("Integrable part: ",p._integrable_part())
print("Definite integral: ", p.integral())
Integrable part:  -t1 + 2
Definite integral:  3/2

Adding new method: find an integrand for a given definite integral

The linear map we have defined above is not injective, but we can consider one way to find a polynomial whose image by the definite integral map is given. We call "integrand" to this particular element of the preimage set of the given element by the linear map.

$$ \int_{n}^{-1} c = c (n!) dt_1 \wedge \dots \wedge dt_n$$ $$ c \in \mathbb{Q} $$

It is easy to see that $ \int_{n}\int_{n}^{-1} c = c $ although the inverse is not always true.

We define a class method, called "integrand", that returns a polynomial whose definite integral image is equal to a rational number given as input.

In [ ]:
class AplN(AplN,UniqueRepresentation):
    def integrand(self, value): 
        """
        Returns a polynomial whose definite integral is equal to 'value'

        OUTPUT:
            - A polynomial
        """
        return value*factorial(self.n)*prod(self.gens()[self.n+2:])

Example: getting a polynomial for whose definite integral is given

In [ ]:
apl = AlgebraPL(1)
apl.inject_variables()
Defining t0, t1, y0, y1
In [ ]:
value = 5
apl.integrand(value)
Out[ ]:
5*y1
In [ ]:
#checking that the composition of the integral and the lift is the identity map
p = apl.integrand(value)
p.integral()==value
Out[ ]:
True

Adding new methods: find a primitive of the coboundary operator

As we have mentioned before, there is an isomorphism between the cochain algebra $(A_{PL})_n$ and the algebra $\Lambda (t_1, \dots, t_n,y_1, \dots, y_n)$, where the variables $t_i$ have degree $0$, and the variables $y_j$ have degree $1$. This algebra is the tensor product of the polynomial ring in the variables $t_i$ with the exterior algebra in the variables $y_j$:

$$(A_{PL})_n \cong \mathbb{Q}[t_1, \dots, t_n]\otimes_{\mathbb{Q}} \Lambda ^{\mathbb{Q}}(y_1, \dots, y_n)$$

In this algebra, we define the lexicographic monomial order in the variables $y_i$ as

$$y_i > y_j, \: if \: i<j$$

A polynomial $p \in \Lambda (t_1, \dots, t_n,y_1, \dots, y_n)$ has the form:

$$ p = p^0 y_{1}^{k_{1}^{0}} \dots y_{n}^{k_{n}^{0}} + p^1 y_{1}^{k_{1}^{1}} \dots y_{n}^{k_{n}^{1}} + \dots $$

where

  • $k_{j}^{i} \in \mathbb{Z}_2$
  • $p^i \in \mathbb{Q}[t_1, \dots, t_n]$
  • $(k_{1}^{i}, \dots , k_{n}^{i}) \neq (k_{1}^{j}, \dots, k_{n}^{j})$ if $i \neq j$

It is easy to see that $p$ will have at most $2^n-1$ non-null terms in the variables $y_j$.

As we have set the lexicographic monomial ordering, for every two consecutive non-null terms in a polynomial $p$

$$ p = \dots + p^i y_{1}^{k_{1}^{i}} \dots y_{n}^{k_{n}^{i}} + p^{i+1} y_{1}^{k_{1}^{i+1}} \dots y_{n}^{k_{n}^{i+1}} + \dots$$

occurs one of the following situations:

  • $k_{1}^{i} > k_{1}^{i+1}$
  • $\exists m \in \mathbb{N}, 1 < m \leq n $ so that $k_{l}^{i} = k_{l}^{i+1}$ for $1 \leq l < m$, and $k_{l}^{i} > k_{l}^{i+1}$ for $l= m$.

Our algebra $(A_{PL})_n$, as a cochain algebra, has a coboundary operator, $d$, and by definition, we know that $d^2=0$. Also, we know that $H^p((A_{PL})_n)=0$ for $p \geq 1$, meaning that, for every element $\theta \in (A_{PL})_n$ so that $d\theta=0$ (i.e., $\theta$ is closed), there exists an element $\omega \in (A_{PL})_n$ so that $\theta = d\omega$.

Our problem here is to find a primitive $\omega$ whose differential $\theta$ is known, $\theta = d\omega$. As we know that $\theta$ is the differential of some element, for now on we will consider polynomials where all terms have at least one exponent $k_{j}^{i} \neq 0$ (i.e., our polynomial $\theta$ has no terms of degree $0$).

Definition. (pseudo-primitive of a monomial) Let $\theta \in (A_{PL})_n$ be a polynomial formed by only one term of degree $p$ of the form:

$$\theta = ct_{1}^{l_{1}^{0}} \dots t_{n}^{l_{n}^{0}} y_{1}^{k_{1}^{0}} \dots y_{n}^{k_{n}^{0}}$$

satisfying:

  • $c \in \mathbb{Q}$
  • $k_{j}^{i} \in \mathbb{Z}_2$
  • $\sum k_{j}^{i} = p$
  • $l_{j}^{i} \in \mathbb{N} \cup \{0\}$

We define the pseudo-primitive of $\theta$ (in relation to the coboundary operator) as the homogeneous polynomial $\omega$, of degree $p-1$, whose differential is the polynomial $\theta$ plus some homogeneous polynomial $\epsilon$ of degree $p$:

$$d\omega = \theta+\epsilon$$

This polynomial is calculated in the following way:

Step 1: Choose the lowest $i$ for which $k_{i}^{0}=1$

Step 2: Set $k_{i}^{0}=0$ and increase $l_{i}^{0}$ by 1

$$ \omega = ct_{1}^{l_{1}^{0}} \dots t_{i}^{l_{i}^{0}+1} \dots t_{n}^{l_{n}^{0}} y_{1}^{0} \dots y_{i}^{0} \dots y_{n}^{k_{n}^{0}}$$

Step 3: Divide the coefficient $c$ by $l_{i}^{0}+1$

$$ \omega = \frac{c}{l_{i}^{0}+1}t_{1}^{l_{1}^{0}} \dots t_{i}^{l_{i}^{0}+1} \dots t_{n}^{l_{n}^{0}} y_{1}^{0} \dots y_{i}^{0} \dots y_{n}^{k_{n}^{0}}$$

Lemma: $d\omega= \theta + \epsilon$

Proof: TODO


The algorithm

The input of our algorithm is a polynomial $\theta \in (A_{PL})_n$ that has the form:

$$ \theta = p^0 y_{1}^{k_{1}^{0}} \dots y_{n}^{k_{n}^{0}} + p^1 y_{1}^{k_{1}^{1}} \dots y_{n}^{k_{n}^{1}} + \dots $$

$$p^i \in \mathbb{Q}[t_1, \dots, t_n]$$

Step 1: Find a pseudo-primitive of the leading term of $\theta$.

First, we define the variables $\omega, \theta_0$, and we initialize them as

$$\omega=0$$ $$\theta_0=\theta$$

The leading term of $\theta_0$, $LT(\theta_0)$, has the form:

$$LT(\theta_0) = c_0t_{1}^{l_{1}^{0}} \dots t_{n}^{l_{n}^{0}} y_{1}^{k_{1}^{0}} \dots y_{n}^{k_{n}^{0}}$$ $$ c_0\in \mathbb{Q}$$ $$ l_{i}^{0} \geq 0$$

What we want to do in this step is to find a polynomial $\omega_0$ whose differential contains the leading term of the element $\theta_0$ plus some extra terms $\epsilon_0$:

$$d\omega_0 = LT(\theta_0)+\epsilon_0$$

Knowing that the coboundary operator increases the degree in 1 unit, and the degree of every term comes from the variables $y_j$, one approach is the following:

  1. Assign to $\omega_0$ the leading term of $\theta_0$,
  2. Choose the lowest $i$ for which $k_{i}^{0}=1$
  3. Set $k_{i}^{0}=0$ and increase $l_{i}^{0}$ by 1
  4. Divide the coefficient $c_0$ by $l_{i}^{0}+1$

$$ \omega_0 = \frac{c_0}{l_{i}^{0}+1}t_{1}^{l_{1}^{0}} \dots t_{i}^{l_{i}^{0}+1} \dots t_{n}^{l_{n}^{0}} y_{1}^{0} \dots y_{i}^{0} \dots y_{n}^{k_{n}^{0}}$$

TODO: Prueba de que esto funciona:

$$\omega_0 = c_0a(t)*b(y)$$ $$d\omega_0 = d(c_0a(t))*b(y)) = c_0d(a(t))b(y)$$

Step 2: Calculate the new $\theta_i$:

Once we have found the element $\omega_0$, we update the variable $\omega$ as:

$$ \omega = \omega + \omega_0$$

and we define the new variable $\theta_{1}$ as:

$$ \theta_{1} = \theta-d\omega $$

If $\theta_{1}=0$ we have finished and $\omega$ is the element we were looking for. If $\theta_{1} \neq 0$, continue with the following steps.

Step 3:

Get the leading term of $\theta_{1}$, and obtain its pseudo-primitive $\omega_1$.

Step 4:

Update the variable $\omega$ as:

$$ \omega = \omega + \omega_1$$

and define the new variable $\theta_{2}$ as:

$$ \theta_{2} = \theta-d\omega $$

Step 5: If $\theta_{2}=0$ we have finished and $\omega$ is the element we were looking for. If $\theta_{2} \neq 0$, go back to step 3 and repeat the steps 3 and 4 several times until we get that $\theta_{i+1}=0$.

In [ ]:
class AplN(AplN,UniqueRepresentation):
    class Element(AplN.Element):
        """
        Element class of the commutative differential multi-graded algebra (Apl)n.
        """
        
        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()

Example

In [ ]:
apl = AlgebraPL(5)
apl.inject_variables()
Defining t0, t1, t2, t3, t4, t5, y0, y1, y2, y3, y4, y5
In [ ]:
omega_orig = t0+5*y1*y2+y3*y4*t1*y5
theta = omega_orig.differential()
print(theta.primitive().differential()==theta)
print(theta.primitive()==omega_orig)
True
False
In [ ]:
theta = (t0+t1^2+1)*y0*y1*y3
print(theta.differential())
print(theta.primitive().differential()==theta)
print(theta.primitive())
0
True
t0*t1^2*y1*y3 + 1/2*t0^2*y1*y3 + t0*y1*y3
In [ ]:
 

The simplicial cochain algebra $A_{PL}$

This class implements the simplicial cochain algebra $A_{PL} = \{(A_{PL})_{n}\}_{n\geq0}$. For more information, see [Rational homotopy theory, Felix, Halperin, Thomas, chapter 10.c)].

Definition

A simplicial object $K$ with values in a category $\mathfrak{C}$ is a sequence $\{K_n\}_{n \geq 0}$ of objects in $\mathfrak{C}$, together with $\mathfrak{C}$-morphisms:

$$\delta_i: K_{n} \rightarrow K_{n-1}, \; 0 \leq i \leq n $$

$$s_j: K_{n} \rightarrow K_{n+1}, \; 0 \leq j \leq n$$

that satisfy the identities:

$$\delta_i \delta_j = \delta_{j-1} \delta_i,\; i<j$$ $$s_i s_j = s_{j+1} s_i,\; i \leq j$$ $$\delta_i s_j = \left\{\begin{matrix} s_{j-1}\delta_i,& i<j\\ id, & i=j,j+1\\ s_j\delta_{i-1}, & i> j+1 \end{matrix}\right.$$

We define the simplicial cochain algebra $A_{PL} = \{(A_{PL})_n\}_{n \geq 0}$ by:

The cochain algebra $(A_{PL})_n$ is given by: $$(A_{PL})_n = \frac{\wedge(t_0, \dots, t_n,y_0,\dots, y_n)}{<\sum{t_i}-1, \sum{y_j}>}$$

The face and degeneracy morphisms are the unique cochain algebra morphisms:

$$\delta_i: (A_{PL})_{n} \rightarrow (A_{PL})_{n-1}, \; 0 \leq i \leq n$$

$$s_j: (A_{PL})_{n} \rightarrow (A_{PL})_{n+1}, \; 0 \leq j \leq n$$

satisfying

$$\delta_i = t_k \rightarrow \left\{\begin{matrix} t_k,& k<i\\ 0, & k=i\\ t_{k-1}, & k>i \end{matrix}\right. \; \; \; \;\; s_j = t_k \rightarrow \left\{\begin{matrix} t_k,& k<j\\ t_k+t_{k+1}, & k=j\\ t_{k+1}, & k>j \end{matrix}\right.$$

Implementation

In [ ]:
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 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)
In [ ]:
s = SimplicialAlgebraplN(5)
s.inject_variables()
Defining t0, t1, t2, t3, t4, t5, y0, y1, y2, y3, y4, y5
In [ ]:
p = t1+y2
p.degeneration(0).face(1)==p
Out[ ]:
True
In [ ]:
m1 = p.degeneration(1).face_morphism(1)
m2 = p.degeneration_morphism(1)
(m1*m2).is_identity()
Out[ ]:
True
In [ ]:
 

Adding new methods: propagate a simplex through the simplicial structure

In this part, we want to obtain an homogeneous $(n+k)$-simplex of degree $n$, $p_{n+k} \in (A_{PL})_{n+k}^n$, for $k>0$, where a given homogeneous $n$-simplex of degree $n$, $p_n \in (A_{PL})_n^n$ is a face of $p_{n+k}$ of dimension $n$, and the rest of faces of dimension lower or equal to $n$ are $0$. We call this map propagation,denoted by $\Gamma$ :

$$\Gamma_{n+k}^n: (A_{PL})_n^n \rightarrow (A_{PL})_{n+k}^n$$

Without lost of generality, we asume that our polynomial of degree $n$ is expressed as a polynomial in the variables $y_1,\dots,y_n$. Let $p_n \in (A_{PL})_n^n$ be the polynomial:

$$p_n = q_t y_{1} \dots y_{n}$$

$$q_t \in \mathbb{Q}[t_0,\dots,t_n]$$

Let $I$ be the index set $\{0,\dots, n+k\}$. For some subsets $(i_1, \dots, i_k) \subset I$ we know by definition that

$$p_n = \delta_{i_1} \dots \delta_{i_k} \Gamma_{n+k}^n(p_n)$$

and due to the relations between face morphisms, we know that we can chose a unique subset $(i_1, \dots, i_k)$ in a way that $i_1 < \dots < i_k$.

Just to point the definition, notice that any other combination of faces $\delta_{j_1} \dots \delta_{j_k}$ not equivalent to $\delta_{i_1} \dots \delta_{i_k}$ (i.e., applying the relations between face morphisms), makes the result:

$$\delta_{j_1} \dots \delta_{j_k}\Gamma_{n+k}^n(p_n) = 0$$

Let $J=\{j_0,\dots,j_n\} \subset I$ be the subset $\{i_1, \dots, i_k\}^C$, i.e., those elements of $I$ $\notin \{i_1, \dots, i_k\}$, and where $j_l < j_m$, for $l<m$.

Just before defining the propagation morphism, we define the morphism $\Upsilon$ as:

\begin{align} \Upsilon: \mathbb{Q}[t_0,\dots, t_n] & \longrightarrow \mathbb{Q}[t_0, \dots, t_{n+k}] \\ t_i & \rightarrow t_{j_i} \end{align}

$$j_i \in J$$

Now we can define the propagation map $\Gamma_{n+k}^n$ as:

$$\Gamma_{n+k}^n(p_n) = \Upsilon(q_t)\sum_{k=0}^{n}{(-1)^{n-k} t_{j_{n-k}}(y_{j_0}\dots \widehat{y_{j_{n-k}}} \dots y_{j_n})}$$

where $\widehat{y_{j}}$ means that we have removed the variable $y_{j}$.

Implementation

In [ ]:
class SimplicialAplN(SimplicialAplN,UniqueRepresentation):
    class Element(SimplicialAplN.Element):
        """
        Element class of the commutative differential multi-graded algebra (Apl)n.
        """
        
        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

Example

In [43]:
s = SimplicialAlgebraplN(2)
s.inject_variables()
Defining t0, t1, t2, y0, y1, y2
In [51]:
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)
t5^2*t7*y1*y5 + t7^3*y1*y5 - t5^3*y1*y7 - t5*t7^2*y1*y7 + t1*t5^2*y5*y7 + t1*t7^2*y5*y7
True
In [ ]:
 
In [ ]:
q1 = p.propagate([1,5,7],10)
q2 = p.propagate([0,4,6],9)
print(q1.face(0).reduce() == q2.reduce())
True
In [ ]:
# tests torus (remove cell)
s = SimplicialAlgebraplN(1)
s.inject_variables()
p = y1
F1a = p.propagate([0,1],2)
F2a = p.propagate([1,2],2)
F1c = p.propagate([0,2],2)
F2c = p.propagate([0,2],2)
print((F2a).reduce())
C1F1 = F1a+F1c
C1F2 = F2a+F2c
C1F2.reduce()
Defining t0, t1, y0, y1
-t2*y1 + t1*y2
Out[ ]:
y2
In [ ]:
# tests torus (remove cell)
s = SimplicialAlgebraplN(1)
s.inject_variables()
p = y1
F1b = p.propagate([1,2],2)
F2b = p.propagate([0,1],2)
F1c = p.propagate([0,2],2)
F2c = p.propagate([0,2],2)
print((F2b+F2c))
C2F1 = F1b+F1c
C2F2 = F2b+F2c
C2F1.reduce()
Defining t0, t1, y0, y1
-t1*y0 - t2*y0 + t0*y1 + t0*y2
Out[ ]:
y2
In [ ]:
 

The cochain algebra $A_{PL}(K)$

This class implements the cochain algebra $A_{PL}(K)$. For more information, see [Rational homotopy theory, Felix, Halperin, Thomas, chapter 10.c)].

Definition

Let $K$ be a simplicial set, and let $A_{PL} = \{(A_{PL})_n\}_{n \geq 0}$ be the simplicial cochain algebra defined earlier. Then

$$ A_{PL}(K) = \{A_{PL}^p(K)\}_{p \geq 0}$$

is the cochain algebra defined as follows:

  • $A_{PL}^p(K)$ is the set of simplicial set morphisms from $K$ to $A_{PL}^p$. Thus, an element $\Phi \in A_{PL}^p(K)$ is a mapping that assings to each $n$-simplex $\sigma \in K_n$ an element $\Phi_\sigma \in (A_{PL}^p)_n$, such that

$$ \Phi_{\delta_i(\sigma)}=\delta_i(\Phi_\sigma)$$

$$ \Phi_{s_j(\sigma)}=s_j(\Phi_\sigma)$$

  • Addition, scalar multiplication and the differential are given by $$ (\Phi+\Psi)_\sigma = \Phi_\sigma + \Psi_\sigma$$

$$ (\lambda \Phi)_\sigma = \lambda \Phi_\sigma$$

$$ (d\Phi)_\sigma = d(\Phi_\sigma)$$

  • As $A_{PL}$ is a simplicial cochain algebra, multiplication in $A_{PL}(K)$ is given by

$$ (\Phi \Psi)_\sigma = \Phi_\sigma \Psi_\sigma$$

Implementation

In [33]:
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


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 = values
            
        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")

    
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)  

Example

In [ ]:
 
In [34]:
S = SimplicialComplex([[0,1,2]],is_mutable=False)
A = AlgebraAplK(S)
A
Out[34]:
The graded commutative differential cochain algebra Apl(K) of Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)}
In [35]:
values = {(0,): 0, (1,): 0, (2,):0, (0,1): 0, (0,2): 0, (1,2): 1,  (0,1,2):0}
A._element_constructor_(values)
Out[35]:
{(0,): 0, (1,): 0, (2,): 0, (0, 1): 0, (0, 2): 0, (1, 2): 1, (0, 1, 2): 0}
In [36]:
values1 = {(0,): 0, (1,): 0, (2,):0, (0,1): 0, (0,2): 0, (1,2): 1,  (0,1,2):0}
e1 = A._element_constructor_(values1)
values2 = {(0,): 0, (1,): 0, (2,):0, (0,1): 0, (0,2): 0, (1,2): 1,  (0,1,2):0}
e2 = A._element_constructor_(values2)
In [37]:
e1-e2
Out[37]:
{(0,): 0, (1,): 0, (2,): 0, (0, 1): 0, (0, 2): 0, (1, 2): 0, (0, 1, 2): 0}
In [38]:
(e1-e2)==A.null_element()
Out[38]:
True
In [39]:
3*e1
Out[39]:
{(0,): 0, (1,): 0, (2,): 0, (0, 1): 0, (0, 2): 0, (1, 2): 3, (0, 1, 2): 0}
In [40]:
simplex = S._n_cells_sorted(2)[0]
A._faces_of_length(simplex,2)
Out[40]:
[(0, 1), (0, 2), (1, 2)]

Adding methods: reduce polynomials by relations

As the algebra $(A_{PL})_n$ is not freely generated, it could be that two apparently different elements of $A_{PL}(K)$ correspond actually to the same element in the algebra. In order to compare properly elements of the algebra, it is added a new method called "reduce" that, $\forall n \geq 0$, applies the relations of $(A_{PL})_n$ to every polynomial in $ \Phi \in A_{PL}(K)$ in a particular way that makes the representation of every class of elements unique.

In [41]:
class AlgebraAplK(AlgebraAplK, UniqueRepresentation):
    """
    
      The graded commutative differential cochain algebra Apl(K)
      
    """
    
    class Element(AlgebraAplK.Element):
        """
        Element class of the simplicial commutative differential algebra (Apl)n.
        """
        
        def __init__(self, parent, values):
            Element.__init__(self,parent)
            self.elements = values
            self.elements = self._reduce(values) 
            
        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
    

Example

In [42]:
S = SimplicialComplex([[0,1,2]],is_mutable=False)
A = AlgebraAplK(S)
A
Out[42]:
The graded commutative differential cochain algebra Apl(K) of Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)}
In [43]:
s = SimplicialAlgebraplN(3)
s.inject_variables()
values = {(0,): y0*y1*y2*y3, (1,): s(0), (2,): s(0), (0,1): t1, (0,2): y0, (1,2): t0,  (0,1,2): s(0)}
values
Defining t0, t1, t2, t3, y0, y1, y2, y3
Out[43]:
{(0,): y0*y1*y2*y3,
 (1,): 0,
 (2,): 0,
 (0, 1): t1,
 (0, 2): y0,
 (1, 2): t0,
 (0, 1, 2): 0}
In [44]:
e1 = A._element_constructor_(values)
e1
Out[44]:
{(0,): 0, (1,): 0, (2,): 0, (0, 1): t1, (0, 2): -y1 - y2 - y3, (1, 2): -t1 - t2 - t3 + 1, (0, 1, 2): 0}

Adding new methods: Check if a given assignation respects the simplicial structure

During the construction of an element of $A_{PL}(K)$, it is important to be sure that a given assignation of polynomials to our simplicial complex respects the simplicial structure. For that reason, a method called "check" is added. The method verifies the following condition:

$$\Phi (\delta_i \sigma) = \delta_i \Phi (\sigma)$$

$$\forall \sigma \in K_{n}, n \geq 1$$ $$0 \leq i \leq n$$

In [45]:
class AlgebraAplK(AlgebraAplK, UniqueRepresentation):
    """
    
      The graded commutative differential cochain algebra Apl(K)
      
    """
    
    class Element(AlgebraAplK.Element):
        """
        Element class of the simplicial commutative differential algebra (Apl)n.
        """
        
        def __init__(self, parent, values):
            Element.__init__(self,parent)
            self.elements = values
            self.elements = self._reduce(values) 
            self.check()  

        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")

Example

In [46]:
S = SimplicialComplex([[0,1,2]],is_mutable=False)
A = AlgebraAplK(S)
A
Out[46]:
The graded commutative differential cochain algebra Apl(K) of Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)}
In [47]:
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
Defining t0, y0
Defining t0, t1, y0, y1
Defining t0, t1, t2, y0, y1, y2
Out[47]:
{(0,): 2, (1,): 1, (2,): 3, (0, 1): -t1 + 2, (0, 2): t1 + 2, (1, 2): 2*t1 + 1, (0, 1, 2): -t1 + t2 + 2}
In [48]:
simplex = S._n_cells_sorted(2)[0]
list(powerset(simplex))
Out[48]:
[[], [0], [1], [0, 1], [2], [0, 2], [1, 2], [0, 1, 2]]
In [ ]:
 

Adding new methods: Differential

As mentioned in the definition of $A_{PL}(K)$, the differential of $\Phi \in A_{PL}(K)$ is defined as

$$(d\Phi) (\sigma) = d(\Phi (\sigma))$$

In [49]:
class AlgebraAplK(AlgebraAplK, UniqueRepresentation):
    """
    
      The graded commutative differential cochain algebra Apl(K)
      
    """
    
    class Element(AlgebraAplK.Element):
        """
        Element class of the simplicial commutative differential algebra (Apl)n.
        """
        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

Example

In [50]:
S = SimplicialComplex([[0,1,2]],is_mutable=False)
A = AlgebraAplK(S)
A
Out[50]:
The graded commutative differential cochain algebra Apl(K) of Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)}
In [51]:
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
Defining t0, y0
Defining t0, t1, y0, y1
Defining t0, t1, t2, y0, y1, y2
Out[51]:
{(0,): 2, (1,): 1, (2,): 3, (0, 1): -t1 + 2, (0, 2): t1 + 2, (1, 2): 2*t1 + 1, (0, 1, 2): -t1 + t2 + 2}
In [52]:
e1.differential()
Out[52]:
{(0,): 0, (1,): 0, (2,): 0, (0, 1): -y1, (0, 2): y1, (1, 2): 2*y1, (0, 1, 2): -y1 + y2}
In [53]:
e1.is_closed()
Out[53]:
False
In [54]:
e1.differential().is_closed()
Out[54]:
True

Adding new methods: Homogeneity

In [55]:
class AlgebraAplK(AlgebraAplK, UniqueRepresentation):
    """
    
      The graded commutative differential cochain algebra Apl(K)
      
    """
    
    class Element(AlgebraAplK.Element):
        """
        Element class of the simplicial commutative differential algebra (Apl)n.
        """                

        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
            

Examples

In [56]:
S = SimplicialComplex([[0,1,2]],is_mutable=False)
A = AlgebraAplK(S)
A
Out[56]:
The graded commutative differential cochain algebra Apl(K) of Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)}
In [57]:
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
Defining t0, y0
Defining t0, t1, y0, y1
Defining t0, t1, t2, y0, y1, y2
Out[57]:
{(0,): 2, (1,): 1, (2,): 3, (0, 1): -t1 + 2, (0, 2): t1 + 2, (1, 2): 2*t1 + 1, (0, 1, 2): -t1 + t2 + 2}
In [58]:
e1.is_homogeneous()
Out[58]:
True
In [59]:
e1.homogeneous_parts()
Out[59]:
{0: {(0,): 2, (1,): 1, (2,): 3, (0, 1): -t1 + 2, (0, 2): t1 + 2, (1, 2): 2*t1 + 1, (0, 1, 2): -t1 + t2 + 2}}
In [60]:
values = {(0,): p0, (1,): p1, (2,): p2, (0,1): p01, (0,2): p02, (1,2): p12,  (0,1,2): p012+t0*y1*y2+t0*t1*y2}
e2 = A._element_constructor_(values)
e2
Out[60]:
{(0,): 2, (1,): 1, (2,): 3, (0, 1): -t1 + 2, (0, 2): t1 + 2, (1, 2): 2*t1 + 1, (0, 1, 2): -t1*y1*y2 - t2*y1*y2 - t1^2*y2 - t1*t2*y2 + y1*y2 + t1*y2 - t1 + t2 + 2}
In [61]:
e2.is_homogeneous()
Out[61]:
False
In [62]:
e2.homogeneous_parts()
Out[62]:
{0: {(0,): 2, (1,): 1, (2,): 3, (0, 1): -t1 + 2, (0, 2): t1 + 2, (1, 2): 2*t1 + 1, (0, 1, 2): -t1 + t2 + 2},
 1: {(0,): 0, (1,): 0, (2,): 0, (0, 1): 0, (0, 2): 0, (1, 2): 0, (0, 1, 2): -t1^2*y2 - t1*t2*y2 + t1*y2},
 2: {(0,): 0, (1,): 0, (2,): 0, (0, 1): 0, (0, 2): 0, (1, 2): 0, (0, 1, 2): -t1*y1*y2 - t2*y1*y2 + y1*y2}}
In [63]:
sum(e2.homogeneous_parts().values())==e2
Out[63]:
True

Adding new methods: Skeleton

In [64]:
class AlgebraAplK(AlgebraAplK, UniqueRepresentation):
    """
    
      The graded commutative differential cochain algebra Apl(K)
      
    """
    
    class Element(AlgebraAplK.Element):
        """
        Element class of the simplicial commutative differential algebra (Apl)n.
        """  
        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)

Example

In [65]:
S = SimplicialComplex([[0,1,2]],is_mutable=False)
A = AlgebraAplK(S)
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+t0*y1*y2+t0*t1*y2}
e2 = A._element_constructor_(values)
e2
Defining t0, y0
Defining t0, t1, y0, y1
Defining t0, t1, t2, y0, y1, y2
Out[65]:
{(0,): 2, (1,): 1, (2,): 3, (0, 1): -t1 + 2, (0, 2): t1 + 2, (1, 2): 2*t1 + 1, (0, 1, 2): -t1*y1*y2 - t2*y1*y2 - t1^2*y2 - t1*t2*y2 + y1*y2 + t1*y2 - t1 + t2 + 2}
In [ ]:
 
In [ ]:
 

Adding new methods: Integration map

For $(A_{PL})_n$ we have defined the linear map:

$$ \int_{n}: (A_{PL})_{n}^{n} \rightarrow \mathbb{Q} $$

Now we want to extend this map to $A_{PL}(K)$ in order to obtain a natural quasi-isomorphism of cochain complexes:

$$\oint : A_{PL}(K) \rightarrow C^*(K)$$

Remember that $\Phi^n_n \in A_{PL}(K)^n_n$ is a morphism $\Phi^n_n: K_n \rightarrow (A_{PL})^n_n$

We define the integration map $ \oint = \begin{Bmatrix}\oint_n\end{Bmatrix}_{n \geq 0}$ as

$$ \oint_n: A_{PL}(K)^n_n \rightarrow C^n(K)$$

$$ (\oint_n(\Phi^n_n))(\sigma) = (-1)^{\frac{n(n-1)}{2}}\int_{n}(\Phi^n_n(\sigma))$$ $$ \sigma \in K_n$$

In [66]:
class AlgebraAplK(AlgebraAplK, UniqueRepresentation):
    """
    
      The graded commutative differential cochain algebra Apl(K)
      
    """
    
    class Element(AlgebraAplK.Element):
        """
        Element class of the simplicial commutative differential algebra (Apl)n.
        """
        def integral(self):
            """
            Returns the cochain obtained as integration of 'self'
            """
            cochain = {}
            #integration of each polynomial
            for k in self.elements.keys():
                dim = len(k)-1
                # the sign is due to stoke's theorem
                cochain[k] = ((-1)**(dim*(dim-1)/2))*self.elements[k].integral()
            return cochain

Examples

In [67]:
S = SimplicialComplex([[0,1,2]],is_mutable=False)
A = AlgebraAplK(S)
A
Out[67]:
The graded commutative differential cochain algebra Apl(K) of Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)}
In [68]:
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
Defining t0, y0
Defining t0, t1, y0, y1
Defining t0, t1, t2, y0, y1, y2
Out[68]:
{(0,): 2, (1,): 1, (2,): 3, (0, 1): -t1 + 2, (0, 2): t1 + 2, (1, 2): 2*t1 + 1, (0, 1, 2): -t1 + t2 + 2}
In [69]:
e1.integral()
Out[69]:
{(0,): 2, (1,): 1, (2,): 3, (0, 1): 0, (0, 2): 0, (1, 2): 0, (0, 1, 2): 0}
In [70]:
e1.differential()
Out[70]:
{(0,): 0, (1,): 0, (2,): 0, (0, 1): -y1, (0, 2): y1, (1, 2): 2*y1, (0, 1, 2): -y1 + y2}
In [71]:
e1.differential().integral()
Out[71]:
{(0,): 0, (1,): 0, (2,): 0, (0, 1): -1, (0, 2): 1, (1, 2): 2, (0, 1, 2): 0}

Adding new methods: Lift cochain

Here, we define a map

$$\oint^{-1} : C^*(K) \rightarrow A_{PL}(K) $$

that, for a given a cochain $\chi$, we obtain an element $\oint^{-1}\chi \in A_{PL}(K)$ for which

$$\oint\oint^{-1} = id$$

As the map $\oint$ is not injective, the oposite is almost never true. In general, $\oint^{-1}\oint \Phi \neq \Phi$, for any $\Phi \in A_{PL}(K)$

In [72]:
class AlgebraAplK(AlgebraAplK, UniqueRepresentation):
    """
    
      The graded commutative differential cochain algebra Apl(K)
      
    """
    
    def lift_cochain(self, cochain):
        """
        Returns an element of the algebra Apl(K) whose integral is the cochain given as input.
        """
        # first we find polynomials compatible with the integration value
        values = {}
        max_dim = 0
        for k in cochain.keys():
            dim = len(k)-1
            if dim > max_dim:
                max_dim = dim
            apl = SimplicialAlgebraplN(dim)
            if dim>0:
                values[k] = ((-1)**(dim*(dim-1)/2))*apl.integrand(cochain[k])
            else:
                values[k]= apl(cochain[k])
        
        # second, we propagate the simplicial information
        simplicial_structure = {}
        for n in reversed(range(max_dim+1)):
            keys = [k for k in values.keys() if len(k) == n+1] # keys of the n-simplices
            for k in keys:
                simplicial_structure[k] = values[k] + self._propagate(cochain, k)         
        #print(simplicial_structure)
        return AlgebraAplK.Element(self,simplicial_structure)
    
    def _propagate(self, cochain, simplex):
        """
        Returns the part of the polynomial assigned to 'simplex' related to the
        propagation of the simplicial information of its faces
        """
        dim = len(simplex)-1
        apl = SimplicialAlgebraplN(dim)
        propagation = apl(0)
        for n in reversed(range(dim)):
            faces = self._faces_of_length(simplex, n+1)
            apl = SimplicialAlgebraplN(n)
            pol = apl(prod(apl.gens()[n+2:])) # we propagate the polynomial (y_1...y_n)
            for f in faces:
                if f in cochain.keys():
                    value = cochain[f]
                    face_indexes = [simplex.index(i) for i in f]
                    #print("dim: ",n," pol: ",pol," face_idx: ",face_indexes," dim: ", n)
                    propagation += value*((-1)**(n*(n-1)/2))*factorial(n)*pol.propagate(face_indexes, dim)
        return propagation

Example

In [73]:
S = SimplicialComplex([[0,1,2,3]],is_mutable=False) 
A = AlgebraAplK(S)
cochain = {(0,): 0, (1,): 0, (2,):0, (3,): 0, (0,1): 0, (0,2): 0, (0,3): 0,(1,2): 0, (1,3): 1, (2,3): 0, (0,1,2):0, (0,1,3):0, (0,2,3):0, (1,2,3):1, (0,1,2,3):1}
p = A.lift_cochain(cochain)
p
Out[73]:
{(0, 1, 2, 3): -6*y1*y2*y3 - 2*t3*y1*y2 + 2*t2*y1*y3 - 2*t1*y2*y3 - t3*y1 + t1*y3, (0, 1, 2): 0, (0, 1, 3): -t2*y1 + t1*y2, (0, 2, 3): 0, (1, 2, 3): -2*y1*y2 + t2*y1 - t1*y2 + y2, (0, 1): 0, (0, 2): 0, (0, 3): 0, (1, 2): 0, (1, 3): y1, (2, 3): 0, (0,): 0, (1,): 0, (2,): 0, (3,): 0}
In [74]:
print(p.integral()==cochain)
print(A.lift_cochain(p.integral())==p)
True
True

Adding new methods: Managing simplicial-cohomology generators

In [75]:
class AlgebraAplK(AlgebraAplK, UniqueRepresentation):
    """
    
      The graded commutative differential cochain algebra Apl(K)
      
    """
    def cohomology_gens(self,degree):
        """
        Returns the cohomolosy generators of a given degree
        """
        gens = {}
        basis = self.K.cohomology_ring(QQ).basis()
        for k in basis.keys():
            if k[0] == degree:
                gens[k] = basis[k]
        return gens
    
    def cohomology_group(self, n):
        chainComplex = self.K.chain_complex(base_ring=QQ)
        cochainComplex = chainComplex.dual()
        Hn = cochainComplex.differential(n).right_kernel()/cochainComplex.differential(n-1).column_space()
        return Hn

    def cochain_to_vector(self, cochain, dim):
        """
        Converts a cochain, given as a dictionary, to a vector
        """
        basis = self.K.n_chains(dim, base_ring=QQ, cochains=True).basis()
        v = vector(QQ,len(basis)*[0])
        i=0
        for simplex in basis.keys():
            v[i] = cochain[tuple(simplex)]
            i = i+1
        return v

    def vector_to_cochain(self, v, dim):
        """
        Converts a cochain, given as a vector, to a dictionary
        """
        cochain = {}
        for d in range(self.K.dimension()+1):
            basis = self.K.n_chains(d, base_ring=QQ, cochains=True).basis()
            if d!=dim:
                for simplex in basis.keys():
                    cochain[tuple(simplex)] = 0
            else:
                i=0
                for simplex in basis.keys():
                    cochain[tuple(simplex)] = v[i]
                    i = i+1
        return cochain
    
    def lift_cochain_complex(self, cochain, dim):
        """
            (REVISAR)
        Returns an element of the preimage by the differential of a given cochain
        
        INPUT:
            - cochain: a cochain as a vector
            - dim: dimension of the cochain
        """
        # S =  the Simplicial Complex
        # cochain = the cochain you want to find a preimage by the differential
        # dim = the dimension of the cochain
        cochain_vector = self.cochain_to_vector(cochain, dim)
        cochainComplex = self.K.chain_complex(base_ring=QQ).dual()
        diff = cochainComplex.differential(dim-1)
        show(diff.echelon_form())
        result = diff.solve_right(cochain_vector)
        return self.vector_to_cochain(result)

    def lift_cohomology_generator(self, g):
        """
        Returns a representative cochain associated to a given cohomology generator.
        """
        cycle = g.to_cycle()
        cochain = {}
        for n in range(self.K.dimension()+1):
            for simplex in self.K._n_cells_sorted(n):
                cochain[simplex.tuple()] = 0
        for i in cycle:
            #print(type(i[0].tuple()))
            cochain[i[0].tuple()] = i[1]
        return cochain
    
    def get_cohomology_aplk(self,degree):
        """
        Returns a set of representative elements of Apl(K), one for each cohomology generator of a given degree
        """
        representative_aplk_elements = {}
        dic_gens = self.cohomology_gens(degree)
        for key in dic_gens.keys():
            gen = dic_gens[key]
            cochain = self.lift_cohomology_generator(gen)
            representative_aplk_elements[key] = self.lift_cochain(cochain)
        return representative_aplk_elements
    
    def first_cohomology_group(self):
        """
        Returns the degree >= 1 of the first non-null cohomology generator. 
        """
        basis = self.K.cohomology_ring(base_ring=QQ).basis()
        indexes = list(basis.keys())
        indexes.sort(reverse=False)
#         print(indexes)
        for n in range(1,len(indexes)):
            if indexes[n][0] > 0:
                return indexes[n][0]
        return -1

Examples

In [76]:
S = SimplicialComplex([[0,1,2],[1,3],[2,3]],is_mutable=False) 
A = AlgebraAplK(S)
g = A.cohomology_gens(1)
g
Out[76]:
{(1, 0): h^{1,0}}
In [77]:
H1 = A.cohomology_group(1)
l = []
for v in H1.basis():
    l.append(H1.lift_map()(v))
show(l[0])
A.vector_to_cochain(l[0],1)
<html>\[\newcommand{\Bold}[1]{\mathbf{#1}}\left(1,\,0,\,-1,\,0,\,0\right)\]</html>
Out[77]:
{(0,): 0,
 (1,): 0,
 (2,): 0,
 (3,): 0,
 (0, 1): 1,
 (0, 2): 0,
 (1, 2): -1,
 (1, 3): 0,
 (2, 3): 0,
 (0, 1, 2): 0}
In [ ]:
 
In [78]:
gen = list(g.values())[0]
cochain = A.lift_cohomology_generator(gen)
cochain
Out[78]:
{(0,): 0,
 (1,): 0,
 (2,): 0,
 (3,): 0,
 (0, 1): 0,
 (0, 2): 0,
 (1, 2): 0,
 (1, 3): 0,
 (2, 3): 1,
 (0, 1, 2): 0}
In [79]:
A.get_cohomology_aplk(1)
Out[79]:
{(1,
  0): {(0, 1, 2): 0, (0, 1): 0, (0, 2): 0, (1, 2): 0, (1, 3): 0, (2, 3): y1, (0,): 0, (1,): 0, (2,): 0, (3,): 0}}
In [80]:
# get the cohomology generators of a given degree
S2 = SimplicialComplex([[0,1,2],[0,1,3],[0,2,3],[1,2,3],[3,4,5],[3,4,6],[3,5,6],[4,5,6]],is_mutable=False) 
A2 = AlgebraAplK(S2)
g = A2.cohomology_gens(2)
g
Out[80]:
{(2, 0): h^{2,0}, (2, 1): h^{2,1}}
In [81]:
# get a representative cochain for a given cohomology generator
gen = list(g.values())[0]
cochain = A2.lift_cohomology_generator(gen)
cochain
Out[81]:
{(0,): 0,
 (1,): 0,
 (2,): 0,
 (3,): 0,
 (4,): 0,
 (5,): 0,
 (6,): 0,
 (0, 1): 0,
 (0, 2): 0,
 (0, 3): 0,
 (1, 2): 0,
 (1, 3): 0,
 (2, 3): 0,
 (3, 4): 0,
 (3, 5): 0,
 (3, 6): 0,
 (4, 5): 0,
 (4, 6): 0,
 (5, 6): 0,
 (0, 1, 2): 0,
 (0, 1, 3): 0,
 (0, 2, 3): 0,
 (1, 2, 3): 1,
 (3, 4, 5): 0,
 (3, 4, 6): 0,
 (3, 5, 6): 0,
 (4, 5, 6): 0}
In [82]:
phi = A2.get_cohomology_aplk(2)
phi
Out[82]:
{(2,
  0): {(0, 1, 2): 0, (0, 1, 3): 0, (0, 2, 3): 0, (1, 2, 3): -2*y1*y2, (3, 4, 5): 0, (3, 4, 6): 0, (3, 5, 6): 0, (4, 5, 6): 0, (0, 1): 0, (0, 2): 0, (0, 3): 0, (1, 2): 0, (1, 3): 0, (2, 3): 0, (3, 4): 0, (3, 5): 0, (3, 6): 0, (4, 5): 0, (4, 6): 0, (5, 6): 0, (0,): 0, (1,): 0, (2,): 0, (3,): 0, (4,): 0, (5,): 0, (6,): 0},
 (2,
  1): {(0, 1, 2): 0, (0, 1, 3): 0, (0, 2, 3): 0, (1, 2, 3): 0, (3, 4, 5): 0, (3, 4, 6): 0, (3, 5, 6): 0, (4, 5, 6): -2*y1*y2, (0, 1): 0, (0, 2): 0, (0, 3): 0, (1, 2): 0, (1, 3): 0, (2, 3): 0, (3, 4): 0, (3, 5): 0, (3, 6): 0, (4, 5): 0, (4, 6): 0, (5, 6): 0, (0,): 0, (1,): 0, (2,): 0, (3,): 0, (4,): 0, (5,): 0, (6,): 0}}
In [ ]:
 

Adding new methods: find a primitive of a closed element

In [83]:
from sage.rings.polynomial.polydict import ETuple

class SimplicialAplN(SimplicialAplN,UniqueRepresentation):
    class Element(SimplicialAplN.Element):
        """
        Element class of the commutative differential multi-graded algebra (Apl)n.
        """

        def _get_primitive_basis_of_a_monomial(self, monomial):    
            """
            Returns a basis of the primitive of a given monomial

            Example: 

            """
            n_vars = len(monomial)/2
            combinations = []
            for i in range(n_vars):
                mono = list(copy.deepcopy(monomial))
                if mono[n_vars+i]:
                    mono[i] += 1
                    mono[n_vars+i] = 0
                    combinations.append(tuple(mono))
            return list(set(combinations))

        def _add_kernel_terms_to_primitive_basis(self, basis):    
            new_elements = []
            for e in basis:
                monomial = list(copy.deepcopy(e))
                n = len(e)/2
                monomial[:n] = n*[0]
                new_elements.append(tuple(monomial))
            return new_elements
        
        def _get_primitive_basis(self):
            """
            Returns a basis of the primitive of 'self'
            """
            basis = []
            p = self.reduce()
            for monomial in p.dict().keys():
                basis += p._get_primitive_basis_of_a_monomial(monomial)
            basis = list(set(basis))
            basis += p._add_kernel_terms_to_primitive_basis(basis)
            basis = list(set(basis))
            terms = []
            apl = self.parent()
            for b in basis:
                dim = len(monomial)/2-1
                terms.append(self._get_term(1, b, apl.gens()))
            terms.sort(reverse=False)
            return terms

        def _get_differential_basis(self):
            """
            Returns a basis of the differential of 'self'
            """
            primitive_basis = self._get_primitive_basis()
            differential_basis = []
            for b in primitive_basis:
                diff = b.differential().reduce()
                if diff: # as we want a basis, we don't consider the coeffient of the differential
                    for monomial in diff.dict().keys():
                        term = self._get_term(1, monomial, self.parent().gens())
                        differential_basis.append(term)
            differential_basis = list(set(differential_basis))
            differential_basis.sort(reverse=False)
            return differential_basis

        def _get_coordinate_vector_differential(self, diff_basis):
            """
            Returns the coordinates of 'self' 
            in terms of the differential basis
            """
            p_reduced = self.reduce()
            #dic = p.dict()
            #terms = [p_reduced.parent()({t: dic[t]}) for t in dic.keys()]
            monomials = list(p_reduced.dict().keys())
            v = vector(QQ,len(diff_basis)*[0])
            for i in range(len(diff_basis)):
                t = diff_basis[i]
                term = list(t.dict().keys())[0] # there is only one monomial
                if term in monomials:
                    v[i] = p_reduced.dict()[term]
            return v

        def _differential_matrix(self, primitive_basis, differential_basis):
            """
            Returns the matrix of the differential map
            """
            #primitive_basis = get_primitive_basis_of_a_polynomial(p.reduce())
            #differential_basis = get_differential_basis_of_a_polynomial(p.reduce())
            nrows = len(differential_basis)
            ncols = len(primitive_basis)
            M = matrix(QQ, nrows, ncols, nrows*ncols*[0])
            for j in range(ncols):
                primitive = primitive_basis[j]
                #diff_dict = self._tuple_differential(primitive)
                diff_primitive = primitive.differential().reduce()
                monomials = list(diff_primitive.dict().keys())
                for i in range(nrows):
                    diff_basis_element = differential_basis[i]
                    term = list(diff_basis_element.dict().keys())[0] # there is only one monomial
                    if term in monomials:
                        M[i,j] = diff_primitive.dict()[term] 
            return M

        def solve_primitive(self):
            p = self.reduce()
            primitive_basis = p._get_primitive_basis()
            diff_basis = p._get_differential_basis()
            v = p._get_coordinate_vector_differential(diff_basis)
            Mdif = p._differential_matrix(primitive_basis, diff_basis)
            sol = Mdif.solve_right(v)
            return sum(map(lambda x,y: x*y, sol, primitive_basis))
In [84]:
dim = 5
s = SimplicialAlgebraplN(dim)
s.inject_variables()
p = -3*t1^2 +10* y2*t3*t4
p = p.differential()
primitive_basis = p._get_primitive_basis()
primitive_basis
Defining t0, t1, t2, t3, t4, t5, y0, y1, y2, y3, y4, y5
Out[84]:
[1, y4, y3, y2, t1^2, t2*t3*y4, t2*t4*y3, t3*t4*y2]
In [85]:
diff_basis = p._get_differential_basis()
diff_basis
Out[85]:
[t1*y1, t2*y3*y4, t3*y2*y4, t4*y2*y3]
In [86]:
v = p._get_coordinate_vector_differential(diff_basis)
v
Out[86]:
(-6, 0, -10, -10)
In [87]:
Mdif = p._differential_matrix(primitive_basis, diff_basis)
Mdif
Out[87]:
[ 0  0  0  0  2  0  0  0]
[ 0  0  0  0  0  1 -1  0]
[ 0  0  0  0  0  1  0 -1]
[ 0  0  0  0  0  0  1 -1]
In [88]:
p.solve_primitive()
Out[88]:
-10*t2*t4*y3 - 10*t2*t3*y4 - 3*t1^2
In [89]:
p.solve_primitive().differential() == p
Out[89]:
True
In [ ]:
 

find an element of the preimage by the differential of an element of Alp(K)

In [90]:
class AlgebraAplK(AlgebraAplK, UniqueRepresentation):
    """
    
      The graded commutative differential cochain algebra Apl(K)
      
    """
    
    class Element(AlgebraAplK.Element):
        """
        Element class of the simplicial commutative differential algebra (Apl)n.
        """
        
        def _face_basis(self, basis, face_idx):
            face_basis = []
            for b in basis:
                face_pol = b.face(face_idx).reduce()
                if face_pol:
                    for monomial in face_pol.dict().keys():
                        #remove coefficients
                        term = face_pol._get_term(1, monomial, face_pol.parent().gens())
                        face_basis.append(term)
            face_basis = list(set(face_basis))
            face_basis.sort(reverse=False)
            return face_basis
            
        def _get_primitive_basis(self):
            
            primitive_basis = {}
            S = self.parent().simplicialComplex()
            dim = S.dimension()
            min_dim = self.degree()
            for n in reversed(range(min_dim,dim+1)):
                for simplex in S._n_cells_sorted(n):
                    if simplex.tuple() not in primitive_basis.keys():
                        primitive_basis[simplex.tuple()] = []
                    # add primitive elements related to the assigned polynomial
                    pol = self.elements[simplex.tuple()]
                    primitive_basis[simplex.tuple()] += pol._get_primitive_basis()
                    primitive_basis[simplex.tuple()] = list(set(primitive_basis[simplex.tuple()]))
                    primitive_basis[simplex.tuple()].sort(reverse=False)
                    # propagate the primitive basis through its faces
                    for f in range(n+1):
                        simplex_face = S.face(simplex,f).tuple()
                        if simplex_face not in primitive_basis.keys():
                            primitive_basis[simplex_face] = []
                        primitive_basis[simplex_face] += self._face_basis(primitive_basis[simplex.tuple()], f)
                        primitive_basis[simplex_face] = list(set(primitive_basis[simplex_face]))
                        primitive_basis[simplex_face].sort(reverse=False)
            return primitive_basis
        
        def _diff_basis(self, basis):
            diff_basis = []
            for b in basis:
                b_diff = b.differential().reduce()
                if b_diff:
                    for monomial in b_diff.dict().keys():
                        #remove coefficients
                        term = b_diff._get_term(1, monomial, b_diff.parent().gens())
                        diff_basis.append(term)
            diff_basis = list(set(diff_basis))
            diff_basis.sort(reverse=False)
            return diff_basis
            
        def _get_differential_basis(self, primitive_basis):
            differential_basis = {}
            S = self.parent().simplicialComplex()
            dim = S.dimension()
            min_dim = self.degree()
            for n in reversed(range(min_dim,dim+1)):
                for simplex in S._n_cells_sorted(n):
                    prim_diff_basis = primitive_basis[simplex.tuple()]
                    differential_basis[simplex.tuple()] = self._diff_basis(prim_diff_basis)
            return differential_basis
        
        def _get_differential_matrix(self, p_basis, d_basis):
            S = self.parent().simplicialComplex()
            dim = S.dimension()
            min_dim = self.degree()
            ncols = sum([len(l) for l in p_basis.values()])
            nrows = sum([len(l) for l in d_basis.values()])
            M = zero_matrix(QQ, nrows, ncols)
            row_offset = 0
            col_index = 0
            for n in reversed(range(min_dim-1,dim+1)):
                for simplex in S._n_cells_sorted(n):
#                     print(simplex.tuple(), row_offset, col_index)
                    if simplex.tuple() in p_basis.keys():
                        primitives = p_basis[simplex.tuple()]
                        if simplex.tuple() in d_basis.keys():
                            differentials = d_basis[simplex.tuple()]
                            if primitives and differentials:
                                for p in primitives:
                                    dif_p = p.differential().reduce()
                                    dic = dif_p.dict()
                                    for monomial in dic.keys():
                                        term  = dif_p._get_term(1, monomial, dif_p.parent().gens())
                                        if term in differentials:
                                            row_index = differentials.index(term)
                                            M[row_offset + row_index, col_index] = dic[monomial]
                                    col_index += 1
                                row_offset += len(differentials)
                            elif primitives:
                                col_index += len(primitives)
                        elif primitives:
                            col_index += len(primitives)
                        
            return M
        
        def _get_vector_diff_coordinates(self, d_basis):
            len_v = sum([len(l) for l in d_basis.values()])
            v = zero_vector(QQ, len_v)
            offset = 0
            S = self.parent().simplicialComplex()
            dim = S.dimension()
            min_dim = self.degree()
            for n in reversed(range(min_dim,dim+1)):
                for s in S._n_cells_sorted(n):
                    simplex = s.tuple()
                    if simplex in d_basis.keys():
                        differentials = d_basis[simplex]
                        pol = self.elements[simplex].reduce()
                        if pol and differentials:
                            dic = pol.dict()
                            for monomial in dic.keys():
                                term  = pol._get_term(1, monomial, pol.parent().gens())
                                if term in differentials:
                                    v_index = differentials.index(term)
                                    v[offset + v_index] = dic[monomial]
                        offset += len(differentials)
            return v
                    
        def _get_offsets_matrix(self, p_basis):
            offsets = {}
            S = self.parent().simplicialComplex()
            dim = S.dimension()
            min_dim = self.degree()
            offset = 0
            first_simplex = True
            for n in reversed(range(min_dim-1,dim+1)):
                for simplex in S._n_cells_sorted(n):
                    if simplex.tuple() in p_basis.keys():
                        primitives = p_basis[simplex.tuple()]
                        if first_simplex:
                            offsets[simplex.tuple()] = 0
                            first_simplex = False
                        else:
                            offsets[simplex.tuple()] = offset
                        offset += len(primitives)
            return offsets
        
        def _get_row_face_matrix(self, primitives, face_idx, face_basis,
                                 offset_simplex, offset_face, len_row):
            M = zero_matrix(QQ,1, len_row)
            for fb in face_basis:  
                fb_idx = face_basis.index(fb)
                row = zero_matrix(QQ,1, len_row)
                row[0,offset_face+fb_idx] = -1
                for term in primitives:
                    face_term = term.face(face_idx).reduce()
                    dic = face_term.dict()
                    i = primitives.index(term)
                    for monomial in dic:
                        t  = face_term._get_term(1, monomial, face_term.parent().gens())
                        if t == fb:
                            coef = dic[monomial]
                            row[0,offset_simplex+i] = coef
                M = M.stack(row)
            return M[1:]
        
        
        def _get_face_matrix(self, p_basis):
            
            offsets = self._get_offsets_matrix(p_basis)
            len_row = sum([len(l) for l in p_basis.values()])
            M = zero_matrix(QQ,1, len_row)
            min_dim = self.degree()
            for n in reversed(range(min_dim,dim+1)):
                for simplex in S._n_cells_sorted(n):
                    offset_simplex = offsets[simplex.tuple()]
                    primitives = p_basis[simplex.tuple()]
                    for f in range(n+1):
                        simplex_face = S.face(simplex,f).tuple()
                        face_primitives = p_basis[simplex_face]
                        offset_face = offsets[simplex_face]
                        rows = self._get_row_face_matrix(primitives, f, face_primitives,
                                                            offset_simplex, offset_face,
                                                            len_row)
                        M = M.stack(rows)
            return M[1:]
            
        def _get_solution(self, sol, p_basis):
            S = self.parent().simplicialComplex()
            dim = S.dimension()
            min_dim = self.degree()-1
            offset = 0
            values = {}
            for n in reversed(range(dim+1)):
                apl = SimplicialAlgebraplN(n)
                for s in S._n_cells_sorted(n):
                    simplex = s.tuple()
                    pol = apl(0)
                    if simplex in p_basis.keys():
                        primitives = p_basis[simplex]
                        if primitives:
                            for p in primitives:
                                i = primitives.index(p)
                                coef = sol[offset+i]
                                pol += coef*p
                            offset += len(primitives)
                    values[simplex] = pol                       
            A = self.parent()
#             return values
            return A._element_constructor_(values)
            
        def solve_primitive(self):
            p_basis = self._get_primitive_basis()
            d_basis =  self._get_differential_basis(p_basis)
            diff_matrix = self._get_differential_matrix(p_basis, d_basis)
            v_dif = self._get_vector_diff_coordinates(d_basis)
            face_matrix = self._get_face_matrix(p_basis)
            nrows = face_matrix.nrows()
            v = vector(QQ, list(v_dif)+nrows*[0])
            M = diff_matrix.stack(face_matrix)
            sol = M.solve_right(v)
            return self._get_solution(sol, p_basis)

Example

In [91]:
S = SimplicialComplex([[0,1,2,3,4]],is_mutable=False)
A = AlgebraAplK(S)
s0 = SimplicialAlgebraplN(0)
s1 = SimplicialAlgebraplN(1)
s2 = SimplicialAlgebraplN(2)
s3 = SimplicialAlgebraplN(3)
s4 = SimplicialAlgebraplN(4)
s2.inject_variables()
p012 = y1*y2
p013 = y1*y2
p023 = y1*y2
p024 = y1*y2
p034 = y1*y2
p123 = y1*y2
p124 = y1*y2
p134 = y1*y2
p234 = y1*y2

p0123 = p012.propagate([0,1,2],3) + p123.propagate([1,2,3],3)+p013.propagate([0,1,3],3)
p0134 = p013.propagate([0,1,2],3)
p0124 = p012.propagate([0,1,2],3)
p1234 = p123.propagate([0,1,2],3)
p01234 = p012.propagate([0,1,2],4) + p123.propagate([1,2,3],4) + p013.propagate([0,1,3],4)

values = {(0,): s0(0), (1,): s0(0), (2,): s0(0), (3,): s0(0), (4,): s0(0), 
          (0,1): s1(0), (0,2): s1(0), (0,3): s1(0), (0,4): s1(0),
          (1,2): s1(0), (1,3): s1(0), (1,4): s1(0),  
          (2,3): s1(0), (2,4): s1(0), 
          (3,4): s1(0), 
          (0,1,2): p012, (0,1,3): p013, (0,1,4): s2(0), 
          (0,2,3): s2(0), (0,2,4): s2(0), (0,3,4): s2(0),
          (1,2,3): p123, (1,2,4): s2(0), (1,3,4): s2(0),
          (2,3,4): s2(0), 
          (0,1,2,3): p0123,(0,1,2,4):p0124,(0,1,3,4): p0134,
          (0,2,3,4): s3(0),(1,2,3,4): p1234,
          (0,1,2,3,4): p01234}
e1 = A._element_constructor_(values)
dife1 = e1.differential()
dife1
Defining t0, t1, t2, y0, y1, y2
Out[91]:
{(0,): 0, (1,): 0, (2,): 0, (3,): 0, (4,): 0, (0, 1): 0, (0, 2): 0, (0, 3): 0, (0, 4): 0, (1, 2): 0, (1, 3): 0, (1, 4): 0, (2, 3): 0, (2, 4): 0, (3, 4): 0, (0, 1, 2): 0, (0, 1, 3): 0, (0, 1, 4): 0, (0, 2, 3): 0, (0, 2, 4): 0, (0, 3, 4): 0, (1, 2, 3): 0, (1, 2, 4): 0, (1, 3, 4): 0, (2, 3, 4): 0, (0, 1, 2, 3): 3*y1*y2*y3, (0, 1, 2, 4): -3*y1*y2*y3, (0, 1, 3, 4): -3*y1*y2*y3, (0, 2, 3, 4): 0, (1, 2, 3, 4): -3*y1*y2*y3, (0, 1, 2, 3, 4): 3*y1*y2*y3 - 3*y1*y2*y4 - 3*y1*y3*y4}
In [92]:
primitive = dife1.solve_primitive()
primitive
Out[92]:
{(0, 1, 2, 3, 4): t3*y1*y2 - t4*y1*y2 - t2*y1*y3 - t4*y1*y3 + t1*y2*y3 + t2*y1*y4 + t3*y1*y4 - t1*y2*y4 - t1*y3*y4 + y1*y2 + y1*y3, (0, 1, 2, 3): t3*y1*y2 - t2*y1*y3 + t1*y2*y3 + y1*y2 + y1*y3, (0, 1, 2, 4): -t3*y1*y2 + t2*y1*y3 - t1*y2*y3 + y1*y2, (0, 1, 3, 4): -t3*y1*y2 + t2*y1*y3 - t1*y2*y3 + y1*y2, (0, 2, 3, 4): 0, (1, 2, 3, 4): -t3*y1*y2 + t2*y1*y3 - t1*y2*y3 + y1*y2, (0, 1, 2): y1*y2, (0, 1, 3): y1*y2, (0, 1, 4): 0, (0, 2, 3): 0, (0, 2, 4): 0, (0, 3, 4): 0, (1, 2, 3): y1*y2, (1, 2, 4): 0, (1, 3, 4): 0, (2, 3, 4): 0, (0, 1): 0, (0, 2): 0, (0, 3): 0, (0, 4): 0, (1, 2): 0, (1, 3): 0, (1, 4): 0, (2, 3): 0, (2, 4): 0, (3, 4): 0, (0,): 0, (1,): 0, (2,): 0, (3,): 0, (4,): 0}
In [93]:
primitive.differential() == dife1
Out[93]:
True
In [94]:
primitive == e1
Out[94]:
True
In [ ]:
 
In [ ]:
 

Example Torus

In [95]:
# S = simplicial_complexes.Torus()
# A = AlgebraAplK(S)
# s0 = SimplicialAlgebraplN(0)
# s1 = SimplicialAlgebraplN(1)
# s2 = SimplicialAlgebraplN(2)
# s2.inject_variables()
# values = {(0, 1, 2): s2(0), (0, 1, 5): s2(0), (0, 2, 6): s2(0), (0, 3, 4): s2(0), (0, 3, 5): s2(0), 
#           (0, 4, 6): s2(0), (1, 2, 4): s2(0), (1, 3, 4): s2(0), (1, 3, 6): -y1*y2, (1, 5, 6): s2(0), 
#           (2, 3, 5): s2(0), (2, 3, 6): s2(0), (2, 4, 5): y1*y2, (4, 5, 6): s2(0), 
#           (0, 1): s1(0), (0, 2): s1(0), (0, 3): s1(0), (0, 4): s1(0), (0, 5): s1(0), (0, 6): s1(0), 
#           (1, 2): s1(0), (1, 3): s1(0), (1, 4): s1(0), (1, 5): s1(0), (1, 6): s1(0), (2, 3): s1(0), (2, 4): s1(0), 
#           (2, 5): s1(0), (2, 6): s1(0), (3, 4): s1(0), (3, 5): s1(0), (3, 6): s1(0), (4, 5): s1(0), (4, 6): s1(0), (5, 6): s1(0), 
#           (0,): s0(0), (1,): s0(0), (2,): s0(0), (3,): s0(0), (4,): s0(0), (5,): s0(0), (6,): s0(0)}
# e = A._element_constructor_(values)
# preimage = e.solve_primitive()
In [ ]:
 

Test with random complexes and random elements of Apl(K)

In [96]:
def sublist(lst1, lst2):
    return set(lst1) <= set(lst2)

def sublist_of_lists(list1,list_of_lists):
    is_sublist = False
    for list2 in list_of_lists:
        if sublist(list1,list2):
            is_sublist = True
            break
    return is_sublist

def random_simplicial_complex(num_vertices,max_num_facets):
    vertices = list(range(num_vertices))
    simplices = []
    partitions = []
    for length in range(num_vertices):
        partitions += [tuple(e) for e in powerset(vertices) if len(e)==length and e]
    for i in range(max_num_facets):
        r = randint(0,len(partitions)-1)
        candidate = partitions[r]
        if not sublist_of_lists(candidate,simplices):
            simplices.append(candidate)
    return SimplicialComplex(simplices,is_mutable=False)

def random_cochain(S):
    dim = S.dimension()
    cochain = {}
    for n in range(dim+1):
        for s in S._n_cells_sorted(n):
            cochain[s.tuple()] = randint(-100,100)
    return cochain
In [97]:
S = random_simplicial_complex(6,4)
# S = SimplicialComplex([5],is_mutable=False)
cochain = random_cochain(S)
S
Out[97]:
Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and facets {(0,), (2, 3), (4, 5), (1, 2, 5)}
In [98]:
A = AlgebraAplK(S)
e = A.lift_cochain(cochain)
dife = e.differential()
In [99]:
hom_parts = dife.homogeneous_parts()
recover = A.null_element()
for deg in hom_parts.keys():
    print("deg: ", deg)
    recover += hom_parts[deg].solve_primitive()
print("dif(recover) == dif(e): ", recover.differential() == dife)
# print("recover == e: ", recover == e) # Although their differentials are equal, this is not always True
deg:  1
deg:  2
dif(recover) == dif(e):  True
In [ ]:
 

Example

In [100]:
S = SimplicialComplex([[0,1,2,3]],is_mutable=False)
A = AlgebraAplK(S)
s0 = SimplicialAlgebraplN(0)
s1 = SimplicialAlgebraplN(1)
s2 = SimplicialAlgebraplN(2)
s3 = SimplicialAlgebraplN(3)
s3.inject_variables()
p0123 = y1*y2*y3
values = {(0,): s0(0), (1,): s0(0), (2,): s0(0), (3,): s0(0), (0,1): s1(0), (0,2): s1(0),
          (0,3): s1(0), (1,2): s1(0), (1,3): s1(0), (2,3): s1(0), (0,1,2): s2(0), (0,1,3): s2(0), 
          (0,2,3): s2(0), (1,2,3): s2(0), (0,1,2,3): p0123}
e1 = A._element_constructor_(values)
print("")
print("e1:")
print(e1)
print("")
e2 = e1.solve_primitive()
print("e2")
print(e2)
print("")
print("d(e2) == e1: ", e2.differential() == e1)
Defining t0, t1, t2, t3, y0, y1, y2, y3

e1:
{(0,): 0, (1,): 0, (2,): 0, (3,): 0, (0, 1): 0, (0, 2): 0, (0, 3): 0, (1, 2): 0, (1, 3): 0, (2, 3): 0, (0, 1, 2): 0, (0, 1, 3): 0, (0, 2, 3): 0, (1, 2, 3): 0, (0, 1, 2, 3): y1*y2*y3}

e2
{(0, 1, 2, 3): 1/3*t3*y1*y2 - 1/3*t2*y1*y3 + 1/3*t1*y2*y3 - 1/3*y1*y2, (0, 1, 2): -1/3*y1*y2, (0, 1, 3): 0, (0, 2, 3): 0, (1, 2, 3): 0, (0, 1): 0, (0, 2): 0, (0, 3): 0, (1, 2): 0, (1, 3): 0, (2, 3): 0, (0,): 0, (1,): 0, (2,): 0, (3,): 0}

d(e2) == e1:  True
In [ ]:
 
In [ ]:
 

Example of degree 0

In [101]:
S = SimplicialComplex([[0,1,2], [0,1,3]],is_mutable=False)
A = AlgebraAplK(S)
s0 = SimplicialAlgebraplN(0)
s2 = SimplicialAlgebraplN(2)
s2.inject_variables()
p012 = y0
p013 = y0
values = {(0,): s0(0), (1,): s0(0), (2,): s0(0), (3,): s0(0), (0,1): p012.face(2), (0,2): p012.face(1),
          (0,3): p013.face(1), (1,2): p012.face(0), (1,3): p013.face(0), (0,1,2): p012, (0,1,3): p013}
e1 = A._element_constructor_(values)
e2 = e1.solve_primitive()
e2.differential() == e1
Defining t0, t1, t2, y0, y1, y2
Out[101]:
True
In [ ]:
 

Example

In [102]:
S = SimplicialComplex([[0,1,2,3]],is_mutable=False)
A = AlgebraAplK(S)
s0 = SimplicialAlgebraplN(0)
s1 = SimplicialAlgebraplN(1)
s2 = SimplicialAlgebraplN(2)
s3 = SimplicialAlgebraplN(3)
s3.inject_variables()
p0123 = y1*y2*y3
values = {(0,): s0(0), (1,): s0(0), (2,): s0(0), (3,): s0(0), (0,1): s1(0), (0,2): s1(0),
          (0,3): s1(0), (1,2): s1(0), (1,3): s1(0), (2,3): s1(0), (0,1,2): s2(0), (0,1,3): s2(0), 
          (0,2,3): s2(0), (1,2,3): s2(0), (0,1,2,3): p0123}
e1 = A._element_constructor_(values)
primitive = e1.solve_primitive()
primitive.differential() == e1
Defining t0, t1, t2, t3, y0, y1, y2, y3
Out[102]:
True
In [ ]:
 

Another example

In [103]:
S = SimplicialComplex([[0,1,2,3,4]],is_mutable=False)
A = AlgebraAplK(S)
s0 = SimplicialAlgebraplN(0)
s1 = SimplicialAlgebraplN(1)
s2 = SimplicialAlgebraplN(2)
s3 = SimplicialAlgebraplN(3)
s4 = SimplicialAlgebraplN(4)
s2.inject_variables()
p012 = y1*y2
p013 = y1*y2
p023 = y1*y2
p024 = y1*y2
p034 = y1*y2
p123 = y1*y2
p124 = y1*y2
p134 = y1*y2
p234 = y1*y2

p0123 = p012.propagate([0,1,2],3) + p123.propagate([1,2,3],3)+p013.propagate([0,1,3],3)
p0134 = p013.propagate([0,1,2],3)
p0124 = p012.propagate([0,1,2],3)
p1234 = p123.propagate([0,1,2],3)
p01234 = p012.propagate([0,1,2],4) + p123.propagate([1,2,3],4) + p013.propagate([0,1,3],4)

values = {(0,): s0(0), (1,): s0(0), (2,): s0(0), (3,): s0(0), (4,): s0(0), 
          (0,1): s1(0), (0,2): s1(0), (0,3): s1(0), (0,4): s1(0),
          (1,2): s1(0), (1,3): s1(0), (1,4): s1(0),  
          (2,3): s1(0), (2,4): s1(0), 
          (3,4): s1(0), 
          (0,1,2): p012, (0,1,3): p013, (0,1,4): s2(0), 
          (0,2,3): s2(0), (0,2,4): s2(0), (0,3,4): s2(0),
          (1,2,3): p123, (1,2,4): s2(0), (1,3,4): s2(0),
          (2,3,4): s2(0), 
          (0,1,2,3): p0123,(0,1,2,4):p0124,(0,1,3,4): p0134,
          (0,2,3,4): s3(0),(1,2,3,4): p1234,
          (0,1,2,3,4): p01234}
e1 = A._element_constructor_(values)
e1
Defining t0, t1, t2, y0, y1, y2
Out[103]:
{(0,): 0, (1,): 0, (2,): 0, (3,): 0, (4,): 0, (0, 1): 0, (0, 2): 0, (0, 3): 0, (0, 4): 0, (1, 2): 0, (1, 3): 0, (1, 4): 0, (2, 3): 0, (2, 4): 0, (3, 4): 0, (0, 1, 2): y1*y2, (0, 1, 3): y1*y2, (0, 1, 4): 0, (0, 2, 3): 0, (0, 2, 4): 0, (0, 3, 4): 0, (1, 2, 3): y1*y2, (1, 2, 4): 0, (1, 3, 4): 0, (2, 3, 4): 0, (0, 1, 2, 3): t3*y1*y2 - t2*y1*y3 + t1*y2*y3 + y1*y2 + y1*y3, (0, 1, 2, 4): -t3*y1*y2 + t2*y1*y3 - t1*y2*y3 + y1*y2, (0, 1, 3, 4): -t3*y1*y2 + t2*y1*y3 - t1*y2*y3 + y1*y2, (0, 2, 3, 4): 0, (1, 2, 3, 4): -t3*y1*y2 + t2*y1*y3 - t1*y2*y3 + y1*y2, (0, 1, 2, 3, 4): t3*y1*y2 - t4*y1*y2 - t2*y1*y3 - t4*y1*y3 + t1*y2*y3 + t2*y1*y4 + t3*y1*y4 - t1*y2*y4 - t1*y3*y4 + y1*y2 + y1*y3}
In [104]:
dife1 = e1.differential()
#dife1
In [105]:
recovery = dife1.solve_primitive()
print("dif(recovery) == diff(e1): ",recovery.differential() == dife1)
#recovery
dif(recovery) == diff(e1):  True
In [ ]:
 

Minimal model

In [106]:
def update_cdga(gca, diff_dict={}):

    gens_dict = gca.gens_dict()
    differentials = {}
    if diff_dict:
        for gen_name in diff_dict.keys():
            gen = gens_dict[gen_name]
            product_gens = diff_dict[gen_name]
            gen0 = gens_dict[product_gens[0]]
            if type(product_gens[1]) == int: # is a power
                power = product_gens[1]
                mult = gen0
                for i in range(1, power):
                    mult *= gen0
                differentials.update({gen:mult})
            else:
                gen1 = gens_dict[product_gens[1]]
                differentials.update({gen:gen0*gen1})
#     print("differentials", differentials)
    return gca.cdg_algebra(differentials) 

    
def has_preimage(M, element):
    
    d = M.differential()
    deg = element.degree()-1
#     print("differential of ", element, "degree: ", deg)
    Md = d.differential_matrix(deg)
    gens = M.basis(deg)
    try:
        solution = Md.solve_left(vector(QQ,element.basis_coefficients()))
        primitive = sum(map(lambda coef, gen: coef*gen, solution, gens))
        print(element, " has preimage ", primitive)
        return True
    except ValueError:
        return False

def injectivity_step(M, phi, check_dim, names, 
                     degrees, differentials, index=0, depth=0, max_depth=5):
    
    print("Injectivity at dimension ", check_dim)
    if depth > max_depth:
        return [M, phi, names, degrees, differentials, True]
    deg = check_dim -1
    gens = [g for g in M.gens() if g.degree() < check_dim]
#     gens.sort(reverse=True)
    gen_products = []
    for i in range(len(gens)):
        e0 = gens[i]
        d = e0.degree()
        mcd = gcd(d, check_dim)
        if d%2 == 0 and mcd == d:
            power = True
            exp = check_dim / d
            mult = e0
            for j in range(1,exp-1):
                mult *= e0
            gen_products.append([e0, mult, power, exp])
        for j in range(i+1,len(gens)):
            power = False
            e1 = gens[j]
            if e0.degree() + e1.degree() == check_dim:
                gen_products.append([e0, e1, power])
#     print("products at dim ",check_dim,": ", gen_products)
    new_M = M
    new_phi = phi
    new_names = names
    new_degrees = degrees
    new_differentials = differentials
    for pair in gen_products:
        # Looking at the minimal model side
        gen0 = pair[0]
        deg_0 = gen0.degree()
        gen1 = pair[1]
        deg_1 = gen1.degree()
        M_product = gen0*gen1
        # Looking at the AplK side
        gen0_name = list(M.gens_dict().keys())[list(M.gens_dict().values()).index(gen0)]
        aplk_gen0 = phi[deg_0][gen0_name]
        is_power = pair[2]
        power = 0
        if is_power:
            power = pair[3]
            aplk_gen1 = aplk_gen0
            for i in range(1, power):
                aplk_gen1 *= aplk_gen0
        else:
            gen1_name = list(M.gens_dict().keys())[list(M.gens_dict().values()).index(gen1)]
            aplk_gen1 = phi[deg_1][gen1_name]
        aplk_product = aplk_gen0*aplk_gen1
        A = aplk_product.parent()
        # Check conditions
        if (M_product == M(0)) and (aplk_product == A.null_element()): # zero element
            pass
        elif (M_product == M(0)) and (aplk_product != A.null_element()): #check
            if aplk_product.is_closed():
                pass #trivial cohomology class (assuming aplk_product has preimage by differential)
            else:
                raise ValueError('zero associated to a non-closed element in AplK')
        elif (M_product.differential() != 0): # is not closed
            print("The element ",M_product, " is not closed")
            pass   
        elif (M_product.differential() == 0) and aplk_product.is_closed() and has_preimage(M, M_product):
            pass #trivial cohomology class (assuming aplk_product has preimage by differential)
        elif (M_product.differential() == 0) and aplk_product.is_closed(): #key step because M_product doesn't have preimage
            new_gen = 'y'+str(deg)+'_'+str(index)
            if aplk_product != A.null_element():
                try:
                    preimage = aplk_product.solve_primitive()
                except ValueError:
                    print(" The element ",M_product, " has non trivial cohomology class. No generator added.")
                    continue # we don't add a new generator because the element has non trivial cohomology class
                phi_new_gen = {new_gen: preimage}
            else: 
                phi_new_gen = {new_gen: A.null_element()}
            print(" Proceed to kill", M_product, " because has associated trivial cohomology class")
            print("Adding generator "+new_gen)
            new_names = names + [new_gen]
            new_degrees = tuple(list(degrees)+[deg])
            new_differentials = copy.deepcopy(differentials)
            if not is_power:
                new_differentials.update({new_gen: [gen0_name, gen1_name]})
            else:
                new_differentials.update({new_gen: [gen0_name, int(power)]})
            gca = GradedCommutativeAlgebra(QQ, names=new_names, degrees=new_degrees)
            new_M = update_cdga(gca, new_differentials)
            new_phi = copy.deepcopy(phi)
            d = new_phi[deg]
            d.update(phi_new_gen)
            return injectivity_step(new_M, new_phi, check_dim, new_names, 
                             new_degrees, new_differentials, index=index+1, depth=depth+1, max_depth=max_depth)
        else:
            pass

    return [new_M, new_phi, new_names, new_degrees, new_differentials, False]

def get_unmapped_generators(dim, coho_aplk, coho_gens, correspondence):

    dimensions = list(correspondence.keys())
    unmmaped_elements = copy.deepcopy(coho_aplk)
    for gen_key in coho_gens.keys():
        gen = coho_gens[gen_key]
        found = False
        for i in range(1,dim):
            if (not found) and (i in dimensions):
                for j in range(i,dim):
                    if (j in dimensions) and (i+j == dim):
                        gensi = correspondence[i]
                        gensj = correspondence[j]
                        for gi in gensi.values():
                            if not found:
                                for gj in gensj.values():
                                    product = gi.cup_product(gj) 
                                    if product == gen or -product == gen:
                                        found = True
                                        i_name = list(gensi.keys())[list(gensi.values()).index(gi)]
                                        j_name = list(gensj.keys())[list(gensj.values()).index(gj)]
                                        if gen_key in unmmaped_elements.keys():
                                            print("The element ",i_name+"*"+j_name," is already mapped")
                                            del unmmaped_elements[gen_key]
                                        break
    return unmmaped_elements

def surjective_step(A, M, phi, dim, names, degrees, differentials, cohomology_correspondence):
    
    print("Surjectivity at dimension ", dim)
    coho_aplk = A.get_cohomology_aplk(dim)
    cohomology_gens = A.cohomology_gens(dim)
    g = get_unmapped_generators(dim, coho_aplk, cohomology_gens, cohomology_correspondence)
    new_names = copy.deepcopy(names)
    new_degrees = copy.deepcopy(degrees)
    new_phi = copy.deepcopy(phi)
    new_correspondence = copy.deepcopy(cohomology_correspondence)
    phi_n_map = {}
    correspondence_n_map = {}
    for gen_key in g.keys():
        deg = gen_key[0]
        index = gen_key[1]
        new_var = 'x'+str(deg)+'_'+str(index)
        new_names += [new_var]
        new_degrees += [deg]
        phi_n_map[new_var] = g[gen_key]
        correspondence_n_map[new_var] = cohomology_gens[gen_key]
    gca = GradedCommutativeAlgebra(QQ, names=new_names, degrees=new_degrees)
    new_M = update_cdga(gca, differentials)
    new_phi[dim] = phi_n_map
    new_correspondence[dim] = correspondence_n_map
    return [new_M, new_phi, new_names, new_degrees, new_correspondence]
    
def minimal_model(S, max_depth=5, max_dim=5):
    
    fundamental_group = S.fundamental_group()
    if (fundamental_group.order() != 1):
        print("Warning: The space is not simply-connected")
    phi = {}
    cohomology_correspondence = {}
    A = AlgebraAplK(S)
    # First step of the algorithm
    first_dim = A.first_cohomology_group()
    phi_n_map = {}
    correspondence_n_map = {}
    if first_dim < 0:
        return [0,{},{}] # due to the function returns multiple objects
    elif first_dim > max_dim:
        return [0,{},{}] # due to the function returns multiple objects
    g = A.get_cohomology_aplk(first_dim)
    cohomology_gens = A.cohomology_gens(first_dim)
    names = []
    degrees = []
    for gen_key in g.keys():
        deg = gen_key[0]
        index = gen_key[1]
        new_var = 'x'+str(deg)+'_'+str(index)
        names += [new_var]
        degrees += [deg]
        phi_n_map[new_var] = g[gen_key]
        correspondence_n_map[new_var] = cohomology_gens[gen_key]
    differentials={}
    gca = GradedCommutativeAlgebra(QQ, names=names, degrees=degrees)
    M = gca.cdg_algebra(differentials)
    phi[first_dim] = phi_n_map
    cohomology_correspondence[first_dim] = correspondence_n_map
    # Now we have an isomorphism in the first non-null cohomology group
    # we check if phi is a monomorphism
    [M, phi, names, degrees, differentials, limit] = injectivity_step(M, phi, first_dim+1, names, 
                     degrees, differentials, index=0, depth=0, max_depth=5)
    if limit:
        print("The algorithm has reach the injectivity depth-limit at dimension ", first_dim)
        return [M, phi]
    else:
        dim = first_dim + 1
        for i in range(dim, max_dim):
            [M, phi, names, degrees, cohomology_correspondence] = surjective_step(A, M, phi, i, names, degrees, 
                                                       differentials, cohomology_correspondence)
            [M, phi, names, degrees, differentials, limit] = injectivity_step(M, phi, i+1, names, 
                     degrees, differentials, index=0, depth=0, max_depth=5)
            if limit:
                print("The algorithm has reach the injectivity depth-limit at dimension", dim)
                break            
        
    return [M, phi, cohomology_correspondence]

# def minimal_model():
#     names = ['x1_0','x1_1']
#     degrees = (1,1)
#     differentials={}
#     gca = GradedCommutativeAlgebra(QQ, names=names, degrees=degrees)
#     cdga = gca.cdg_algebra(differentials)
#     print(cdga)
#     # añadimos el generador de grado uno con differencial producto de los anteriores
#     new_gen = 'y1_0'
#     names = names + [new_gen]
#     deg = 1
#     degrees = tuple(list(degrees)+[deg])
#     differentials.update({'y1_0':['x1_0','x1_1']})
#     gca = GradedCommutativeAlgebra(QQ, names=names, degrees=degrees)
#     new_cdga = update_cdga(gca, differentials)
#     print(new_cdga)
In [107]:
# S = SimplicialComplex([[0,1],[0,2],[1,2]],is_mutable=False) # S^1
# S = SimplicialComplex([[0,1],[0,2],[1,2],[1,3],[2,3]],is_mutable=False) # S^1 wedge S^1
# S = SimplicialComplex([[0,1,2],[0,1,3],[0,2,3],[1,2,3]],is_mutable=False) # S^2
# S = SimplicialComplex([[0,1,2],[0,1,3],[0,2,3],[1,2,3],[3,4],[3,5],[4,5]],is_mutable=False) # S^1 wedge S^2
# S = SimplicialComplex([[0,1,2],[0,3,4],[1,3,5]],is_mutable=False) # S^1 surrounded by 2-cells
# S = SimplicialComplex([[0,1,2],[0,1,3],[0,2,3],[1,2,3],[3,4,5],[3,4,6],[3,5,6],[4,5,6]],is_mutable=False) # S^2 wedge S^2
# S = SimplicialComplex([[0,1,2],[0,1,3],[0,2,3],[1,2,3],[0,2,4],[0,3,4],[2,3,4]],is_mutable=False) # S^2 wedge S^2
# S = SimplicialComplex([[0,2,3],[0,2,4,5],[0,1,2,5],[1,2,3,6],[2,3,4,6],[0,1,3,7],[0,3,4,7]],is_mutable=False) # S^2 wedge S^2
# S = simplicial_complexes.Torus()
#######################################################
# S = simplicial_complexes.Sphere(2)
#######################################################
# S = simplicial_complexes.K3Surface()
#######################################################
S1 = simplicial_complexes.Sphere(2)
S2 = simplicial_complexes.Sphere(2)
S = S1.wedge(S2, is_mutable=False)
#######################################################
#S = simplicial_complexes.ComplexProjectivePlane()
#######################################################
# S = simplicial_complexes.BrucknerGrunbaumSphere()
#######################################################
# S = simplicial_complexes.ProjectivePlane().wedge(simplicial_complexes.Sphere(2), is_mutable=False)
#######################################################
[sullivan_model, model_morphism, cohomology_correspondence] = minimal_model(S, max_depth=5, max_dim=10)
sullivan_model
Injectivity at dimension  3
Surjectivity at dimension  3
Injectivity at dimension  4
 Proceed to kill x2_0^2  because has associated trivial cohomology class
Adding generator y3_0
Injectivity at dimension  4
x2_0^2  has preimage  y3_0
 Proceed to kill x2_0*x2_1  because has associated trivial cohomology class
Adding generator y3_1
Injectivity at dimension  4
x2_0^2  has preimage  y3_0
x2_0*x2_1  has preimage  y3_1
 Proceed to kill x2_1^2  because has associated trivial cohomology class
Adding generator y3_2
Injectivity at dimension  4
x2_0^2  has preimage  y3_0
x2_0*x2_1  has preimage  y3_1
x2_1^2  has preimage  y3_2
Surjectivity at dimension  4
Injectivity at dimension  5
The element  x2_0*y3_0  is not closed
The element  x2_0*y3_1  is not closed
The element  x2_0*y3_2  is not closed
The element  x2_1*y3_0  is not closed
The element  x2_1*y3_1  is not closed
The element  x2_1*y3_2  is not closed
Surjectivity at dimension  5
Injectivity at dimension  6
x2_0^3  has preimage  x2_0*y3_0
x2_1^3  has preimage  x2_1*y3_2
The element  y3_0*y3_1  is not closed
The element  y3_0*y3_2  is not closed
The element  y3_1*y3_2  is not closed
Surjectivity at dimension  6
Injectivity at dimension  7
Surjectivity at dimension  7
Injectivity at dimension  8
x2_0^4  has preimage  x2_0^2*y3_0
x2_1^4  has preimage  x2_1^2*y3_2
Surjectivity at dimension  8
Injectivity at dimension  9
Surjectivity at dimension  9
Injectivity at dimension  10
x2_0^5  has preimage  x2_0^3*y3_0
x2_1^5  has preimage  x2_1^3*y3_2
Out[107]:
Commutative Differential Graded Algebra with generators ('x2_0', 'x2_1', 'y3_0', 'y3_1', 'y3_2') in degrees (2, 2, 3, 3, 3) over Rational Field with differential:
   x2_0 --> 0
   x2_1 --> 0
   y3_0 --> x2_0^2
   y3_1 --> x2_0*x2_1
   y3_2 --> x2_1^2
In [108]:
sullivan_model.basis(6)
Out[108]:
[x2_0^3, x2_0^2*x2_1, x2_0*x2_1^2, x2_1^3, y3_0*y3_1, y3_0*y3_2, y3_1*y3_2]
In [109]:
sullivan_model.cohomology_generators(10)
Out[109]:
{2: [x2_0, x2_1], 5: [x2_1*y3_0 - x2_0*y3_1, x2_1*y3_1 - x2_0*y3_2]}
In [110]:
a = model_morphism[2]['x2_0']^2
b = model_morphism[3]['y3_0'] # no deberia salir 0
In [111]:
a.solve_primitive()
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/tmp/ipykernel_7283/764669733.py in <module>
----> 1 a.solve_primitive()

/tmp/ipykernel_7283/334124851.py in solve_primitive(self)
    214 
    215         def solve_primitive(self):
--> 216             p_basis = self._get_primitive_basis()
    217             d_basis =  self._get_differential_basis(p_basis)
    218             diff_matrix = self._get_differential_matrix(p_basis, d_basis)

/tmp/ipykernel_7283/334124851.py in _get_primitive_basis(self)
     35                         primitive_basis[simplex.tuple()] = []
     36                     # add primitive elements related to the assigned polynomial
---> 37                     pol = self.elements[simplex.tuple()]
     38                     primitive_basis[simplex.tuple()] += pol._get_primitive_basis()
     39                     primitive_basis[simplex.tuple()] = list(set(primitive_basis[simplex.tuple()]))

KeyError: ()
In [ ]:
 
In [112]:
S1 = simplicial_complexes.Sphere(2)
S2 = simplicial_complexes.Sphere(2)
S = S1.wedge(S2, is_mutable=False)
A = AlgebraAplK(S)
In [113]:
S.cohomology_ring().basis()
Out[113]:
Finite family {(0, 0): h^{0,0}, (2, 0): h^{2,0}, (2, 1): h^{2,1}}
In [ ]:
 
In [137]:
#def get_ker_elements(sullivan_model, model_morphism, A, dim):
dim = 5
# Get the basis of Hm
hm_basis = sullivan_model.cohomology(dim).basis()
Hc = A.cohomology_group(dim)
apl_correspondence = {}
cochain_correspondence = {}
Hc_correspondence = {}
M = zero_matrix(QQ,len(Hc.basis()),len(hm_basis))
i = 0
hm_basis_keys = list(hm_basis.keys())
for gen in hm_basis_keys:
    # get an element of the basis of Hm
    # get a representative of a cohomology class in M
    gen_repr = gen.representative()
    # get the apl representative for each generator of M^p
    m_gens = gen_repr.parent().gens()
    aplgens = [model_morphism[g.degree()][str(g)] for g in m_gens]
    apl_correspondence[gen] = A.null_element()
    for exp in gen_repr.dict().keys():
        coef = gen_repr.dict()[exp]
        term = coef*prod([gen^e for gen,e in zip(aplgens, exp)])
        apl_correspondence[gen] += term
    cochain = apl_correspondence[gen].integral()
    cochain_correspondence[gen] = cochain
    vcochain = A.cochain_to_vector(cochain,dim)
    Hc_correspondence[gen] = Hc(vcochain)
    M[:,i] = Hc_correspondence[gen]
    i+=1
    
Hc_correspondence

# falta crear la matriz
Out[137]:
{[x2_1*y3_0 - x2_0*y3_1]: (), [x2_1*y3_1 - x2_0*y3_2]: ()}
In [138]:
Kgens = M.right_kernel().gens()
Kgens
Out[138]:
((1, 0), (0, 1))
In [148]:
[e.representative() for e in hm_basis_keys]
Out[148]:
[x2_1*y3_0 - x2_0*y3_1, x2_1*y3_1 - x2_0*y3_2]
In [ ]:
gdca_basis = gen_repr.parent().basis(gen_repr.degree())
gdca_basis
In [ ]:
print(gen_repr.dict())
gen_repr.parent().gens()
In [ ]:
Hc_2 = A.cohomology_group(2).basis()
#A.lift_cochain(A.vector_to_cochain(Hc_2.lift((0,1)),2))
Hc_2
In [ ]:
element = Hm_basis[2][0] + Hm_basis[2][1]
def integralPhiCohomology(element):
    aplelement = model_morphism[2][str(element)]
    cochain = aplelement.integral()
    vcochain = A.cochain_to_vector(cochain,2)
    Hc_2 = A.cohomology_group(2)
    return Hc_2(vcochain)
integralPhiCohomology(element)
In [ ]:
Hc_basis = A.cohomology_gens(2)
cochain0 = A.lift_cohomology_generator(list(Hc_basis.values())[0])
cochain1 = A.lift_cohomology_generator(list(Hc_basis.values())[1])
vcochain0 = A.cochain_to_vector(cochain0,2)
vcochain1 = A.cochain_to_vector(cochain1,2)
vcochain = vcochain0-3*vcochain1
In [ ]:
Hc_2 = A.cohomology_group(2)
Hc_2(vcochain)
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 

Example step by step

In [ ]:
K = simplicial_complexes.ComplexProjectivePlane()
A = AlgebraAplK(K)
In [ ]:
# get the lowest dim for which H^{dim} is not trivial
first_dim = A.first_cohomology_group()
first_dim
In [ ]:
g = A.get_cohomology_aplk(first_dim)
In [ ]:
g = A.get_cohomology_aplk(first_dim)
cohomology_gens = A.cohomology_gens(first_dim)
In [ ]:
cohomology_gens
In [ ]:
names = []
degrees = []
phi = {}
phi_n_map = {}
cohomology_correspondence = {}
correspondence_n_map = {}
for gen_key in g.keys():
    deg = gen_key[0]
    index = gen_key[1]
    new_var = 'x'+str(deg)+'_'+str(index)
    names += [new_var]
    degrees += [deg]
    phi_n_map[new_var] = g[gen_key]
    correspondence_n_map[new_var] = cohomology_gens[gen_key]
differentials={}
gca = GradedCommutativeAlgebra(QQ, names=names, degrees=degrees)
M = gca.cdg_algebra(differentials)
phi[first_dim] = phi_n_map
cohomology_correspondence[first_dim] = correspondence_n_map
In [ ]:
r = phi[2]['x2_0']
In [ ]:

In [ ]:
count = 0
for s in r.elements.keys():
    if len(s)==5:
        v = r.elements[s]
        if v != 0:
            count += 1
            print(s, " ==> ", v)
print(count)
In [ ]:
r = (phi[2]['x2_0']^2)
In [ ]:
count = 0
for s in r.elements.keys():
    if len(s)==5:
        v = r.elements[s]
        if v != 0:
            count += 1
            print(s, " ==> ", v)
print(count)
In [ ]:
M.cohomology(4)
In [ ]:
A.cohomology_gens(4)
In [ ]:
c = M.cohomology(4)
c
In [ ]:
K.cohomology(4,base_ring=QQ)
In [ ]:
M
In [ ]:
dim = 2
# Get the basis of Hm
hm_basis = M.cohomology(dim).basis()
Hc = A.cohomology_group(dim)
apl_correspondence = {}
cochain_correspondence = {}
Hc_correspondence = {}
Mat = zero_matrix(QQ,len(Hc.basis()),len(hm_basis))
i = 0
for gen in list(hm_basis.keys()):
    # get an element of the basis of Hm
    # get a representative of a cohomology class in M
    gen_repr = gen.representative()
    # get the apl representative for each generator of M^p
    m_gens = gen_repr.parent().gens()
    aplgens = [phi[g.degree()][str(g)] for g in m_gens]
    apl_correspondence[gen] = A.null_element()
    for exp in gen_repr.dict().keys():
        coef = gen_repr.dict()[exp]
        term = coef*prod([gen^e for gen,e in zip(aplgens, exp)])
        apl_correspondence[gen] += term
    cochain = apl_correspondence[gen].integral()
    cochain_correspondence[gen] = cochain
    vcochain = A.cochain_to_vector(cochain,dim)
    Hc_correspondence[gen] = Hc(vcochain)
    Mat[:,i] = Hc_correspondence[gen]
    i+=1
Hc_correspondence
In [ ]:
M.cohomology(2)
In [ ]:
K.cohomology(5,base_ring=QQ)
In [ ]:
gca = GradedCommutativeAlgebra(QQ, names=['x2_0','y5_0'], degrees=(2,5))
differentials = {gca.gens()[1]:gca.gens()[0]^3}
M = gca.cdg_algebra(differentials)
In [ ]:
 
In [ ]:
 
In [ ]: