# Schwarzschild spacetime¶

This notebook illustrates a few capabilities of SageMath in computations regarding Schwarzschild spacetime. The corresponding tools have been developed within the SageManifolds project.

Click here to download the notebook file (ipynb format). To run it, you must start SageMath with the Jupyter interface, via the command sage -n jupyter

NB: a version of SageMath at least equal to 8.2 is required to run this notebook:

In :
version()

Out:
'SageMath version 8.9, Release Date: 2019-09-29'

First we set up the notebook to display mathematical objects using LaTeX rendering:

In :
%display latex


## Spacetime manifold¶

We declare the Schwarzschild spacetime as a 4-dimensional Lorentzian manifold:

In :
M = Manifold(4, 'M', r'\mathcal{M}', structure='Lorentzian')
M

Out:
In :
print(M)

4-dimensional Lorentzian manifold M


The spacetime manifold $\mathcal{M}$ can be split into 4 regions, corresponding to the 4 quadrants in the Kruskal diagram.Let us denote by $\mathcal{R}_{\mathrm{I}}$ to $\mathcal{R}_{\mathrm{IV}}$ the interiors of these 4 regions. $\mathcal{R}_{\mathrm{I}}$ and  $\mathcal{R}_{\mathrm{III}}$  are asymtotically flat regions outside the event horizon;  $\mathcal{R}_{\mathrm{II}}$ is inside the future event horizon and $\mathcal{R}_{\mathrm{IV}}$ is inside the past event horizon.

In :
regI = M.open_subset('R_I', r'\mathcal{R}_{\mathrm{I}}')
regII = M.open_subset('R_II', r'\mathcal{R}_{\mathrm{II}}')
regIII = M.open_subset('R_III', r'\mathcal{R}_{\mathrm{III}}')
regIV = M.open_subset('R_IV', r'\mathcal{R}_{\mathrm{IV}}')
regI, regII, regIII, regIV

Out:

The parameter $m$ of the Schwarzschild spacetime is declared as a symbolic variable:

In :
m = var('m') ; assume(m>=0)


## Boyer-Lindquist coordinates

The standard Boyer-Lindquist coordinates (also called Schwarzschild coordinates) are defined on $\mathcal{R}_{\mathrm{I}}\cup \mathcal{R}_{\mathrm{II}}$

In :
regI_II = regI.union(regII) ; regI_II

Out:
In :
X.<t,r,th,ph> = regI_II.chart(r't r:(0,+oo) th:(0,pi):\theta ph:(0,2*pi):\phi')
print(X)

Chart (R_I_union_R_II, (t, r, th, ph))

In :
X

Out:

We naturally introduce two subcharts as the restrictions of the chart $X$ to regions $\mathcal{R}_{\mathrm{I}}$ and $\mathcal{R}_{\mathrm{II}}$ respectively. Since, in terms of the Boyer-Lindquist coordinates, $\mathcal{R}_{\mathrm{I}}$ (resp. $\mathcal{R}_{\mathrm{II}}$) is defined by $r>2m$ (resp. $r<2m$), we set

In :
X_I = X.restrict(regI, r>2*m) ; X_I

Out:
In :
X_II = X.restrict(regII, r<2*m) ; X_II

Out:

At this stage, the manifold's atlas has 3 charts:

In :
M.atlas()

Out:
In :
M.default_chart()

Out:

Three vector frames have been defined on the manifold: the three coordinate frames:

In :
M.frames()

Out:
In :
print(M.default_frame())

Coordinate frame (R_I_union_R_II, (d/dt,d/dr,d/dth,d/dph))

In :
M.default_frame().domain()

Out:

## Metric tensor¶

The metric tensor is obtained as follows:

In :
g = M.metric()
print(g)

Lorentzian metric g on the 4-dimensional Lorentzian manifold M


One has to set its components in the coordinate frame associated with Schwarzschild coordinates, which is the current manifold's default frame:

In :
g[0,0], g[1,1] = -(1-2*m/r), 1/(1-2*m/r)
g[2,2], g[3,3] = r^2, (r*sin(th))^2

In :
g.display()

