# Mixed Forms and Characteristic Classes¶

This Jupyter notebook illustrates SageMath's functionalities regarding mixed differential forms and characteristic classes. The involved tools have been developed through the SageManifolds project.

Author: Michael Jung

A version of SageMath at least equal to 9.0 is required to run this notebook:

In [1]:
version()

Out[1]:
'SageMath version 9.1.beta6, Release Date: 2020-03-01'

First we set up the notebook to display math formulas using LaTeX formatting:

In [2]:
%display latex


## Mixed Differential Forms¶

We briefly demonstrate the capabilities of our implementation, and start by declaring the manifold $M=\mathbb{R}^2$:

In [3]:
M = Manifold(2, 'R^2', latex_name=r'\mathbb{R}^2')
X.<x,y> = M.chart()


We define the corresponding spaces of differential forms:

In [4]:
Omega0 = M.diff_form_module(0); print(Omega0)
Omega1 = M.diff_form_module(1); print(Omega1)
Omega2 = M.diff_form_module(2); print(Omega2)

Algebra of differentiable scalar fields on the 2-dimensional differentiable manifold R^2
Free module Omega^1(R^2) of 1-forms on the 2-dimensional differentiable manifold R^2
Free module Omega^2(R^2) of 2-forms on the 2-dimensional differentiable manifold R^2


The algebra of mixed forms is returned by a simple command:

In [5]:
Omega = M.mixed_form_algebra(); print(Omega)

Graded algebra Omega^*(R^2) of mixed differential forms on the 2-dimensional differentiable manifold R^2


It belongs to the category of graded algebras over the symbolic ring:

In [6]:
print(Omega.category())

Category of graded algebras over Symbolic Ring


Before proceeding with mixed forms, let us first declare some differential forms:

In [7]:
f = M.scalar_field(name='f')
omega1 = M.diff_form(1, name='omega_1', latex_name=r'\omega_1')
omega2 = M.diff_form(2, name='omega_2', latex_name=r'\omega_2')
eta = M.diff_form(1, name='eta', latex_name=r'\eta')


In the next step, we provide some expressions in local coordinates:

In [8]:
f.set_expr(x^2)
omega1[:] = y, 2*x
omega2[0,1] = 4*x^3
eta[:] = x, y

In [9]:
f.display()

Out[9]:
In [10]:
omega1.display()

Out[10]:
In [11]:
omega2.display()

Out[11]:
In [12]:
eta.display()

Out[12]:

The category framework of Sage captures the entire setup:

In [13]:
all([f in Omega0,
f in Omega,
omega1 in Omega1,
omega1 in Omega,
omega2 in Omega2,
omega2 in Omega])

Out[13]:

Now, let us define a mixed form:

In [14]:
A = M.mixed_form(name='A'); print(A)

Mixed differential form A on the 2-dimensional differentiable manifold R^2


It shall consist of the differential forms $f, \omega_1, \omega_2$. The forms are assigned by using index operations:

In [15]:
A[:] = [f,omega1,omega2]; A.display()

Out[15]:
In [16]:
A.display_expansion()

Out[16]:

As we can see, the output is sorted by degree. Notice that the forms stored in A are given by the very same instances we declared beforehand:

In [17]:
all([A[0] is f,
A[1] is omega1,
A[2] is omega2])

Out[17]:

If that behavior is unwanted, a copy can be made which has the very same expressions in local coordinates but has stored entirely new instances:

In [18]:
Aclone = A.copy()
any(Aclone[k] is A[k] for k in Omega.irange())

Out[18]:
In [19]:
all(Aclone[k] == A[k] for k in Omega.irange())

Out[19]:

Let us perform some computations and define another mixed form:

In [20]:
B = M.mixed_form([2,eta,0], name='B'); B.display_expansion()

Out[20]:

The multiplication is executed degree wise:

In [21]:
all((A * B)[k] == sum(A[j].wedge(B[k - j])
for j in range(k + 1))
for k in Omega.irange())

Out[21]:
In [22]:
(A * B).display_expansion()

Out[22]:

This particular example is also convenient to demonstrate that the multiplication given by the wedge product is in general neither commutative nor anticommutative:

In [23]:
(B * A).display_expansion()

Out[23]:

Finally, let us compute the exterior derivative:

In [24]:
dA = A.exterior_derivative(); dA.display_expansion()

Out[24]:

## Characteristic Classes¶

### Chern Character over Minkowski Space¶

We want to exemplify the usage of characteristic classes within Sage by computing the Chern character form $\mathrm{ch}(E, \nabla^E)$ on a complex trivial line bundle $E$ over the 2-dimensional Minkowski space $M$ equipped with a bundle connection $\nabla^E$. We start with the general setup:

In [25]:
M = Manifold(2, 'M', structure='Lorentzian')
X.<t,x> = M.chart()
E = M.vector_bundle(1, 'E', field='complex'); print(E)

Differentiable complex vector bundle E -> M of rank 1 over the base space 2-dimensional Lorentzian manifold M


To trivialize the vector bundle $E$, we fix a global frame $e$:

In [26]:
e = E.local_frame('e') # trivialize


Let us declare an $\mathrm{U}(1)$-connection $\nabla^E$ on $E$ given by an electromagnetic potential $A(t)$:

In [27]:
nab = E.bundle_connection('nabla^E', latex_name=r'\nabla^E')
A = function('A')


The corresponding connection form $\omega$ turns out as:

In [28]:
omega = M.one_form(name='omega', latex_name=r'\omega')
omega[1] = I*A(t)
omega.display()

Out[28]:

Let us put this into the connection:

In [29]:
nab.set_connection_form(0, 0, omega)


Notice that the Chern character $\mathrm{ch}(E)$ is already predefined in the system. We can get it by the following command:

In [30]:
ch = E.characteristic_class('ChernChar'); print(ch)

Characteristic class ch of additive type associated to e^x on the Differentiable complex vector bundle E -> M of rank 1 over the base space 2-dimensional Lorentzian manifold M


The computation of the corresponding Chern character form $\mathrm{ch}(E,\nabla^E)$ can be invoked by the method \texttt{get_form}:

In [31]:
ch_form = ch.get_form(nab)
ch_form.display_expansion()

Out[31]:

We can see that the resulting 2-form coincides with the Faraday tensor divided by $2 \pi$ as expected.

### Example: Chern Class of the Tautological Line Bundle¶

We start our computation by initializing the complex projective space as 2-dimensional real manifold with coordinates on $U:= \mathbb{CP}^1 \setminus \left\{ [1:0] \right\}$:

In [32]:
M = Manifold(2, 'CP^1', start_index=1)
U = M.open_subset('U')
c_cart.<x,y> = U.chart() # [1:x+I*y]


For the sake of convenience, we additionally declare the complex coordinates $z$ and $\bar{z}$ on $U$:

In [33]:
c_comp.<z, zbar> = U.chart(r'z:z zbar:\bar{z}')
cart_to_comp = c_cart.transition_map(c_comp, (x+I*y, x-I*y))
cart_to_comp.display()

Out[33]:
In [34]:
comp_to_cart = cart_to_comp.inverse()
comp_to_cart.display()

Out[34]:

Now, we are ready to construct the tautological line bundle $\gamma_1$:

In [35]:
E = M.vector_bundle(1, 'gamma_1',
latex_name=r'\gamma_1',
field='complex')


Furthermore we declare a local frame $e$ on $U$ naturally given by $[z:1] \mapsto \left(\begin{smallmatrix} z \\ 1 \end{smallmatrix}\right)$:

In [36]:
e = E.local_frame('e', domain=U)


To compute the Chern class, we still need a connection. The tautological line bundle inherits a Hermitian metric from the overlying trivial bundle $\mathbb{C}^2 \times \mathbb{CP}^1$:

In [37]:
nab = E.bundle_connection('nabla', latex_name=r'\nabla')
omega = U.one_form(name='omega')
omega[c_comp.frame(), 1, c_comp] = zbar/(1+z*zbar)
nab.set_connection_form(1, 1, omega, frame=e)


It is time to initialize $c(\gamma_1)$. Fortunately Sage already knows the Chern class:

In [38]:
c = E.characteristic_class('Chern'); print(c)

Characteristic class c of multiplicative type associated to x + 1 on the Differentiable complex vector bundle gamma_1 -> CP^1 of rank 1 over the base space 2-dimensional differentiable manifold CP^1


Let the machinery do its work:

In [39]:
c_form = c.get_form(nab)
c_form.display_expansion(c_comp.frame(), chart=c_comp)

Out[39]:

Since this particular representation is defined outside a set of measure zero, we can compute its integral over $\mathbb{CP}^1$ in real coordinates:

In [40]:
integrate(integrate(c_form[2][[1,2]].expr(c_cart), x, -infinity, infinity).full_simplify(), y, -infinity, infinity)

Out[40]:

The result shows that $c_1(\gamma_1)$ generates the second integer cohomology $H^2(\mathbb{CP}^1, \mathbb{Z})$.

### Euler Class of $\mathbb{S}^2$¶

In this example, we want to compute the Euler class of the 2-sphere $\mathbb{S}^2 \subset \mathbb{R}^3$. As usual, we cover $\mathbb{S}^2$ by two parallelizable open subsets $U:=\mathbb{S}^2 \setminus \{(0,0,1)\}$ and $V:=\mathbb{S}^2 \setminus \{(0,0,-1)\}$ for which the point $(0,0,1)$ is identified with the north pole. We state stereographic coordinates in Sage:

In [41]:
M = Manifold(2, name='S^2', latex_name=r'\mathbb{S}^2',
structure='Riemannian', start_index=1)
U = M.open_subset('U') ; V = M.open_subset('V')
M.declare_union(U,V)   # M is the union of U and V
stereoN.<x,y> = U.chart()
stereoS.<xp,yp> = V.chart("xp:x' yp:y'")
N_to_S = stereoN.transition_map(stereoS,
(x/(x^2+y^2), y/(x^2+y^2)),
intersection_name='W',
restrictions1= x^2+y^2!=0,
restrictions2= xp^2+yp^2!=0)
S_to_N = N_to_S.inverse()


Next we define the tangent bundle and its local frames induced by the charts:

In [42]:
eU = stereoN.frame(); eV = stereoS.frame()
TM = M.tangent_bundle()


The Euler class is also one of the predefined classes. Thus, we can easily get it from the tangent bundle's instance:

In [43]:
e_class = TM.characteristic_class('Euler'); print(e_class)

Characteristic class e of Pfaffian type associated to x on the Tangent bundle TS^2 over the 2-dimensional Riemannian manifold S^2


To compute a form representing the Euler class, we need to state a suitable connection first. Here we want to use the Levi-Civita connection induced by the standard metric. This is simply given by the pullback of the Euclidean scalar product of the ambient space $\mathbb{R}^3$ along the canonical embedding $\iota: \mathbb{S}^2 \hookrightarrow \mathbb{R}^3$. Let us define the ambient space $\mathbb{R}^3$ and its Euclidean scalar product $h$:

In [44]:
E = Manifold(3, 'R^3', latex_name=r'\mathbb{R}^3', start_index=1)
cart.<X,Y,Z> = E.chart()
h = E.metric('h')
h[1,1], h[2,2], h[3, 3] = 1, 1, 1
h.display()

Out[44]:

On that account, we declare the embedding $\iota: \mathbb{S}^2 \hookrightarrow \mathbb{R}^3$ in stereographic coordinates when one considers its projection from the north pole $(0, 0, 1)$ to the equatorial plane $Z=0$:

In [45]:
iota = M.diff_map(E, {(stereoN, cart):
[2*x/(1+x^2+y^2), 2*y/(1+x^2+y^2),
(1-x^2-y^2)/(1+x^2+y^2)],
(stereoS, cart):
[2*xp/(1+xp^2+yp^2), 2*yp/(1+xp^2+yp^2),
(xp^2+yp^2-1)/(1+xp^2+yp^2)]},
name='iota', latex_name=r'\iota')
iota.display()

Out[45]:

We can define the standard metric $g$ on $\mathbb{S}^2$ by setting it as the pullback metric $\iota^*h$:

