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.4 is required to run this notebook:
version()
'SageMath version 9.4.rc1, Release Date: 2021-08-08'
First we set up the notebook to display math formulas using LaTeX formatting:
%display latex
We briefly demonstrate the capabilities of our implementation, and start by declaring the manifold $M=\mathbb{R}^2$:
M = Manifold(2, 'R^2', latex_name=r'\mathbb{R}^2')
X.<x,y> = M.chart()
We define the corresponding spaces of differential forms:
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:
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:
print(Omega.category())
Join of Category of graded algebras over Symbolic Ring and Category of chain complexes over Symbolic Ring
Let us define a mixed differential form:
A = M.mixed_form(name='A'); print(A)
Mixed differential form A on the 2-dimensional differentiable manifold R^2
The homogeneous components of A
can be accessed and altered using the A[i]
syntax:
A[0].set_expr(x^2)
A[1][:] = y, 2*x
A[2][0,1] = 4*x^3
A.display()
A.display_expansion()
A[0].display()
A[1].display()
A[2].display()
The category framework of Sage
captures the entire setup:
all([A[0] in Omega0,
A[0] in Omega,
A[1] in Omega1,
A[1] in Omega,
A[2] in Omega2,
A[2] in Omega])
Let us perform some computations and define another mixed form. This time, we predefine some differential forms:
f = M.scalar_field(2, name='f')
eta = M.diff_form(1, name='eta', latex_name=r'\eta')
eta[:] = x, y
xi = M.diff_form(2, name='xi', latex_name=r'\xi')
xi[0,1] = y^2
B = M.mixed_form([f,eta,0], name='B'); B.display()
B.display_expansion()
Though the homogeneous components share the same name, they are independent instances:
B[0] == f
B[0] is f
As long as B
is mutable, the homogeneous components can still be changed:
B[2] = xi
B.display()
But if we set the mixed form immutable, this is no longer possible:
B.set_immutable()
try:
B[1] = 0
except ValueError as ve:
print(ve)
the components of an immutable element cannot be changed
The multiplication is executed degree wise:
all((A * B)[k] == sum(A[j].wedge(B[k - j])
for j in range(k + 1))
for k in Omega.irange())
(A * B).display_expansion()
This particular example is also convenient to demonstrate that the multiplication given by the wedge product is in general neither commutative nor anticommutative:
(B * A).display_expansion()
Finally, let us compute the exterior derivative:
dA = A.exterior_derivative(); dA.display_expansion()
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:
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$:
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)$:
nab = E.bundle_connection('nabla^E', latex_name=r'\nabla^E')
A = function('A')
The corresponding connection form $\omega$ turns out as:
omega = M.one_form(name='omega', latex_name=r'\omega')
omega[1] = I*A(t)
omega.display()
Let us put this into the connection:
nab[0,0] = omega
For the following, nab
must be hashable. Thus we have to make it immutable first:
nab.set_immutable()
Notice that the Chern character $\mathrm{ch}(E)$ is already predefined in the system. We can get it by the following command:
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 get_form
:
ch_form = ch.get_form(nab)
ch_form.display_expansion()
We can see that the resulting 2-form coincides with the Faraday tensor divided by $2 \pi$ as expected.
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\}$:
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$:
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()
comp_to_cart = cart_to_comp.inverse()
comp_to_cart.display()
Now, we are ready to construct the tautological line bundle $\gamma_1$:
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)$:
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$:
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[e, 1, 1] = omega
nab.set_immutable()
It is time to initialize $c(\gamma_1)$. Fortunately Sage
already knows the Chern class:
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:
c_form = c.get_form(nab)
c_form.display_expansion(c_comp.frame(), chart=c_comp)
Since this particular representation is defined outside a set of measure zero, we can compute its integral over $\mathbb{CP}^1$ in real coordinates:
integrate(integrate(c_form[2][[1,2]].expr(c_cart), x, -infinity, infinity).full_simplify(), y, -infinity, infinity)
The result shows that $c_1(\gamma_1)$ generates the second integer cohomology $H^2(\mathbb{CP}^1, \mathbb{Z})$.
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
:
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:
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:
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$:
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()
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$:
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()
We can define the standard metric $g$ on $\mathbb{S}^2$ by setting it as the pullback metric $\iota^*h$:
g = M.metric()
g.set(iota.pullback(h))
g[1,1].factor(); g[2,2].factor() # simplifications
g.display()
The corresponding Levi-Civita connection is computed automatically:
nab = g.connection()
nab.set_immutable()
Since we have found the desired Levi-Civita connection, we want to compute the associated curvature forms and store them in a Python
list:
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:
for i in range(TM.rank()):
for j in range(TM.rank()):
show(cmatrix_U[i][j].display())
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:
cmatrices = {eU: cmatrix_U, eV: cmatrix_V}
e_class_form = e_class.get_form(nab, cmatrices)
e_class_form.display_expansion()
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$:
integrate(integrate(e_class_form[2][[1,2]].expr(), x, -infinity, infinity).simplify_full(), y, -infinity, infinity)
We have eventually obtained the Euler characteristic of $\mathbb{S}^2$.
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:
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)$:
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:
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()
From above, we know that $M$ admits a global frame $(\varepsilon_0, \varepsilon_1, \varepsilon_2, \varepsilon_3)$:
E = M.vector_frame('E', latex_symbol=r'\varepsilon')
E_U = E.restrict(U); E_U
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:
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]
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
:
P = U.automorphism_field()
for i in M.irange():
for j in M.irange():
P[j,i] = E_U[i][j]
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
:
for i in M.irange():
E[i].add_comp_by_continuation(stereoS.frame(), W)
And again, we declare the change of frame:
P = V.automorphism_field()
for i in M.irange():
for j in M.irange():
P[j,i] = E.restrict(V)[i][j]
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$:
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:
A = TU.characteristic_class('AHat'); A
Its holomorphic function is given by:
A.function()
We are ready to define the Berger metric, at least on the subset $U$:
a = function('a')
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)
The corresponding connection is automatically computed by Sage
:
nab = g.connection(); nab
nab.set_immutable()
Finally, we perform the computation of the $\hat{A}$-form with respect to this connection $\nabla_g$:
A_form = A.get_form(nab) # long time
A_form.display_expansion(E_U, stereoN)
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.