Out:

As an example, let us consider a vector field defined only on $\mathcal{R}_{\mathrm{I}}$:

In :
v = regI.vector_field('v')
v = 1
v = 1 - 2*m/r
# unset components are zero
v.display()

Out:
In :
v.domain()

Out:
In :
g.domain()

Out:

Since $\mathcal{R}_{\mathrm{I}}\subset \mathcal{M}$, it is possible to apply $g$ to $v$:

In :
s = g(v,v)
print(s)

Scalar field g(v,v) on the Open subset R_I of the 4-dimensional Lorentzian manifold M

In :
s.display() # v is indeed a null vector

Out:

## Levi-Civita Connection

The Levi-Civita connection $\nabla$ associated with $g$:

In :
nab = g.connection()
print(nab)

Levi-Civita connection nabla_g associated with the Lorentzian metric g on the 4-dimensional Lorentzian manifold M


Let us verify that the covariant derivative of $g$ with respect to $\nabla$ vanishes identically:

In :
nab(g) == 0

Out:
In :
nab(g).display()

Out:

The nonzero Christoffel symbols of $g$ with respect to Schwarzschild coordinates, skipping those that can be deduced by symmetry:

In :
g.christoffel_symbols_display()

Out:

## Curvature

The Riemann curvature tensor associated with $g$:

In :
R = g.riemann()
print(R)

Tensor field Riem(g) of type (1,3) on the 4-dimensional Lorentzian manifold M


The Weyl conformal tensor associated with $g$:

In :
C = g.weyl()
print(C)

Tensor field C(g) of type (1,3) on the 4-dimensional Lorentzian manifold M

In :
C.display()

Out:

The Ricci tensor associated with $g$:

In :
Ric = g.ricci()
print(Ric)

Field of symmetric bilinear forms Ric(g) on the 4-dimensional Lorentzian manifold M


### Einstein equation

Let us check that the Schwarzschild metric is a solution of the vacuum Einstein equation:

In :
Ric == 0

Out:
In :
Ric.display() # another view of the above

Out:

Contrary to the Ricci tensor, the Riemann tensor does not vanish:

In :
R[:]

Out:
In :
R.display()

Out:

The nonzero components of the Riemann tensor, skipping those that can be deduced by antisymmetry:

In :
R.display_comp(only_nonredundant=True)

Out:
In :
Ric[:]

Out:

Since the Ricci tensor is zero, the Weyl tensor is of course equal to the Riemann tensor:

In :
C == R

Out:

### Bianchi identity

Let us check the Bianchi identity $\nabla_p R^i_{\ \, j kl} + \nabla_k R^i_{\ \, jlp} + \nabla_l R^i_{\ \, jpk} = 0$:

In :
DR = nab(R)
print(DR)

Tensor field nabla_g(Riem(g)) of type (1,4) on the 4-dimensional Lorentzian manifold M

In :
from __future__ import print_function  # because the print below is valid only in Python3
for i in M.irange():
for j in M.irange():
for k in M.irange():
for l in M.irange():
for p in M.irange():
print(DR[i,j,k,l,p] + DR[i,j,l,p,k] + DR[i,j,p,k,l], end=' ')

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Let us check that if we turn the first $+$ sign into a $-$ one, the Bianchi identity does no longer hold:

In :
for i in M.irange():
for j in M.irange():
for k in M.irange():
for l in M.irange():
for p in M.irange():
print(DR[i,j,k,l,p] - DR[i,j,l,p,k] + DR[i,j,p,k,l], end=' ')

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -12*m/(2*m*r^3 - r^4) 0 0 12*m/(2*m*r^3 - r^4) 0 0 0 0 0 0 0 0 0 0 0 0 0 -6*m/r^2 0 0 0 0 0 6*m/r^2 0 0 0 0 0 0 0 0 0 0 -6*m*sin(th)^2/r^2 0 0 0 0 0 0 0 0 6*m*sin(th)^2/r^2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -6*m/r^2 0 0 0 0 0 6*m/r^2 0 0 0 0 0 0 0 0 -6*m/r^2 0 0 6*m/r^2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -6*m*sin(th)^2/r^2 0 0 0 0 0 0 0 0 6*m*sin(th)^2/r^2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -6*m*sin(th)^2/r^2 0 0 6*m*sin(th)^2/r^2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -12*(2*m^2 - m*r)/r^5 0 0 12*(2*m^2 - m*r)/r^5 0 0 0 0 0 0 0 0 0 0 0 0 0 -6*(4*m^3 - 4*m^2*r + m*r^2)/r^4 0 0 0 0 0 6*(4*m^3 - 4*m^2*r + m*r^2)/r^4 0 0 0 0 0 0 0 0 0 0 -6*(4*m^3 - 4*m^2*r + m*r^2)*sin(th)^2/r^4 0 0 0 0 0 0 0 0 6*(4*m^3 - 4*m^2*r + m*r^2)*sin(th)^2/r^4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -6*m/r^2 0 0 6*m/r^2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6*(2*m^2 - m*r)*sin(th)^2/r 0 0 -6*(2*m^2 - m*r)*sin(th)^2/r 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -6*m*sin(th)^2/r^2 0 0 0 0 0 6*m*sin(th)^2/r^2 0 0 0 0 0 0 0 0 0 0 0 0 0 -6*(2*m^2 - m*r)*sin(th)^2/r 0 0 6*(2*m^2 - m*r)*sin(th)^2/r 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6*(2*m^2 - m*r)/r^5 0 0 0 0 0 -6*(2*m^2 - m*r)/r^5 0 0 0 0 0 0 0 0 6*(2*m^2 - m*r)/r^5 0 0 -6*(2*m^2 - m*r)/r^5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -6*m/(2*m*r^3 - r^4) 0 0 6*m/(2*m*r^3 - r^4) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6*m*sin(th)^2/r^2 0 0 -6*m*sin(th)^2/r^2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 12*m*sin(th)^2/r^2 0 0 -12*m*sin(th)^2/r^2 0 0 0 0 0 0 0 0 6*m*sin(th)^2/r^2 0 0 0 0 0 -6*m*sin(th)^2/r^2 0 0 0 0 0 0 0 0 -6*m*sin(th)^2/r^2 0 0 6*m*sin(th)^2/r^2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6*(2*m^2 - m*r)/r^5 0 0 0 0 0 0 0 0 -6*(2*m^2 - m*r)/r^5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6*(2*m^2 - m*r)/r^5 0 0 -6*(2*m^2 - m*r)/r^5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -6*m/(2*m*r^3 - r^4) 0 0 0 0 0 6*m/(2*m*r^3 - r^4) 0 0 0 0 0 0 0 0 0 0 0 0 0 -6*m/r^2 0 0 6*m/r^2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -12*m/r^2 0 0 12*m/r^2 0 0 0 0 0 0 0 0 -6*m/r^2 0 0 0 0 0 6*m/r^2 0 0 0 0 0 0 0 0 6*m/r^2 0 0 -6*m/r^2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

### Kretschmann scalar

Let us first introduce tensor $R^\flat$, of components $R_{ijkl} = g_{ip} R^p_{\ \, jkl}$:

In :
dR = R.down(g)
print(dR)

Tensor field of type (0,4) on the 4-dimensional Lorentzian manifold M


and tensor $R^\sharp$, of components $R^{ijkl} = g^{jp} g^{kq} g^{lr} R^i_{\ \, pqr}$:

In :
uR = R.up(g)
print(uR)

Tensor field of type (4,0) on the 4-dimensional Lorentzian manifold M


The Kretschmann scalar is $K := R^{ijkl} R_{ijkl}$:

In :
Kr = 0
for i in M.irange():
for j in M.irange():
for k in M.irange():
for l in M.irange():
Kr += uR[i,j,k,l]*dR[i,j,k,l]
Kr

Out:

Instead of the above loops, the Kretschmann scalar can also be computed by means of the method contract(), asking that the contraction takes place on all indices (positions 0, 1, 2, 3):

In :
Kr = uR.contract(0, 1, 2, 3, dR, 0, 1, 2, 3)
Kr.expr()

Out:

The contraction can also be performed by means of index notations:

In :
Kr = uR['^{ijkl}']*dR['_{ijkl}']
Kr.expr()

Out:

## Eddington-Finkelstein coordinates

Let us introduce new coordinates on the spacetime manifold: the ingoing Eddington-Finkelstein ones:

In :
X_EF.<v,r,th,ph> = regI_II.chart(r'v r:(0,+oo) th:(0,pi):\theta ph:(0,2*pi):\varphi')
print(X_EF); X_EF

Chart (R_I_union_R_II, (v, r, th, ph))

Out:

The change from Schwarzschild (Boyer-Lindquist) coordinates to the ingoing Eddington-Finkelstein ones:

In :
ch_BL_EF_I = X_I.transition_map(X_EF, [t+r+2*m*ln(r/(2*m)-1), r, th, ph],
restrictions2=r>2*m)

In :
print(ch_BL_EF_I) ; ch_BL_EF_I

Change of coordinates from Chart (R_I, (t, r, th, ph)) to Chart (R_I, (v, r, th, ph))

Out:
In :
ch_BL_EF_I.display()

Out:
In :
X_EF_I = X_EF.restrict(regI) ; X_EF_I

Out:
In :
ch_BL_EF_II = X_II.transition_map(X_EF, [t+r+2*m*ln(1-r/(2*m)), r, th, ph],
restrictions2=r<2*m)

In :
print(ch_BL_EF_II) ; ch_BL_EF_II

Change of coordinates from Chart (R_II, (t, r, th, ph)) to Chart (R_II, (v, r, th, ph))

Out:
In :
ch_BL_EF_II.display()

Out:
In :
X_EF_II = X_EF.restrict(regII) ; X_EF_II

Out:

The manifold's atlas has now 6 charts:

In :
M.atlas()

Out:

The default chart is 'BL':

In :
M.default_chart()

Out:

The change from Eddington-Finkelstein coordinates to the Schwarzschild (Boyer-Lindquist) ones, computed as the inverse of ch_BL_EF:

In :
ch_EF_BL_I = ch_BL_EF_I.inverse()
print(ch_EF_BL_I)

Change of coordinates from Chart (R_I, (v, r, th, ph)) to Chart (R_I, (t, r, th, ph))

In :
ch_EF_BL_I.display()

Out:
In :
ch_EF_BL_II = ch_BL_EF_II.inverse()
print(ch_EF_BL_II)

Change of coordinates from Chart (R_II, (v, r, th, ph)) to Chart (R_II, (t, r, th, ph))

In :
ch_EF_BL_II.display()

Out:

At this stage, 6 vector frames have been defined on the manifold: the 6 coordinate frames associated with the various charts:

In :
M.frames()

Out:

The default frame is:

In :
M.default_frame()

Out:

The coframes are the duals of the defined vector frames:

In :
M.coframes()

Out:

If not specified, tensor components are assumed to refer to the manifold's default frame. For instance, for the metric tensor:

In :
g.display()

Out:
In :
g[:]

Out:

The tensor components in the frame associated with Eddington-Finkelstein coordinates in Region I are obtained by providing the frame to the function display():

In :
g.display(X_EF_I.frame())

Out:

They are also returned by the method comp(), with the frame as argument:

In :
g.comp(X_EF_I.frame())[:]

Out:

or, as a schortcut,

In :
g[X_EF_I.frame(),:]

Out:

Similarly, the metric components in the frame associated with Eddington-Finkelstein coordinates in Region II are obtained by

In :
g.display(X_EF_II.frame())

Out:

Note that their form is identical to that in Region I.

### Plot of the Boyer-Lindquist coordinates in terms of the Eddington-Finkelstein ones

Let us perform the plot in Region I:

In :
X_I.plot(X_EF_I, ranges={t:(0,8), r:(2.1,10)}, fixed_coords={th:pi/2,ph:0},
ambient_coords=(r,v), style={t:'--', r:'-'}, parameters={m:1})

Out: ## Tetrad of the static observer

Let us introduce the orthonormal tetrad $(e_\alpha)$ associated with the static observers in Schwarzschild spacetime, i.e. the observers whose worldlines are parallel to the timelike Killing vector in the Region I.

The orthonormal tetrad is defined via a tangent-space automorphism that relates it to the Boyer-Lindquist coordinate frame in Region I:

In :
ch_to_stat = regI.automorphism_field()
ch_to_stat[0,0], ch_to_stat[1,1] = 1/sqrt(1-2*m/r), sqrt(1-2*m/r)
ch_to_stat[2,2], ch_to_stat[3,3] = 1/r, 1/(r*sin(th))
ch_to_stat[:]

Out:
In :
e = X_I.frame().new_frame(ch_to_stat, 'e')
print(e)

Vector frame (R_I, (e_0,e_1,e_2,e_3))


At this stage, 7 vector frames have been defined on the manifold $\mathcal{M}$:

In :
M.frames()

Out:

The first vector of the tetrad is the static observer 4-velocity:

In :
print(e)

Vector field e_0 on the Open subset R_I of the 4-dimensional Lorentzian manifold M

In :
e.display()

Out:

As any 4-velocity, it is a unit timelike vector:

In :
g(e,e).expr()

Out:

Equivalently, we may use the method dot to get the scalar product (since $g$ is the default metric on $\mathcal{M}$):

In :
e.dot(e).expr()

Out:

Let us check that the tetrad $(e_\alpha)$ is orthonormal:

In :
for i in M.irange():
for j in M.irange():
print(e[i].dot(e[j]).expr(), end=' ')
print("")

-1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1


Another view of the above result:

In :
g[e,:]

Out:

or, equivalently,

In :
g.display(e)

Out:

The expression of the 4-velocity $e_0$ and the vector $e_1$ in terms of the frame associated with Eddington-Finkelstein coordinates:

In :
e.display(X_EF_I.frame())

Out:
In :
e.display(X_EF_I.frame())

Out:

Contrary to vectors of a coordinate frame, the vectors of the tetrad $e$ do not commute: their structure coefficients are not identically zero:

In :
e.structure_coeff()[:]

Out:

Equivalently, the Lie derivative of one vector along another one is not necessarily zero:

In :
e.lie_der(e).display(e)

Out:

The curvature 2-form $\Omega^0_{\ \, 1}$ associated with the tetrad $(e_\alpha)$:

In :
g.connection().curvature_form(0,1,e).display(X_I.frame())

Out:

## Kruskal-Szekeres coordinates

Let us now introduce the Kruskal-Szekeres coordinates $(U,V,\theta,\varphi)$ on the spacetime manifold, via the standard transformation expressing them in terms of the Boyer-Lindquist coordinates $(t,r,\theta,\varphi)$:

In :
M0 = regI.union(regII).union(regIII).union(regIV) ; M0

Out:
In :
X_KS.<U,V,th,ph> = M0.chart(r'U V th:(0,pi):\theta ph:(0,2*pi):\varphi')
X_KS

Out:
In :
X_KS_I = X_KS.restrict(regI, [U>0, V<U, V>-U]) ; X_KS_I

Out:
In :
ch_BL_KS_I = X_I.transition_map(X_KS_I, [sqrt(r/(2*m)-1)*exp(r/(4*m))*cosh(t/(4*m)),
sqrt(r/(2*m)-1)*exp(r/(4*m))*sinh(t/(4*m)),
th, ph])
print(ch_BL_KS_I)
ch_BL_KS_I.display()

Change of coordinates from Chart (R_I, (t, r, th, ph)) to Chart (R_I, (U, V, th, ph))

Out:
In :
X_KS_II = X_KS.restrict(regII, [V>0, V>U, V>-U]) ; X_KS_II

Out:
In :
ch_BL_KS_II = X_II.transition_map(X_KS_II, [sqrt(1-r/(2*m))*exp(r/(4*m))*sinh(t/(4*m)),
sqrt(1-r/(2*m))*exp(r/(4*m))*cosh(t/(4*m)),
th, ph])
print(ch_BL_KS_II)
ch_BL_KS_II.display()