In [46]:
g = M.metric()
g.set(iota.pullback(h))
g[1,1].factor(); g[2,2].factor() # simplifications
g.display()

Out[46]:

The corresponding Levi-Civita connection is computed automatically:

In [47]:
nab = g.connection()


Since we have found the desired Levi-Civita connection, we want to compute the associated curvature forms and store them in a Python list:

In [48]:
cmatrix_U = [[nab.curvature_form(i,j,eU) for j in TM.irange()]
for i in TM.irange()]
cmatrix_V = [[nab.curvature_form(i,j,eV) for j in TM.irange()]
for i in TM.irange()]


Fortunately, the curvature form matrices are already skew-symmetric:

In [49]:
for i in range(TM.rank()):
for j in range(TM.rank()):
show(cmatrix_U[i][j].display())

In [50]:
for i in range(TM.rank()):
for j in range(TM.rank()):
show(cmatrix_V[i][j].display())


Hence we can put them into a dictionary and apply the algorithm:

In [51]:
cmatrices = {eU: cmatrix_U, eV: cmatrix_V}
e_class_form = e_class.get_form(nab, cmatrices)
e_class_form.display_expansion()

Out[51]:

We want to compute the Euler characteristic of $\mathbb{S}^2$ now. This can be achieved by integrating the top form over $\mathbb{S}^2$. But since $U$ and $\mathbb{S}^2$ differ only by a point and therefore a set of measure zero, it is enough to integrate over the subset $U$:

In [52]:
integrate(integrate(e_class_form[2][[1,2]].expr(), x, -infinity, infinity).simplify_full(), y, -infinity, infinity)

Out[52]:

We have eventually obtained the Euler characteristic of $\mathbb{S}^2$.

### $\hat{A}$-Class of Lorentzian Foliation of Berger Spheres¶

We consider the space of unit quaternions $\mathbb{S}^3 \subset \mathbb{R}^4 \cong \mathbb{H}$, where $\mathbb{H}$ is endowed with the canonical basis $(\mathbf{1}, \mathbf{i}, \mathbf{j}, \mathbf{k})$. It turns out that $\mathbb{S}^3$ admits a global frame $(\varepsilon_1, \varepsilon_2, \varepsilon_3)$ so that we observe $\mathbb{S}^3$ to be a parallelizable manifold.

We introduce the following smooth family of so-called Berger metrics: \begin{align*} g_t = a(t)^{2} \; \varepsilon^{1}\otimes \varepsilon^{1} +\varepsilon^{2}\otimes \varepsilon^{2} +\varepsilon^{3}\otimes \varepsilon^{3}. \end{align*}

This family can be used to define a globally hyperbolic manifold $M=\mathbb{R} \times \mathbb{S}^3$ equipped with the Lorentzian metric $g = - {\mathrm{d} t}^2 + g_t$ and hence foliated by Berger spheres.

In the following we compute the $\hat{A}$-form of the corresponding Levi-Civita connection $\nabla_g$. We start the computation by declaring the Lorentzian manifold first:

In [53]:
M = Manifold(4, 'M', structure='Lorentzian'); print(M)

4-dimensional Lorentzian manifold M


We cover $M$ by two open subsets defined as $U := \mathbb{R} \times \left( \mathbb{S}^3 \setminus \{ -\mathbf{1} \} \right)$ and $V := \mathbb{R} \times \left( \mathbb{S}^3 \setminus \{ \mathbf{1} \} \right)$:

In [54]:
U = M.open_subset('U'); V = M.open_subset('V')
M.declare_union(U,V)


We need to impose coordinates on $M$ and use stereographic projections with respect to the foliated 3-sphere:

In [55]:
stereoN.<t,x,y,z> = U.chart()
stereoS.<tp,xp,yp,zp> = V.chart("tp:t' xp:x' yp:y' zp:z'")
N_to_S = stereoN.transition_map(stereoS,
(t, x/(x^2+y^2+z^2),
y/(x^2+y^2+z^2),
z/(x^2+y^2+z^2)),
intersection_name='W',
restrictions1= x^2+y^2+z^2!=0,
restrictions2= xp^2+yp^2+zp^2!=0)
W = U.intersection(V)
S_to_N = N_to_S.inverse()
N_to_S.display()

Out[55]:

From above, we know that $M$ admits a global frame $(\varepsilon_0, \varepsilon_1, \varepsilon_2, \varepsilon_3)$:

In [56]:
E = M.vector_frame('E', latex_symbol=r'\varepsilon')
E_U = E.restrict(U); E_U

Out[56]:

The vector field $\varepsilon_0$ is simply given by $\frac{\partial}{\partial t}$. To obtain the global vector frame $(\varepsilon_1, \varepsilon_2, \varepsilon_3)$ on $\mathbb{S}^3$ in stereographic coordinates, a computation within Sage is performed in the this Jupyter-Notebook. This is done by embedding $\mathbb{S}^3$ into $\mathbb{R}^4 \cong \mathbb{H}$, endowing it with the quaternionic structure. This eventually leads to:

In [57]:
E_U[0][:] = [1,0,0,0]
E_U[1][:] = [0, (x^2-y^2-z^2+1)/2, x*y+z, x*z-y]
E_U[2][:] = [0, x*y-z, (1-x^2+y^2-z^2)/2, x+y*z]
E_U[3][:] = [0, x*z+y, y*z-x, (1-x^2-y^2+z^2)/2]

In [58]:
for i in M.irange():
show(E_U[i].display())


To ensure evaluations in this particular frame, we must communicate the change-of-frame formula to Sage:

In [59]:
P = U.automorphism_field()
for i in M.irange():
for j in M.irange():
P[j,i] = E_U[i][j]

In [60]:
U.set_change_of_frame(stereoN.frame(), E_U, P)


The subset $U$ differs from $M$ only by a one dimensional slit, and is therefore dense in $M$. As we know that $\left(\varepsilon_{0},\varepsilon_{1},\varepsilon_{2},\varepsilon_{3}\right)$ defines a \emph{global} frame, its components can be easily and uniquely extended to all of $M$. For this, we use the method add_comp_by_continuation:

In [61]:
for i in M.irange():
E[i].add_comp_by_continuation(stereoS.frame(), W)


And again, we declare the change of frame:

In [62]:
P = V.automorphism_field()
for i in M.irange():
for j in M.irange():
P[j,i] = E.restrict(V)[i][j]

In [63]:
V.set_change_of_frame(stereoS.frame(), E.restrict(V), P)


In order to reduce the computation time, we examine the $\hat{A}$-class on the open subset $U \subset M$ first. The final result can be obtained by continuation. For this purpose we define the tangent bundle over $U$:

In [64]:
TU = U.tangent_bundle(); print(TU)

Tangent bundle TU over the Open subset U of the 4-dimensional Lorentzian manifold M


Notice that the $\hat{A}$-class is already predefined:

In [65]:
A = TU.characteristic_class('AHat'); A

Out[65]:

Its holomorphic function is given by:

In [66]:
A.function()

Out[66]:

We are ready to define the Berger metric, at least on the subset $U$:

In [67]:
a = function('a')

In [68]:
g = U.metric()
g.add_comp(E_U)[0, 0] = - 1
g.add_comp(E_U)[1, 1] = a(t)^2
g.add_comp(E_U)[2, 2] = 1
g.add_comp(E_U)[3, 3] = 1
g.display(E_U)

Out[68]:

The corresponding connection is automatically computed by Sage:

In [69]:
nab = g.connection(); nab

Out[69]:

Finally, we perform the computation of the $\hat{A}$-form with respect to this connection $\nabla_g$:

In [70]:
A_form = A.get_form(nab) # long time
A_form.display_expansion(E_U, stereoN)

Out[70]:

To attain $\hat{A}(TM, \nabla_g)$ in all given coordinates, we still have to extend the result onto $M$. With respect to the global frame $(\varepsilon_0, \varepsilon_1,\varepsilon_2,\varepsilon_3)$, the form $\hat{A}(TU, \nabla_g)$ only depends on the global coordinate $t$. This makes the continuation trivial. Besides, for the most part, one is interested in characteristic forms outside a set of measure zero. Hence, we terminate our calculation at this point.

In [ ]: