This Jupyter notebook illustrates some basic features and functionalities regarding vector bundles within SageMath. 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:
version()
First we set up the notebook to display math formulas using LaTeX formatting:
%display latex
In this section we want to introduce a non-trivial line bundle and explain how it can be applied within Sage
making use of our implementation.
M = Manifold(1, 'RP1',
latex_name=r'\mathbb{RP}^1',
start_index=1,
structure='topological')
U = M.open_subset('U'); hu.<u> = U.chart()
V = M.open_subset('V'); hv.<v> = V.chart()
M.declare_union(U, V)
The change of coordinates is given by:
u_to_v = hu.transition_map(hv, 1/u,
intersection_name='W',
restrictions1= u!=0,
restrictions2= v!=0)
u_to_v.display()
W = U.intersection(V)
v_to_u = u_to_v.inverse()
v_to_u.display()
We define the Möbius bundle in Sage
:
E = M.vector_bundle(1, 'E'); print(E)
Let us state the two trivializations:
psiU = E.trivialization('psiU', latex_name=r'\psi_U', domain=U)
psiU
psiV = E.trivialization('psiV', latex_name=r'\psi_V', domain=V)
psiV
Next we declare the transition map between $\psi_U$ and $\psi_V$:
transf = psiU.transition_map(psiV, [[u]]); transf
Of course each trivialization induces a local frame which we can get by the following command:
eU = psiU.frame(); eU
The notation $\left(\psi_U^* e_{ 1 }\right)$ stands for the local section on $U$ given by $p \mapsto \psi^{-1}_U(p,e_1)$, where $e_1$ is the standard basis of $\mathbb{R}$. Similarly, we have for $\psi_V$:
eV = psiV.frame(); eV
The corresponding bundle automorphism $\psi_U^{-1} \circ \psi_V:\left. E \right|_{W} \to \left. E \right|_{W}$ translating $\left(\psi_V^* e_{ 1 }\right)$ into $\left(\psi_U^* e_{ 1 }\right)$ can be easily returned:
transf.automorphism()
This is an instance of FreeModuleAutomorphism
:
from sage.tensor.modules.free_module_automorphism import FreeModuleAutomorphism
isinstance(transf.automorphism(), FreeModuleAutomorphism)
We can even get its determinant which is a scalar field on the intersection $W = U \cap V$:
transf.det().display()
We can see that the determinant is negative if $u<0$. It is therefore reasonable to suspect that the vector bundle $E$ is not orientable. This is indeed true. As a consequence, $E$ is not trivial and each global section must vanish somewhere. To exemplify this, we define the corresponding section module over $\mathbb{RP}^1$:
C0 = E.section_module(); C0
We can see that Sage
rejects $C^{0}(\mathbb{RP}^1;E)$ as a free module:
from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule
isinstance(C0, FiniteRankFreeModule)
This is because there is no global frame that Sage
knows about:
E.is_manifestly_trivial()
On the contrary, the section module over $U$ must be free:
C0U = E.section_module(domain=U); C0U
And indeed, it is:
from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule
isinstance(C0U, FiniteRankFreeModule)
We start with some concrete computations and therefore define a section on $U$:
sU = E.section(name='sigma', latex_name=r'\sigma', domain=U)
sU[eU,1] = (1-u)/(1+u^2)
sU.display()
This local section lives in the free module $C^0(U;E)$:
sU in C0U
We can perform a change of frame on the subset $W$:
sU.display(eV.restrict(W), hv.restrict(W))
This expression is obviously well-defined on the whole subset $V$. Hence, we can continue this section onto $\mathbb{RP}^1$:
s = E.section(name='sigma', latex_name=r'\sigma')
s.set_restriction(sU)
s.add_comp_by_continuation(eV, W)
s.display(eV)
The corresponding continuation is indeed an element of $C^{0}(\mathbb{RP}^1;E)$:
s in C0
Let us define another global section in $E$:
t = E.section(name='tau', latex_name=r'\tau')
t[eV,1] = (3-v^2)/(1+v^4)
t.add_comp_by_continuation(eU, W)
t.display(eU)
Now, $\sigma$ and $\tau$ can be added pointwise:
r = (s + t); r.display(eU)
r.display(eV)
Since $\sigma + \tau$ is again a well-defined continuous section on $E$, it must vanish at some point. We want to check this by solving an equation:
sol = solve(r[eU,1,hu].expr() == 0, u, solution_dict=True)
sol
Let us investigate what happens at this particular point $p\in \mathbb{RP}^1$ determined by $u=-1$:
p = M.point([sol[0][u]], name='p', chart=hu); print(p)
The corresponding section $\sigma$ evaluated at $p$ is an element of the fiber $E_p$:
print(s.at(p))
Concretely we have:
s.at(p).display(basis=eU.at(p))
For $\tau$ we similarly obtain:
t.at(p).display(basis=eU.at(p))
As expected, the sum vanishes at $p$:
r.at(p).display(basis=eU.at(p))
We define $U \subset \mathbb{S}^2$ to be the complement of the meridian lying in the upper $x$-$z$-plane for $x\geq 0$. Similarly $V \subset \mathbb{S}^2$ defines the complement of the meridian going through the $x$-$y$-plane for $x \leq 0$. In the language of Sage
we write:
M = Manifold(2, name='S^2', latex_name=r'\mathbb{S}^2')
U = M.open_subset('U'); V = M.open_subset('V')
M.declare_union(U,V) # M is the union of U and V
The corresponding tangent bundle can be returned by the following command:
TM = M.tangent_bundle(); TM
However for now, we are interested in the parallelizable subset $U \subset \mathbb{S}^2$:
TU = U.tangent_bundle(); print(TU)
Trivializations entirely fall back on the class DiffChart
at this stage. To demonstrate how it is done, we introduce spherical coordinates on the subset $U$:
c_spher.<th,ph> = TU.trivialization(r'th:(0,pi):\theta ph:(0,2*pi):\phi')
To demonstrate the pullback of tensor bundles, we define the Euclidean space $\mathbb{R}^3$ and introduce a differential map $\varphi: \mathbb{S}^2 \to \mathbb{R}^3$ given by the embedding of $\mathbb{S}^2$ into $\mathbb{R}^3$:
R = Manifold(3, 'R^3', r'\mathbb{R}^3')
c_cart.<x,y,z> = R.chart() # Cartesian coord. on R^3
phi = U.diff_map(R, (sin(th)*cos(ph), sin(th)*sin(ph), cos(th)),
name='phi', latex_name=r'\varphi'); print(phi)
Let us fix the point $p$ in $U \subset \mathbb{S}^2$ determined by $(\frac{\pi}{2},\pi)$ in spherical coordinates:
p = U.point((pi/2, pi), name='p'); print(p)
We can evaluate $\varphi$ at this point $p$:
phi(p).coord(c_cart)
We get the corresponding pullback tensor bundles by stating $\varphi$ as the destination map:
phiT11U = U.tensor_bundle(1,1, dest_map=phi); phiT11U
More precisely:
print(phiT11U)
We see that sections completely fall back on the preexisting implementation of tensor fields:
phiT11U.section_module() is U.tensor_field_module((1,1), dest_map=phi)
The fiber at $p$ is given by the space of $(1,1)$-tensors of the tangent space over $\mathbb{R}^3$ at $\varphi(p)$:
phiT11U.fiber(p)
Since $\mathbb{R}^3$ is parallelizable, the pullback tensor bundle $\varphi^* T^{(1,1)}\mathbb{R}^3\to U$ must be trivial:
phiT11U.is_manifestly_trivial()
Hence it comes with a frame naturally induced by the pullback:
phiT11U.frames()
Strictly speaking, this is a frame of $\varphi^* T\mathbb{R}^3$ rather than $\varphi^* T^{(1,1)}\mathbb{R}^3$. But remember that all frames in the tensor bundle can be retrieved from frames in the tangent bundle. Thus, there is no loss of generality here. We can extract our frame by applying index operations on the returned list:
print(phiT11U.frames()[0])