#!/usr/bin/env python
# coding: utf-8
#
Chaotic photon orbits and shadows of a non-Kerr
object described by the Hartle-Thorne spacetime
# These notebooks accompany the paper
# "Chaotic photon orbits and shadows of a non-Kerr object described by the Hartle-Thorne spacetime" ([arXiv:2111.09367 [gr-qc]](https://arxiv.org/abs/2111.09367)) and give a demonstration of the calculations present in the paper.
# The code is in some extent based on programs available as examples in the [SageManifolds project's](https://sagemanifolds.obspm.fr/examples.html) website.
# Sticky Light Rays
# We follow a light ray that is launched at $r_0=40M$ and with impact parameters $b=4.304332$, $\alpha=0.005402$.
# In[1]:
get_ipython().run_line_magic('display', 'latex')
version()
# In[2]:
import numpy as np
import pandas as pd
from scipy.interpolate import interp1d
import multiprocessing as mp
# In[3]:
M = Manifold(4, 'M', latex_name=r'\mathcal{M}', structure='Lorentzian')
X.=M.chart(r"t r:(2.001,+inf) th:(0,pi):\theta ph:(0,2*pi):periodic\phi")
X.coord_range()
# In[4]:
var('m,b,a,Ω,q', domain='real')
g = M.metric()
m=1
# In[5]:
g[0,0] =((2*m)/r - 1) + (1/(16*m^2*r^5))*(a^2)*(((64*m^6)*r)*(sin(th)^2) - (32*m^6)*r -(1 - 3*(cos(th)^2))*(((16*m^5)*(2*m - r))*(m + r) - ((5*r^3)*q)*(((2*m)*(m - r))*(2*m^2 - 3*r^2 + (6*m)*r) + ((3*r^2)*(r - 2*m)^2)*log(1 - (2*m)/r))))
g[0,3] = -2*a*(m^2)*((sin(th))^2)/r
##
g[1,1]= 1/(1 - (2*m)/r) - (1/(32*m^2*r^3*(r - 2*m)^2))*(a^2)*((2*m)*(80*m^6 + (8*m^4)*r^2 - (24*m^5)*r + ((10*m^3)*r^3)*q + ((20*m^2)*r^4)*q - ((45*m)*r^5)*q + (15*r^6)*q) + (((15*r^5)*(r - 2*m)^2)*q)*log(1 - (2*m)/r) + (3*cos(2*th))*((2*m)*(80*m^6 + (8*m^4)*r^2 - (56*m^5)*r + ((10*m^3)*r^3)*q + ((20*m^2)*r^4)*q - ((45*m)*r^5)*q + (15*r^6)*q) + (((15*r^5)*(r - 2*m)^2)*q)*log(1 - (2*m)/r)))
g[2,2] =r^2 + (1/8)*((a^2*r^2)*(3*(cos(th)^2) - 1))*(-(((8*m^3)*(2*m + r))/r^4) - (5*q)*(-((2*m)/r) + (3*r)/m + ((3*r^2)/(2*m^2) - 3)*log(1 - (2*m)/r) + 3))
g[3,3] = (r^2)*(sin(th)^2) + (1/8)*(((a^2*r^2)*(3*(cos(th)^2) - 1))*(-(((8*m^3)*(2*m+ r))/r^4)-(5*q)*(-((2*m)/r) + (3*r)/m + ((3*r^2)/(2*m^2) - 3)*log(1 - (2*m)/r) + 3)))*(sin(th)^2)
g.display()
# We create $(m,r,\theta,a)$ functions for each of the metric components, for easier manipulation.
# In[6]:
def g00(m,r,th,a):return g[0,0](m,r,th,a)
def g03(m,r,th,a):return g[0,3](m,r,th,a)
def g11(m,r,th,a):return g[1,1](m,r,th,a)
def g22(m,r,th,a):return g[2,2](m,r,th,a)
def g33(m,r,th,a):return g[3,3](m,r,th,a)
def D(m,r,th,a): return (g03(m,r,th,a))^2-g00(m,r,th,a)*g33(m,r,th,a)
# In[7]:
E. = EuclideanSpace()
phi = M.diff_map(E, [r*sin(th)*cos(ph), r*sin(th)*sin(ph), r*cos(th)])
phi.display()
# We use $\mathcal{H}= \frac{1}{2}\left(g_{rr}\dot{r}^2 +g_{\theta\theta}\dot{\theta}^2 - \frac{L^2 g_{tt} + 2EL g_{t\phi} + E^2 g_{\phi\phi}}{\mathcal{D}}\right)=0$ , to define an initial velocity vector. We express the 4-velocity components in terms of the impact parameters $b$ and $a$, for which we use the variables $b$ and $al$ respectively.
# In[8]:
th0=pi/2
s = var('s') # affine parameter
# In[9]:
def initial_sticky(r0, b,al, ph0=0, E=1, inward=False):
t0,th0=0,pi/2
L = -b*E
vth0 = al/(40*r0)
vt0=(E*g33(m,r0,th0,a)+L*g03(m,r0,th0,a))/(D(m,r0,th0,a))
vr0=sqrt((((1/D(m,r0,th0,a))*((L^2)*g00(m,r0,th0,a)+2*E*L*g03(m,r0,th0,a)+(E^2)*g33(m,r0,th0,a)))
-((vth0)^2)*g22(m,r0,th0,a))/g11(m,r0,th0,a))
if inward:
vr0 = - vr0
vph0 = -(1/D(m,r0,th0,a))*(E*g03(m,r0,th0,a)+L*(g00(m,r0,th0,a)))
p0 = M((t0, r0, th0, ph0), name='p_0')
return M.tangent_space(p0)((vt0, vr0, vth0, vph0), name='v_0')
# In[10]:
v0 = initial_sticky(r0=40,b=-4.304332,al=0.005402,ph0=0, inward=True)
c3 = M.integrated_geodesic(g, (s, 0, 86000), v0)
sol = c3.solve(0.1,method='dopri5',parameters_values={a:0.327352,q:1},
solution_key='sol',verbose=True)
interp = c3.interpolate(solution_key='sol',
interpolation_key='interp 3', verbose=True)
# The equatorial plane ($\theta=\frac{\pi}{2}$) is chosen as the section where the orbits with $u^\theta > 0 $ are recorded. The solution of the integrated curve is interpolated by the implemented **integrated_geodesic** function, but in order to also interpolate the velocity components, we use **SciPy's interp1d** function. Since the code will be run in parallel, we also keep the **coordinate time $t$** in the result lists, in order to later be able to sort them chronologically to catch the evolution of the photon's orbit.
# In[11]:
loop=[]
for i in range(2,860000):
loop.append(i)
def sticky(i):
poin0=[]
if cos(sol[i][3])*cos(sol[i+1][3]) < 0 :
v0=c3.tangent_vector_eval_at((((i+1)+i)/2)*0.1, verbose=False)
if v0[2]>0:
X1=[]
Y1=[]
X2=[]
Y2=[]
for j in range((i-5),(i+5)):
##r,th τιμές
X1.append(sol[j][3])
Y1.append(sol[j][2])
##vr,vth τιμές
v1=c3.tangent_vector_eval_at(j*0.1, verbose=False)
Y2.append(v1[1])
interpolate_x1 = numerical_approx(pi/2)
y_interp1 = interp1d(X1, Y1,kind='cubic')
y_interp2=interp1d(X1,Y2,kind='cubic')
l1=y_interp1(interpolate_x1)
l2=y_interp2(interpolate_x1)
poin0.append([sol[i][1],numerical_approx(l1),numerical_approx(l2)])
return poin0
# In[12]:
pool = mp.Pool(8)
poinc_single = pool.map(sticky,[i for i in loop])
# In[13]:
pool.close()
# We sort the computed piercings of the Poincare section in terms of the coordinate time $t$, so we'll be able to compute the average rotation angle $\theta_{Navg}$.
We pick $\{r_0=2.24,u^r_0=0\}$ as our invariant point.
# In[14]:
list2 = filter(None, poinc_single)
list3 = list(list2)
from operator import itemgetter
list3=(sorted(list3, key=itemgetter(0)))
gg=[]
for i in range(len(list3)):
gg.append([list3[i][0][1],list3[i][0][2]])
# In[15]:
graphc=scatter_plot(gg,aspect_ratio='automatic',figsize=[50,50],alpha=1,markersize=100,zorder=1,edgecolor='black',facecolor='black',frame=True)
show(graphc,xmin=2.2,xmax=2.33,ymin=-0.03,ymax=0.03,fontsize=100,figsize=50,frame=True,axes_labels=[r'$r$', r'$u^r$'])
# We plot the **$z$-component** of the position of the photon as a time series.
# In[24]:
zt=[]
for i in range (0,len(sol)):
zt.append([sol[i][1],sol[i][2]*cos(sol[i][3])])
graphzt=list_plot(zt,plotjoined=True,thickness=0.5,frame=True,zorder=2, axes=False,color='red')
show(graphzt,figsize=10,xmin=0,xmax=len(loop)/2,ymin=-0.35,ymax=0.35,fontsize=10,axes_labels=[r'$t$', r'$ z$'])
# We perform a fast-Fourier Transform on the $z$-component time series using **scipy.fft**, in order to get the Power Spectra.
# In[17]:
size=len(zt)-1
fft_data=FFT(size)
# In[18]:
for j in range(size):
fft_data[j]=zt[j][1]
# In[19]:
fft_data.forward_transform()
# In[20]:
deltaX=2*pi/size
fft_freq=[[0,0] for k in range((size)/2)]
for j in range((size)/2):
fft_freq[j][0]=j*deltaX
fft_freq[j][1]=deltaX*abs(vector(fft_data[j]))^2
# In[21]:
graphps=list_plot(fft_freq,plotjoined=True,scale='loglog',thickness=0.1,color='red',frame=True,)
# In[23]:
show(graphps,figsize=50,fontsize=100,axes_labels=[r'$\omega$', r'$P_z ( \omega)$'])
# In[25]:
rn=[]
n=0
ang1=0
rn0=0
for i in range (len(gg)-1):
c=vector([2.24,0])
a = vector([gg[i][0],gg[i][1]])
b = vector([gg[i+1][0],gg[i+1][1]])
c1=a-c
c2=b-c
dotproduct = c1.dot_product(c2)
myfraction = dotproduct/(norm(c1) * norm(c2))
ang=arccos(myfraction).n()
ang1+=ang
if i % 15 == 0:
rn0=ang1/15
rn.append([list3[i][0][0],rn0])
ang1=0
graphrot=list_plot(rn,plotjoined=True,thickness=1,zorder=1,frame=True,color='blue',base=10)
show(graphrot,ymin=0,ymax=2.5,figsize=50,fontsize=100,axes_labels=[r'$t$', r'$\theta_{Navg}$'])
# In[30]:
# This cell is ran occasionally, in order to better allign the average rotation number curve with the z-component's
# time-series.
rn_trans=[]
for i in range(len(rn)):
rn_trans.append([rn[i][0],rn[i][1]-2])
graphrot=list_plot(rn_trans,plotjoined=True,thickness=1,zorder=3,frame=True,color='blue',base=10)
# In[31]:
graph_all=graphrot+graphzt
show(graph_all,ymin=-1)
# In[ ]: