# Calculate general relativity-related tensors/arrays using metric tensor as input, part 2

I’m new to coding, and for my first project I’m working on a calculator that takes coordinate labels and metric components as input and outputs non-zero components of a number of other tensors (technically Christoffel symbols aren’t components of a tensor, they’re just elements of an array disguised as a tensor).

I posted my first draft on code review—Here’s the link. I received some great feedback, but I understand that the feedback given was only scratching the surface of what could be improved. I would love to receive some more feedback on my 2nd draft, so here it is (execute in Jupyter to see all the pretty LaTeX, see (EDIT) below the code block for example inputs):

from sympy import MutableDenseNDimArray, Symbol, eye, Matrix, reshape, S, diff
from sympy import det, tensorcontraction, latex, Eq, sympify, simplify
from dataclasses import dataclass, field
from itertools import product
from IPython.display import display as disp
from IPython.display import Math

@dataclass
class Tensor:
name: str
symbol: str
key: str
use: MutableDenseNDimArray = field(default_factory=MutableDenseNDimArray)

def rank(self):
return self.key.count('*')

def initialize(self, t=0):
for i in range(self.rank()):
t = [t,] * n
self.use = MutableDenseNDimArray(t)

def print_tensor(self):
for i in product(range(n), repeat=self.rank()):
if self.use[i] != 0:
index_key = self.key
umth_star = 0
for k in index_key:
if k == '*':
index_key = index_key.replace('*',
coords[i[umth_star]], 1)
umth_star += 1
disp(Math(latex(Eq(Symbol(self.symbol + index_key),
self.use[i]))))
if any(self.use.reshape(len(self.use)).tolist()):
print('\n\n')

def get_dimension():
global n
n = input('Enter the number of dimensions:  ')

def check_dimension():
global n
if n.isnumeric():
n = int(n)
else:
print('Number of dimensions needs to be an integer!')
get_dimension()
check_dimension()

def get_coordinates():
global coordinates, coords
coordinates = []
for i in range(n):
coordinates.append(input('Enter coordinate label %d:  ' % i))
coords = coordinates[:]
for j in range(len(coords)):
if any(symbol in coords[j] for symbol in GREEK_SYMBOLS):
coords[j] = '\\' + coords[j] + ' '

def check_coordinates():
for i in range(len(coordinates)):
if any(char.isdigit() for char in coordinates[i]):
print("You shouldn't have numbers in coordinate labels!")
get_coordinates()
check_coordinates()

global diagonal
diagonal = input('Is metric diagonal?  ').lower()

def check_diagonal():
if diagonal not in OK_RESPONSES:
print('It was a yes or no question...')
check_diagonal()

def get_metric():
global g_m
g_m = eye(n).tolist()
for i in range(n):
for j in range(n):
g_m[i][j] = '0'
if diagonal[0] == 'y':
for i in range(n):
g_m[i][i] = input('What is g_[%s%s]?  '
% (coordinates[i], coordinates[i]))
else:
for i in range(n):
for j in range(i, n):
g_m[i][j] = input('What is g_[%s%s]?  '
% (coordinates[i], coordinates[j]))

def format_metric():
global g_m
for i in range(n):
for j in range(i, n):
g_m[i][j] = g_m[i][j].replace('^', '**')
for k in range(len(g_m[i][j])-1):
if (
g_m[i][j][k].isnumeric() and (g_m[i][j][k+1].isalpha() or
g_m[i][j][k+1] == '(')
) or (g_m[i][j][k] == ')' and g_m[i][j][k+1].isalpha()):
g_m[i][j] = g_m[i][j][:k+1] + '*' + g_m[i][j][k+1:]
g_m[j][i] = g_m[i][j]
g_m = Matrix(g_m)
g.use = MutableDenseNDimArray(g_m)

def check_metric():
if g_m.det() == 0:
print('\nMetric is singular, try again!\n')
get_metric()
format_metric()
check_metric()

def calculate_g_d():
g_d.initialize()
for i in product(range(n), repeat=3):
g_d.use[i] = diff(g.use[i[:2]], coordinates[i[2]])

def calculate_Gamma():
Gamma.initialize()
for i in product(range(n), repeat=3):
for j in range(n):
Gamma.use[i] += S(1)/2 * g_inv.use[i[0], j] * (
g_d.use[i[2], j, i[1]]
+ g_d.use[j, i[1], i[2]]
- g_d.use[i[1], i[2], j]
)

def calculate_Gamma_d():
Gamma_d.initialize()
for i in product(range(n), repeat=4):
Gamma_d.use[i] = simplify(diff(Gamma.use[i[:3]], coordinates[i[3]]))

def calculate_Rie():
Rie.initialize()
for i in product(range(n), repeat=4):
Rie.use[i] = Gamma_d.use[i[0], i[1], i[3], i[2]] - Gamma_d.use[i]
for j in range(n):
Rie.use[i] += (
Gamma.use[j, i[1], i[3]] * Gamma.use[i[0], j, i[2]]
- Gamma.use[j, i[1], i[2]] * Gamma.use[i[0], j, i[3]]
)
Rie.use[i] = simplify(Rie.use[i])

def calculate_R():
global R
R = 0
for i in product(range(n), repeat=2):
R += g_inv.use[i] * Ric.use[i]
R = simplify(R)

def calculate_G():
G.initialize()
for i in product(range(n), repeat=2):
G.use[i] = simplify(Ric.use[i] - S(1)/2 * R * g.use[i])

def calculate_G_alt():
G_alt.initialize()
for i in product(range(n), repeat=2):
for j in range(n):
G_alt.use[i] += g_inv.use[i[0], j] * G.use[j, i[1]]
G_alt.use[i] = simplify(G_alt.use[i])

def compile_metric():
get_dimension()
check_dimension()
get_coordinates()
check_coordinates()
check_diagonal()
get_metric()
format_metric()
check_metric()
g_inv.use = MutableDenseNDimArray(g_m.inv())

def calculate_GR_tensors():
calculate_g_d()
calculate_Gamma()
calculate_Gamma_d()
calculate_Rie()
Ric.use = simplify(tensorcontraction(Rie.use, (0, 2)))
calculate_R()
calculate_G()
calculate_G_alt()

def print_GR_tensors():
g.print_tensor()
g_inv.print_tensor()
g_d.print_tensor()
Gamma.print_tensor()
Gamma_d.print_tensor()
Rie.print_tensor()
Ric.print_tensor()
if R != 0:
disp(Math(latex(Eq(Symbol('R'), R))))
print('\n\n')
G.print_tensor()
G_alt.print_tensor()

g = Tensor('metric tensor', 'g', '_**')
g_inv = Tensor('inverse of metric tensor', 'g', '__**')
g_d = Tensor('partial derivative of metric tensor', 'g', '_**,*')
Gamma = Tensor('Christoffel symbol - 2nd kind', 'Gamma', '__*_**')
Gamma_d = Tensor('partial derivative of Christoffel symbol',
'Gamma', '__*_**,*')
Rie = Tensor('Riemann curvature tensor', 'R', '__*_***')
Ric = Tensor('Ricci curvature tensor', 'R', '_**')
G = Tensor('Einstein tensor', 'G', '_**')
G_alt = Tensor('Einstein tensor', 'G', '__*_*')

GREEK_SYMBOLS = ['alpha', 'beta', 'gamma', 'Gamma', 'delta', 'Delta',
'epsilon', 'varepsilon', 'zeta', 'eta', 'theta', 'vartheta',
'Theta', 'iota', 'kappa', 'lambda', 'Lambda', 'mu', 'nu',
'xi', 'Xi', 'pi', 'Pi', 'rho', 'varrho', 'sigma', 'Sigma',
'tau', 'upsilon', 'Upsilon', 'phi', 'varphi', 'Phi', 'chi',
'psi', 'Psi', 'omega', 'Omega']
OK_RESPONSES = ['y', 'yes', 'n', 'no']

if __name__ == '__main__':
compile_metric()
calculate_GR_tensors()
print_GR_tensors()


Example input:

• number of dimensions: 4
• coordinate 0: t
• coordinate 1: x
• coordinate 2: theta
• coordinate 3: phi
• metric diagonal?: y
• g_tt: -1
• g_xx: 1
• g_thetatheta: r(x)^2
• g_phiphi: r(x)^2sin(theta)^2

I've said it before and I'll say it again: you need to stop asking your user for markup that's non-standard and goes through an undocumented blender. Ask for Sympy-format expressions, period. I suspect that one of the reasons you attempted this is that your keys, such as

__*_**


for gamma are a weird and round-about post-processed way to get the following behaviour, which will actually just work with non-processed, non-backslash-escaped input:

^*_*_*


Jettison your string substitution. Parsing exists in Sympy for a reason, and it already understands Greek characters and ^ or ** just fine. The only thing it doesn't understand is implicit multiplication, but it's not too much to ask the user to write a * where they want one. Delete almost all of format_metric and all of GREEK_SYMBOLS.

Otherwise:

• Combine your sympy imports into one tuple-enclosed import
• You give Tensor.use two different default values, when it should have no default values at all. Make it accept an array on construction. Delete your field call and your initialize routine.
• Turn rank into a @property.
• Consider at least partial support for a non-Notebook mode that is still able to print expressions. In my suggested code I've had it show latex as a fallback.
• Your code is very difficult to reuse and test due to its reliance on global state. Tear everything out of the global namespace, make a top-level class (I've called it System but you can change that to whatever).
• Make a clean separation between interactive (from_stdin) and non-interactive mode. This enables, among other things, running a demo() function that doesn't require user input, and testing.
• Your check_dimension is problematic because instead of looping on failure, it recurses. Don't do that. Just loop.
• Enforce a positive integer in your dimension input.
• Separate out your ipynb to be a thin frontend to a potentially non-interactive Python backend; I've only shown the latter here.
• Show your coordinate prompts in actual rendered math.
• Don't call eye; call zeros
• Simplify your in-place successive string replacement for your key with a formatting call.

## Suggested

from dataclasses import dataclass, field
from itertools import product
from typing import List, Union, Optional, Sequence, Literal

from IPython import get_ipython
from sympy import (
MutableDenseNDimArray, Symbol, Matrix, S, diff,
tensorcontraction, latex, Eq, simplify, MutableDenseMatrix, Expr,
)
from IPython.display import Math, display

def is_notebook() -> bool:
return get_ipython() is not None

def display_eq(symbol: str, value: Expr) -> None:
markup = latex(Eq(Symbol(symbol), value))
if is_notebook():
display(Math(markup))
else:
print(markup)

@dataclass
class Coordinate:
index: int
label: str

def __post_init__(self) -> None:
self.symbol = Symbol(self.label)

@classmethod
def from_stdin(cls, index: int) -> 'Coordinate':
while True:
label = input(f'Enter coordinate label {index}: ')
if label.isalpha():
return cls(index, label)
print(r'Label must be alphabetic.')

def __str__(self) -> str:
return self.label

@property
def latex(self) -> str:
return latex(self.symbol)

@dataclass
class Tensor:
name: str
symbol: str
key: Sequence[Literal['*', '_']]
use: MutableDenseNDimArray

@property
def rank(self) -> int:
return self.key.count('*')

def print(self, coords: Sequence[Coordinate]) -> None:
n = len(coords)

for i in product(range(n), repeat=self.rank):
if self.use[i] == 0:
continue

index_key = self.key.replace('*', '%s') % tuple(
coords[t] for t in i
)

display_eq(self.symbol + index_key, self.use[i])

if any(self.use.reshape(len(self.use)).tolist()):
print('\n\n')

InputMatrix = List[List[Union[int, str]]]

class System:
def __init__(
self,
coords: List[Coordinate],
g_m: MutableDenseMatrix,
) -> None:
self.coords = coords
self.g = Tensor(name='metric tensor', symbol='g', key='_*_*',
use=MutableDenseNDimArray(g_m))
self.g_inv = Tensor(name='inverse of metric tensor', symbol='g', key='^*^*',
use=MutableDenseNDimArray(g_m.inv()))
self.g_d = Tensor(name='partial derivative of metric tensor', symbol='g', key='_*_*_,_*',
use=self.calculate_g_d())
self.Gamma = Tensor(name='Christoffel symbol - 2nd kind', symbol='Gamma', key='^*_*_*',
use=self.calculate_Gamma())
self.Gamma_d = Tensor(name='partial derivative of Christoffel symbol',
symbol='Gamma', key='^*_*_*_,_*',
use=self.calculate_Gamma_d())
self.Rie = Tensor(name='Riemann curvature tensor', symbol='R', key='^*_*_*_*',
use=self.calculate_Rie())
self.Ric = Tensor(name='Ricci curvature tensor', symbol='R', key='_*_*',
use=self.calculate_Ric())
self.R = self.calculate_R()
self.G = Tensor(name='Einstein tensor', symbol='G', key='_*_*',
use=self.calculate_G())
self.G_alt = Tensor(name='Einstein tensor', symbol='G', key='^*_*',
use=self.calculate_G_alt())

@property
def n(self) -> int:
return len(self.coords)

@classmethod
def from_stdin(cls) -> 'System':
coords = [Coordinate.from_stdin(i) for i in range(n)]
return cls(
coords=coords,
)

@staticmethod
while True:
try:
n = int(input('Enter the number of dimensions: '))
if n > 0:
return n
except ValueError:
pass

print('Number of dimensions needs to be a positive integer!')

@staticmethod
while True:
diagonal = input('Is metric diagonal (y/n)? ').lower()
if diagonal[:1] in {'y', 'n'}:
return diagonal.startswith('y')

@staticmethod
def g_prompt(i: Coordinate, j: Coordinate) -> str:
if is_notebook():
display(Math(
r'\text{What is } g_{'
+ i.latex + ' '
+ j.latex + '} ? '
))
prompt = ''
else:
prompt = 'What is g_{%s%s}? ' % (i, j)

return input(prompt)

@classmethod
def ask_metric(cls, coords: List[Coordinate], diagonal: bool) -> InputMatrix:
g_m: InputMatrix = [
[0 for _ in coords]
for _ in coords
]
if diagonal:
for i, coord in enumerate(coords):
g_m[i][i] = cls.g_prompt(coord, coord)
else:
for i, i_coord in enumerate(coords):
for j, j_coord in enumerate(coords):
g_m[i][j] = cls.g_prompt(i_coord, j_coord)
return g_m

@classmethod
def ask_and_parse_metric(cls, coords: List[Coordinate], diagonal: bool) -> MutableDenseMatrix:
while True:
# Is a transpose needed?
# g_m[j][i] = g_m[i][j]
if g_m.det() == 0:
print('Metric is singular; try again!')
else:
return g_m

def calculate_g_d(self) -> MutableDenseNDimArray:
g = self.g.use
n = self.n
g_d = MutableDenseNDimArray.zeros(n, n, n)
for i, j, k in product(range(n), repeat=3):
g_d[i, j, k] = diff(g[i, j], self.coords[k].symbol)
return g_d

def calculate_Gamma(self) -> MutableDenseNDimArray:
g_inv, g_d = self.g_inv.use, self.g_d.use
n = self.n
Gamma = MutableDenseNDimArray.zeros(n, n, n)
for i, j, k in product(range(n), repeat=3):
for m in range(n):
Gamma[i, j, k] += S(1)/2 * g_inv[i, m] * (
g_d[k, m, j]
+ g_d[m, j, k]
- g_d[j, k, m]
)
return Gamma

def calculate_Gamma_d(self) -> MutableDenseNDimArray:
n = self.n
Gamma_d = MutableDenseNDimArray.zeros(n, n, n, n)
Gamma = self.Gamma.use
for ijkl in product(range(n), repeat=4):
*ijk, l = ijkl
Gamma_d[ijkl] = simplify(diff(Gamma[ijk], self.coords[l].symbol))
return Gamma_d

def calculate_Rie(self) -> MutableDenseNDimArray:
n = self.n
Gamma, Gamma_d = self.Gamma.use, self.Gamma_d.use
Rie = MutableDenseNDimArray.zeros(n, n, n, n)
for ijkl in product(range(n), repeat=4):
i, j, k, l = ijkl
Rie[ijkl] = Gamma_d[i, j, l, k] - Gamma_d[ijkl]
for m in range(n):
Rie[ijkl] += (
Gamma[m, j, l] * Gamma[i, m, k]
- Gamma[m, j, k] * Gamma[i, m, l]
)
Rie[ijkl] = simplify(Rie[ijkl])
return Rie

def calculate_Ric(self) -> MutableDenseNDimArray:
return simplify(tensorcontraction(self.Rie.use, (0, 2)))

def calculate_R(self) -> Expr:
n = self.n
g_inv, Ric = self.g_inv.use, self.Ric.use
R = 0
for i in product(range(n), repeat=2):
R += g_inv[i] * Ric[i]
return simplify(R)

def calculate_G(self) -> MutableDenseNDimArray:
n = self.n
Ric, R, g = self.Ric.use, self.R, self.g.use
G = MutableDenseNDimArray.zeros(n, n)
for i in product(range(n), repeat=2):
G[i] = simplify(Ric[i] - S(1)/2 * R * g[i])
return G

def calculate_G_alt(self) -> MutableDenseNDimArray:
n = self.n
g_inv, G = self.g_inv.use, self.G.use
G_alt = MutableDenseNDimArray.zeros(n, n)
for ij in product(range(n), repeat=2):
i, j = ij
for k in range(n):
G_alt[ij] += g_inv[i, k] * G[k, j]
G_alt[ij] = simplify(G_alt[ij])
return G_alt

def print_GR_tensors(self) -> None:
for tensor in (
self.g, self.g_inv, self.g_d, self.Gamma, self.Gamma_d, self.Rie, self.Ric,
):
tensor.print(self.coords)

if self.R != 0:
display_eq('R', self.R)
print('\n\n')

self.G.print(self.coords)
self.G_alt.print(self.coords)

def demo() -> System:
return System(
coords=[
Coordinate(i, name)
for i, name in enumerate((
't', 'x', 'theta', 'phi',
))
],
g_m=MutableDenseMatrix([
[-1, 0, 0, 0],
[ 0, 1, 0, 0],
[ 0, 0, 'r(x)^2', 0],
[ 0, 0, 0, 'r(x)^2 * sin(theta)^2']
])
)

if __name__ == '__main__':
system = demo()
# system = System.from_stdin()
system.print_GR_tensors()


## Output

This is just the output of the non-Notebook mode with  prefixes and suffixes for the StackExchange math markup system.

$$g_{t t} = -1$$ $$g_{x x} = 1$$ $$g_{\theta \theta} = r^{2}{\left(x \right)}$$ $$g_{\phi \phi} = r^{2}{\left(x \right)} \sin^{2}{\left(\theta \right)}$$ $$g^{t t} = -1$$ $$g^{x x} = 1$$ $$g^{\theta \theta} = \frac{1}{r^{2}{\left(x \right)}}$$ $$g^{\phi \phi} = \frac{1}{r^{2}{\left(x \right)} \sin^{2}{\left(\theta \right)}}$$ $$g_{\theta \theta , x} = 2 r{\left(x \right)} \frac{d}{d x} r{\left(x \right)}$$ $$g_{\phi \phi , x} = 2 r{\left(x \right)} \sin^{2}{\left(\theta \right)} \frac{d}{d x} r{\left(x \right)}$$ $$g_{\phi \phi , \theta} = 2 r^{2}{\left(x \right)} \sin{\left(\theta \right)} \cos{\left(\theta \right)}$$ $$\Gamma^{x}_{\theta \theta} = - r{\left(x \right)} \frac{d}{d x} r{\left(x \right)}$$ $$\Gamma^{x}_{\phi \phi} = - r{\left(x \right)} \sin^{2}{\left(\theta \right)} \frac{d}{d x} r{\left(x \right)}$$ $$\Gamma^{\theta}_{x \theta} = \frac{\frac{d}{d x} r{\left(x \right)}}{r{\left(x \right)}}$$ $$\Gamma^{\theta}_{\theta x} = \frac{\frac{d}{d x} r{\left(x \right)}}{r{\left(x \right)}}$$ $$\Gamma^{\theta}_{\phi \phi} = - \sin{\left(\theta \right)} \cos{\left(\theta \right)}$$ $$\Gamma^{\phi}_{x \phi} = \frac{\frac{d}{d x} r{\left(x \right)}}{r{\left(x \right)}}$$ $$\Gamma^{\phi}_{\theta \phi} = \frac{\cos{\left(\theta \right)}}{\sin{\left(\theta \right)}}$$ $$\Gamma^{\phi}_{\phi x} = \frac{\frac{d}{d x} r{\left(x \right)}}{r{\left(x \right)}}$$ $$\Gamma^{\phi}_{\phi \theta} = \frac{\cos{\left(\theta \right)}}{\sin{\left(\theta \right)}}$$ $$\Gamma^{x}_{\theta \theta , x} = - r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)} - \left(\frac{d}{d x} r{\left(x \right)}\right)^{2}$$ $$\Gamma^{x}_{\phi \phi , x} = - \left(r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)} + \left(\frac{d}{d x} r{\left(x \right)}\right)^{2}\right) \sin^{2}{\left(\theta \right)}$$ $$\Gamma^{x}_{\phi \phi , \theta} = - r{\left(x \right)} \sin{\left(2 \theta \right)} \frac{d}{d x} r{\left(x \right)}$$ $$\Gamma^{\theta}_{x \theta , x} = \frac{r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)} - \left(\frac{d}{d x} r{\left(x \right)}\right)^{2}}{r^{2}{\left(x \right)}}$$ $$\Gamma^{\theta}_{\theta x , x} = \frac{r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)} - \left(\frac{d}{d x} r{\left(x \right)}\right)^{2}}{r^{2}{\left(x \right)}}$$ $$\Gamma^{\theta}_{\phi \phi , \theta} = - \cos{\left(2 \theta \right)}$$ $$\Gamma^{\phi}_{x \phi , x} = \frac{r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)} - \left(\frac{d}{d x} r{\left(x \right)}\right)^{2}}{r^{2}{\left(x \right)}}$$ $$\Gamma^{\phi}_{\theta \phi , \theta} = - \frac{1}{\sin^{2}{\left(\theta \right)}}$$ $$\Gamma^{\phi}_{\phi x , x} = \frac{r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)} - \left(\frac{d}{d x} r{\left(x \right)}\right)^{2}}{r^{2}{\left(x \right)}}$$ $$\Gamma^{\phi}_{\phi \theta , \theta} = - \frac{1}{\sin^{2}{\left(\theta \right)}}$$ $$R^{x}_{\theta x \theta} = - r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)}$$ $$R^{x}_{\theta \theta x} = r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)}$$ $$R^{x}_{\phi x \phi} = - r{\left(x \right)} \sin^{2}{\left(\theta \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)}$$ $$R^{x}_{\phi \phi x} = r{\left(x \right)} \sin^{2}{\left(\theta \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)}$$ $$R^{\theta}_{x x \theta} = \frac{\frac{d^{2}}{d x^{2}} r{\left(x \right)}}{r{\left(x \right)}}$$ $$R^{\theta}_{x \theta x} = - \frac{\frac{d^{2}}{d x^{2}} r{\left(x \right)}}{r{\left(x \right)}}$$ $$R^{\theta}_{\phi \theta \phi} = \left(1 - \left(\frac{d}{d x} r{\left(x \right)}\right)^{2}\right) \sin^{2}{\left(\theta \right)}$$ $$R^{\theta}_{\phi \phi \theta} = \left(\left(\frac{d}{d x} r{\left(x \right)}\right)^{2} - 1\right) \sin^{2}{\left(\theta \right)}$$ $$R^{\phi}_{x x \phi} = \frac{\frac{d^{2}}{d x^{2}} r{\left(x \right)}}{r{\left(x \right)}}$$ $$R^{\phi}_{x \phi x} = - \frac{\frac{d^{2}}{d x^{2}} r{\left(x \right)}}{r{\left(x \right)}}$$ $$R^{\phi}_{\theta \theta \phi} = \left(\frac{d}{d x} r{\left(x \right)}\right)^{2} - 1$$ $$R^{\phi}_{\theta \phi \theta} = 1 - \left(\frac{d}{d x} r{\left(x \right)}\right)^{2}$$ $$R_{x x} = - \frac{2 \frac{d^{2}}{d x^{2}} r{\left(x \right)}}{r{\left(x \right)}}$$ $$R_{\theta \theta} = - r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)} - \left(\frac{d}{d x} r{\left(x \right)}\right)^{2} + 1$$ $$R_{\phi \phi} = \left(- r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)} - \left(\frac{d}{d x} r{\left(x \right)}\right)^{2} + 1\right) \sin^{2}{\left(\theta \right)}$$ $$R = \frac{2 \left(- 2 r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)} - \left(\frac{d}{d x} r{\left(x \right)}\right)^{2} + 1\right)}{r^{2}{\left(x \right)}}$$ $$G_{t t} = \frac{- 2 r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)} - \left(\frac{d}{d x} r{\left(x \right)}\right)^{2} + 1}{r^{2}{\left(x \right)}}$$ $$G_{x x} = \frac{\left(\frac{d}{d x} r{\left(x \right)}\right)^{2} - 1}{r^{2}{\left(x \right)}}$$ $$G_{\theta \theta} = r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)}$$ $$G_{\phi \phi} = r{\left(x \right)} \sin^{2}{\left(\theta \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)}$$ $$G^{t}_{t} = \frac{2 r{\left(x \right)} \frac{d^{2}}{d x^{2}} r{\left(x \right)} + \left(\frac{d}{d x} r{\left(x \right)}\right)^{2} - 1}{r^{2}{\left(x \right)}}$$ $$G^{x}_{x} = \frac{\left(\frac{d}{d x} r{\left(x \right)}\right)^{2} - 1}{r^{2}{\left(x \right)}}$$ $$G^{\theta}_{\theta} = \frac{\frac{d^{2}}{d x^{2}} r{\left(x \right)}}{r{\left(x \right)}}$$ $$G^{\phi}_{\phi} = \frac{\frac{d^{2}}{d x^{2}} r{\left(x \right)}}{r{\left(x \right)}}$$

• Wow, thanks for such thorough feedback, I really appreciate your time! Pretty much everything you suggested changing is new stuff to me, so I'll be able to learn a lot from your post. Thanks! Commented Oct 5, 2021 at 2:58
• Np. For what it's worth, a mathematical physicist also looked at the output of your program and concluded that it's probably right - so nice work. Commented Oct 5, 2021 at 3:30