ZAMO frame in Kerr spacetime

This Jupyter/SageMath notebook is relative to the lectures Geometry and physics of black holes.

The corresponding tools have been developed within the SageManifolds project.

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

In [1]:
version()
Out[1]:
'SageMath version 9.2.beta13, Release Date: 2020-09-21'

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

In [2]:
%display latex

We ask for running tensor computations in parallel on 8 threads:

In [3]:
Parallelism().set(nproc=8)

Spacetime manifold

We declare the Kerr spacetime (or more precisely the part of it covered by Boyer-Lindquist coordinates) as a 4-dimensional Lorentzian manifold $\mathcal{M}$:

In [4]:
M = Manifold(4, 'M', latex_name=r'\mathcal{M}', structure='Lorentzian')
print(M)
4-dimensional Lorentzian manifold M

We then introduce the standard Boyer-Lindquist coordinates as a chart BL (for Boyer-Lindquist) on $\mathcal{M}$, via the method chart(), the argument of which is a string (delimited by r"..." because of the backslash symbols) expressing the coordinates names, their ranges (the default is $(-\infty,+\infty)$) and their LaTeX symbols:

In [5]:
BL.<t,r,th,ph> = M.chart(r"t r th:(0,pi):\theta ph:(0,2*pi):\phi") 
print(BL); BL
Chart (M, (t, r, th, ph))
Out[5]:
In [6]:
BL[0], BL[1]
Out[6]:

Metric tensor

The 2 parameters $m$ and $a$ of the Kerr spacetime are declared as symbolic variables:

In [7]:
var('m, a', domain='real')
Out[7]:

We get the (yet undefined) spacetime metric:

In [8]:
g = M.metric()

The metric is set by its components in the coordinate frame associated with Boyer-Lindquist coordinates, which is the current manifold's default frame:

In [9]:
rho2 = r^2 + (a*cos(th))^2
Delta = r^2 -2*m*r + a^2
g[0,0] = -(1-2*m*r/rho2)
g[0,3] = -2*a*m*r*sin(th)^2/rho2
g[1,1], g[2,2] = rho2/Delta, rho2
g[3,3] = (r^2+a^2+2*m*r*(a*sin(th))^2/rho2)*sin(th)^2
g.display()
Out[9]:

A matrix view of the components with respect to the manifold's default vector frame:

In [10]:
g[:]
Out[10]:

The list of the non-vanishing components:

In [11]:
g.display_comp()
Out[11]:

ZAMO frame

The default vector frame on the spacetime manifold is the coordinate basis associated with Boyer-Lindquist coordinates:

In [12]:
M.default_frame() is BL.frame()
Out[12]:
In [13]:
BL.frame()
Out[13]:

The ZAMO 4-velocity is defined by its components w.r.t. the Boyer-Lindquist frame:

In [14]:
e0 = M.vector_field(name='e0', latex_name=r'e_0')
rho = sqrt(rho2)
e0[0] = sqrt(rho2*(r^2 + a^2) + 2*a^2*m*r*sin(th)^2)/(rho*sqrt(Delta))
e0[3] = 2*a*m*r/(rho*sqrt(Delta*(rho2*(r^2 + a^2) + 2*a^2*m*r*sin(th)^2)))
e0.display()
Out[14]:

We check that is a unit timelike vector:

In [15]:
g(e0, e0).expr()
Out[15]:

and that it is future-directed, by computing its scalar product with the global null vector $k$, which generates the ingoing principal null geodesics:

In [16]:
k = M.vector_field([(r^2 + a^2)/Delta, -1, 0, a/Delta],
                   name='k')
k.display()
Out[16]:
In [17]:
g(k, e0).expr().factor()
Out[17]:

The spacelike vectors of the ZAMO frame:

In [18]:
e1 = M.vector_field([0, sqrt(Delta)/rho, 0, 0],
                    name='e1', latex_name=r'e_1')
e1.display()
Out[18]:
In [19]:
e2 = M.vector_field([0, 0, 1/rho, 0],
                    name='e2', latex_name=r'e_2')
e2.display()
Out[19]:
In [20]:
e3 = M.vector_field(name='e3', latex_name=r'e_3')
e3[3] = rho/(sin(th)*sqrt(rho^2*(r^2 + a^2) +  2*a^2*m*r*sin(th)^2))
e3.display()
Out[20]:
In [21]:
ZF = M.vector_frame('e', [e0, e1, e2, e3])
ZF
Out[21]:
In [22]:
for v in ZF:
    show(v.display())

Check that the ZAMO frame is orthonormal:

In [23]:
matrix([[g(u, v).expr() for v in ZF] for u in ZF])  
Out[23]:

The ZAMO coframe

In [24]:
for f in ZF.coframe():
    show(f.display())

4-acceleration of the ZAMO observer

In [25]:
nabla = g.connection()
print(nabla)
Levi-Civita connection nabla_g associated with the Lorentzian metric g on the 4-dimensional Lorentzian manifold M
In [26]:
A = nabla(e0).contract(e0)  # use A to avoid any confusion with the Kerr parameter a
A.set_name('a')
print(A)
Vector field a on the 4-dimensional Lorentzian manifold M
In [27]:
A.apply_map(factor)  # factor the components for a better display
A.display()
Out[27]:

Display in the ZAMO frame:

In [28]:
A.apply_map(factor, frame=ZF, keep_other_components=True)
A.display(ZF)
Out[28]:
In [29]:
A.display_comp(ZF, only_nonzero=False)
Out[29]:
In [30]:
a1_num = A[ZF, 1].expr().numerator()
a1_num
Out[30]:

We check the agreement with Eq. (70) of O. Semerak, Gen. Relat. Grav. 25, 1041 (1993):

In [31]:
bool(a1_num == m*(rho2*(r^4 - a^4) + 2*Delta*(r*a*sin(th))^2))
Out[31]:

4-rotation of the ZAMO frame

We define the rotation operator as $$ \Omega_{\rm rot}(v) = \nabla_{e_0} v - \Omega_{\rm FW}(v)$$ where $$\Omega_{\rm FW}(v) := (a\cdot v) e_0 - (e_0\cdot v) a$$ is the Fermi-Walker operator:

In [32]:
def rotation_operator(v):
    return nabla(v).contract(e0) - g(A, v)*e0 + g(e0, v)*A

Let us evaluate $\Omega_{\rm rot}(e_1)$:

In [33]:
De1 = rotation_operator(e1)
De1.display()
Out[33]:

The output clearly requires some simplification. In particular, the component along $\partial/\partial t$ should be zero. We perform some trigonometric simplication on the components thanks to the method apply_map:

In [34]:
De1.apply_map(lambda x: x.trig_reduce())
De1.apply_map(lambda x: x.simplify_trig())
De1.apply_map(factor)
De1.display()
Out[34]:
In [35]:
De1.display(ZF)
Out[35]:
In [36]:
De1.apply_map(factor, frame=ZF, keep_other_components=True)
De1.display(ZF)
Out[36]:

Let us now evaluate $\Omega_{\rm rot}(e_2)$:

In [37]:
De2 = rotation_operator(e2)
De2.apply_map(lambda x: x.trig_reduce())
De2.apply_map(lambda x: x.simplify_trig())
De2.apply_map(factor)
De2.apply_map(lambda x: x.trig_reduce(), frame=ZF, keep_other_components=True)
De2.apply_map(lambda x: x.simplify_trig(), frame=ZF, keep_other_components=True)
De2.apply_map(factor, frame=ZF, keep_other_components=True)
De2.display(ZF)
Out[37]:

and finally $\Omega_{\rm rot}(e_3)$:

In [38]:
De3 = rotation_operator(e3)
De3.apply_map(factor, frame=ZF, keep_other_components=True)
De3.display(ZF)
Out[38]:

In the orthonormal frame $(e_1, e_2, e_3)$ of the ZAMO's rest space, the matrix of the operator $\Omega_{\rm rot}$ must be of the type $$ \left( \begin{array}{ccc} 0 & - \omega^3 & \omega^2 \ \omega^3 & 0 & -\omega^1 \

- \omega^2 & \omega^1 & 0
\end{array} \right), 
$$ where the $\omega^i$'s are the components on the 4-rotation vector: $$
\omega = \omega^i e_i

$$

From the above expressions of the $\Omega_{\rm rot}(e_i)$'s, we get immediately $\omega^3 = 0$ and we check that

In [39]:
De1[ZF, 3] == - De3[ZF, 1]
Out[39]:
In [40]:
De2[ZF, 3] == - De3[ZF, 2]
Out[40]:

Therefore, we get the 4-rotation vector $\omega$ as

In [41]:
omega = - De3[ZF, 2]*e1 + De3[ZF, 1]*e2
omega.set_name('omega', latex_name=r'\omega')
omega.apply_map(factor)
omega.apply_map(factor, frame=ZF, keep_other_components=True)
omega.display(ZF)
Out[41]:
In [42]:
omega.display()
Out[42]:

As a check, we note that the above formula agrees with that given by Eqs. (73)-(74) of O. Semerak, Gen. Relat. Grav. 25, 1041 (1993).

In [ ]: