-1

I am writing a numerical solver for the null geodesics of an arbitrary spacetime metric, but currently mostly targetting black hole spacetimes. I am attempting to numerically integrate the geodesic equations:

$$ \frac{d^2 x^\mu}{ds^2} = -\Gamma^\mu_{\alpha \beta} \frac{dx^\alpha}{ds} \frac{dx^\beta}{ds} $$

Given $c = G = 1$, $M = 15000$, and initial conditions $x^\mu = \langle 0, 6M, \frac{\pi}{4}, 0\rangle$ and $v^\mu = \left \langle \frac{r_0^2 + a^2}{\Delta}, -1, 0, \frac{a}{\Delta} \right \rangle$, this is the code of my solver:

import numpy as np
import matplotlib.pyplot as plt
import torch

def calculate_christoffel(jacob, g_inv, dims): # based on https://github.com/AndreaAntoniali/Riemann-tensor-calculator/blob/main/Riemann_Calculations.ipynb gamma = np.zeros((dims, dims, dims)) for beta in range(dims): for mu in range(dims): for nu in range(dims): for alpha in range(dims): gamma[beta,mu,nu] = 1/2 * g_inv[alpha][beta] * (jacob[alpha][mu][nu] + jacob[alpha][nu][mu] - jacob[mu][nu][alpha]) return gamma

def christoffel_at_point_4d(metric, inverse_metric, t, r, theta, phi, dims): coord = torch.tensor([t, r, theta, phi], requires_grad=True) g_inv = inverse_metric(coord) jacobian = torch.autograd.functional.jacobian(metric, coord, create_graph=True) return calculate_christoffel(jacobian, g_inv, dims)

def kerr_metric(coords, M=15000, a=0.97): t = coords[0] r = coords[1] theta = coords[2] phi = coords[3] sigma = r ** 2 + a ** 2 * torch.cos(theta) ** 2 delta = r ** 2 - 2 * M * r + a ** 2 return torch.tensor([ [-(1 - (2 * M * r) /sigma), 0., 0., -((2 * M * r * a * torch.sin(theta) ** 2) / sigma)], [0., sigma / delta, 0., 0.], [0., 0., sigma, 0.], [-((2 * M * r * a * torch.sin(theta) ** 2) / sigma), 0., 0., (r ** 2 + a ** 2 + (2 * M * r * a ** 2)/sigma * torch.sin(theta) ** 2) * torch.sin(theta) ** 2] ])

def kerr_inverse_metric(coords, M=15000, a=0.97): # Based off https://www.roma1.infn.it/teongrav/onde19_20/kerr.pdf t = coords[0] r = coords[1] theta = coords[2] phi = coords[3] sigma = r ** 2 + a ** 2 * torch.cos(theta) ** 2 delta = r ** 2 - 2 * M * r + a ** 2 return torch.tensor([ [-1 / delta * (r ** 2 + a ** 2 + (2 * M * r * a ** 2) / sigma * torch.sin(theta) ** 2), 0., 0., -(2 * M * r * a) / (sigma * delta)], [0., delta / sigma, 0., 0.], [0., 0., 1 / sigma, 0.], [-(2 * M * r * a) / (sigma * delta), 0., 0., (delta - a ** 2 * torch.sin(theta) ** 2) / (sigma * delta * torch.sin(theta) ** 2)] ])

def kerr_d_ds(X, s, metric=kerr_metric, inverse_metric=kerr_inverse_metric): """ The value of the first and second derivatives with respect to an affine parameter s """ # Create a new vector to hold the positions and velocities u = np.zeros(X.shape) # X is a vector with 4 components of position # and 4 components of velocity x = X[:4] velocities = X[4:] # Find christoffel symbols given the position and the metric # here t is coordinate time, not the affine parameter s, and # also not the proper time x0, x1, x2, x3 = x Gamma = christoffel_at_point_4d(metric, inverse_metric, x0, x1, x2, x3, 4) # Given the christoffel symbols, calculate the next position for mu in range(4): for i in range(4): for j in range(4): # Solve for x components # we sum due to the Einstein summation convention u[mu] += -Gamma[mu][i][j] * velocities[i] * velocities[j] # Solve for v components u[4:] = velocities return u

def rk4(f, u0, t0, tf, n): """ Expects a function in the form y' = f(y, t) to solve f - function, MUST return numpy array of derivatives u0 - initial values t0 - initial time tf - final time n - number of samples """ t = np.linspace(t0, tf, n+1) u = np.zeros((n+1, len(u0))) u[0] = u0 h = t[1]-t[0] for i in range(n): k1 = h * f(u[i], t[i])
k2 = h * f(u[i] + 0.5 * k1, t[i] + 0.5h) k3 = h f(u[i] + 0.5 * k2, t[i] + 0.5h) k4 = h f(u[i] + k3, t[i] + h) u[i+1] = u[i] + (k1 + 2*(k2 + k3 ) + k4) / 6 return u, t

c = 1 # as we use geometrized units r0 = 6 * 15000 # orbital radius of 6M theta0 = np.pi / 4 # if we use pi / 2 we get NaNs phi0 = 0 v_r0 = c v_theta0 = 0 v_phi0 = 0 u0 = np.array([0, r0, theta0, phi0, 0, v_r0, v_theta0, v_phi0])

tf = 1500

x_sol, t_sol = rk4(kerr_d_ds, u0, 0, tf, 50)

r = x_sol[:,1] theta = x_sol[:,2] phi = x_sol[:,3]

fig = plt.figure() ax = plt.axes(projection="3d")

x = r * np.sin(theta) * np.cos(phi) y = r * np.sin(theta) * np.sin(phi) z = r * np.cos(theta)

ax.plot3D(x, y, z) ax.set_title("Null geodesics 3D plot") plt.show()

However, when I run the solver, this is the value of $r$:

array([90000., 90000., 90000., 90000., 90000., 90000., 90000., 90000.,
       90000., 90000., 90000., 90000., 90000., 90000., 90000., 90000.,
       90000., 90000., 90000., 90000., 90000., 90000., 90000., 90000.,
       90000., 90000., 90000., 90000., 90000., 90000., 90000., 90000.,
       90000., 90000., 90000., 90000., 90000., 90000., 90000., 90000.,
       90000., 90000., 90000., 90000., 90000., 90000., 90000., 90000.,
       90000., 90000., 90000.])

I'm wondering why the radial, as well as azimuthal and polar velocities, are constant. I would not expect that a photon placed at an orbit of $6M$ from a Kerr black hole would suddenly stop moving. I've already factored out issues relating to the photon being too close to the event horizon/ergospheres or numerical precision errors, so why does the solver show this result?

JS4137
  • 181
  • We don’t debug code on this site. – Ghoster Sep 19 '23 at 18:51
  • 1
    $v^\mu = \langle 0, c, 0, 0 \rangle$ is not a null vector. – Ghoster Sep 19 '23 at 19:13
  • It's probably a bit too technical for SO, and it's unlikely that anyone without GR expertise would be able to offer much help. Have you tried the simpler problem of null geodesics in a plain Schwarzschild metric? – PM 2Ring Sep 19 '23 at 19:13
  • @PM2Ring Yes indeed I have, the same issue arises with a plain Schwarzschild metric, but I will definitely try debugging with the simpler Schwarzschild metric – JS4137 Sep 19 '23 at 19:29
  • 2
    You may enjoy Divergent reflections around the photon sphere of a black hole by Albert Sneppen. I have a simple integrator for Schwarzschild photon trajectories (in Sage/Python) here: https://physics.stackexchange.com/a/680961/123208 – PM 2Ring Sep 19 '23 at 19:35
  • $v^\mu = \langle 1, 1, 0, 0 \rangle$ Is a null vector in Minkowski spacetime, but I don’t think it’s a null vector in Kerr spacetime. – Ghoster Sep 19 '23 at 22:00
  • -1 If we did debug code, you would have to provide a debuggable program. I don’t see anywhere that you call rk4 or kerr_d_ds, so how could your program calculate anything? – Ghoster Sep 19 '23 at 22:15
  • 1
    The components on the null vector should be set such that $g_{ab}v^av^b = 0$ – S.G Sep 20 '23 at 00:47
  • @Ghoster Thank you for pointing out these things! I have been very sloppy in asking questions, and you have lead me to recognize the areas in which I am lacking in relativity! Fixed the code where I forgot to add the call to the solver – JS4137 Sep 20 '23 at 01:27

1 Answers1

1

The issue I found after research is with the way Pytorch calculates the Jacobian. To calculate the correct Jacobian, the metric must be explicitly defined, like so:

def kerr_metric(coords, M=15000, a=0.97):
    t = coords[0]
    r = coords[1]
    theta = coords[2]
    phi = coords[3]
    sigma = r ** 2 + a ** 2 * torch.cos(theta) ** 2
    delta = r ** 2 - 2 * M * r + a ** 2
    g = torch.zeros(4, 4)
    g[0][0] = -(1 - (2 * M * r) /sigma)
    g[0][3] = -(2 * M * r * a * torch.sin(theta) ** 2) / sigma
    g[1][1] = sigma / delta
    g[2][2] = sigma
    g[3][0] = -(2 * M * r * a * torch.sin(theta) ** 2) / sigma
    g[3][3] = (r ** 2 + a ** 2 + (2 * M * r * a ** 2)/sigma * torch.sin(theta) ** 2) * torch.sin(theta) ** 2
    return g

def kerr_inverse_metric(coords, M=15000, a=0.97): # Based off https://www.roma1.infn.it/teongrav/onde19_20/kerr.pdf t = coords[0] r = coords[1] theta = coords[2] phi = coords[3] sigma = r ** 2 + a ** 2 * torch.cos(theta) ** 2 delta = r ** 2 - 2 * M * r + a ** 2 g_inv = torch.zeros(4, 4) g_inv[0][0] = -1 / delta * (r ** 2 + a ** 2 + (2 * M * r * a ** 2) / sigma * torch.sin(theta) ** 2) g_inv[0][3] = -(2 * M * r * a) / (sigma * delta) g_inv[1][1] = delta / sigma g_inv[2][2] = 1 / sigma g_inv[3][0] = -(2 * M * r * a) / (sigma * delta) g_inv[3][3] = (delta - a ** 2 * torch.sin(theta) ** 2) / (sigma * delta * torch.sin(theta) ** 2) return g_inv

Reference: https://stackoverflow.com/questions/76022365/why-does-the-jacobian-of-the-metric-tensor-give-zero

JS4137
  • 181