Change of coordinates from Chart (R_II, (t, r, th, ph)) to Chart (R_II, (U, V, th, ph))

Out:

### Plot of the Boyer-Lindquist coordinates in terms of the Kruskal ones

We draw the Boyer-Lindquist chart in Region I (red) and Region II (green), with lines of constant $r$ being dashed:

In :
graphI = X_I.plot(X_KS, ranges={t:(-12,12), r:(2.001,5)}, number_values={t:17, r:9},
fixed_coords={th:pi/2,ph:0}, ambient_coords=(U,V),
style={t:'--', r:'-'}, parameters={m:1})
graphII = X_II.plot(X_KS, ranges={t:(-12,12), r:(0.001,1.999)}, number_values={t:17, r:9},
fixed_coords={th:pi/2,ph:0}, ambient_coords=(U,V),
style={t:'--', r:'-'}, color='green', parameters={m:1})
show(graphI+graphII, xmin=-3, xmax=3, ymin=-3, ymax=3, axes_labels=['$U$', '$V$']) We may add to the graph the singularity $r=0$ as a Boyer-Lindquist chart plot with $(r,\theta,\phi)$ fixed at $(0, \pi/2, 0)$.  Similarly, we add the event horizon $r=2m$ as a Boyer-Lindquist chart plot with $(r,\theta,\phi)$ fixed at $(2.00001m, \pi/2, 0)$:

In :
graph_r0 = X_II.plot(X_KS, fixed_coords={r:0, th:pi/2, ph:0}, ambient_coords=(U,V),
color='yellow', thickness=3, parameters={m:1})
graph_r2 = X_I.plot(X_KS, ranges={t:(-40,40)}, fixed_coords={r:2.00001, th:pi/2, ph:0},
ambient_coords=(U,V), color='black', thickness=2, parameters={m:1})
show(graphI+graphII+graph_r0+graph_r2, xmin=-3, xmax=3, ymin=-3, ymax=3,
axes_labels=['$U$', '$V$']) ### Plot of the Eddington-Finkelstein coordinates in terms of the Kruskal ones

We first get the change of coordinates $(v,r,\theta,\phi) \mapsto (U,V,\theta,\phi)$ by composing the change $(v,r,\theta,\phi) \mapsto (t,r,\theta,\phi)$ with $(t,r,\theta,\phi) \mapsto (U,V,\theta,\phi)$:

In :
ch_EF_KS_I = ch_BL_KS_I * ch_EF_BL_I
ch_EF_KS_I

Out:
In :
ch_EF_KS_I.display()

Out:
In :
ch_EF_KS_II = ch_BL_KS_II * ch_EF_BL_II
ch_EF_KS_II

Out:
In :
graphI_EF = X_EF_I.plot(X_KS, ranges={v:(-12,12), r:(2.001,5)}, number_values={v:17, r:9},
fixed_coords={th:pi/2,ph:0}, ambient_coords=(U,V),
style={v:'--', r:'-'}, parameters={m:1})
graphII_EF = X_EF_II.plot(X_KS, ranges={v:(-12,12), r:(0.001,1.999)},
number_values={v:17, r:9},
fixed_coords={th:pi/2,ph:0}, ambient_coords=(U,V),
style={v:'--', r:'-'}, color='green', parameters={m:1})
show(graphI_EF+graphII_EF+graph_r0+graph_r2, xmin=-3, xmax=3, ymin=-3, ymax=3,
axes_labels=['$U$', '$V$']) There are now 9 charts defined on the spacetime manifold:

In :
M.atlas()

Out:
In :
len(M.atlas())

Out:

There are 8 explicit coordinate changes (the coordinate change KS $\rightarrow$ BL is not known in explicit form):

In :
M.coord_changes()

Out:
In :
len(M.coord_changes())

Out:

There are 10 vector frames (among which 9 coordinate frames):

In :
M.frames()

Out:
In :
len(M.frames())

Out:

There are 14 fields of tangent space automorphisms expressing the changes of coordinate bases and tetrad:

In :
len(M.changes_of_frame())

Out:

Thanks to these changes of frames, the components of the metric tensor with respect to the Kruskal-Szekeres can be computed by the method display() and are found to be:

In :
g.display(X_KS_I.frame())

Out:
In :
g[X_KS_I.frame(),:]

Out:
In :
g.display(X_KS_II.frame())

Out:

The first vector of the orthonormal tetrad $e$ expressed on the Kruskal-Szekeres frame:

In :
e.display(X_KS_I.frame())

Out:

The Riemann curvature tensor in terms of the Kruskal-Szekeres coordinates:

In :
g.riemann().display(X_KS_I.frame())

Out:
In :
g.riemann().display_comp(X_KS_I.frame(), only_nonredundant=True)

Out:

The curvature 2-form $\Omega^0_{\ \, 1}$ associated to the Kruskal-Szekeres coordinate frame:

In :
om = g.connection().curvature_form(0,1, X_KS_I.frame())
print(om)

2-form curvature (0,1) of connection nabla_g w.r.t. Coordinate frame (R_I, (d/dU,d/dV,d/dth,d/dph)) on the Open subset R_I of the 4-dimensional Lorentzian manifold M

In :
om.display(X_KS_I.frame())

Out:

## Isotropic coordinates

Let us now introduce isotropic coordinates $(t,\bar{r},\theta,\varphi)$ on the spacetime manifold:

In :
regI_III = regI.union(regIII) ; regI_III

Out:
In :
X_iso.<t,ri,th,ph> = regI_III.chart(r't ri:(0,+oo):\bar{r} th:(0,pi):\theta ph:(0,2*pi):\varphi')
print(X_iso) ; X_iso

Chart (R_I_union_R_III, (t, ri, th, ph))

Out:
In :
X_iso_I = X_iso.restrict(regI, ri>m/2) ; X_iso_I

Out:

The transformation from the isotropic coordinates to the Boyer-Lindquist ones:

In :
assume(2*ri>m) # we consider only region I
ch_iso_BL_I = X_iso_I.transition_map(X_I, [t, ri*(1+m/(2*ri))^2, th, ph])
print(ch_iso_BL_I)
ch_iso_BL_I.display()

Change of coordinates from Chart (R_I, (t, ri, th, ph)) to Chart (R_I, (t, r, th, ph))

Out:
In :
assume(r>2*m) # we consider only region I
ch_iso_BL_I.set_inverse(t, (r-m+sqrt(r*(r-2*m)))/2, th, ph, verbose=True)

Check of the inverse coordinate transformation:
t == t  *passed*
ri == ri  *passed*
th == th  *passed*
ph == ph  *passed*
t == t  *passed*
r == r  *passed*
th == th  *passed*
ph == ph  *passed*

In :
ch_iso_BL_I.inverse().display()

Out:

At this stage, 11 charts have been defined on the manifold $\mathcal{M}$:

In :
M.atlas()

Out:
In :
len(M.atlas())

Out:

12 vector frames have been defined on $\mathcal{M}$: 11 coordinate bases and the tetrad $(e_\alpha)$:

In :
M.frames()

Out:
In :
len(M.frames())

Out:

The components of the metric tensor in terms of the isotropic coordinates are given by

In :
g.display(X_iso_I.frame(), X_iso_I)

Out:

The $g_{00}$ component can be factorized:

In :
g[X_iso_I.frame(), 0,0, X_iso_I]

Out:
In :
g[X_iso_I.frame(), 0,0, X_iso_I].factor()

Out:

Let us also factor the other components:

In :
for i in range(1,4):
g[X_iso_I.frame(), i,i, X_iso_I].factor()


The output of the display() command looks nicer:

In :
g.display(X_iso_I.frame(), X_iso_I)

Out:

Expression of the tetrad associated with the static observer in terms of the isotropic coordinate basis:

In :
e.display(X_iso_I.frame(), X_iso_I)

Out:
In :
e.display(X_iso_I.frame(), X_iso_I)

Out:
In :
e.display(X_iso_I.frame(), X_iso_I)

Out:
In :
e.display(X_iso_I.frame(), X_iso_I)

Out:
In [ ]: