-- Parameters
x := [| θ, φ |]

X := [| r * sin θ * cos φ -- x
      , r * sin θ * sin φ -- y
      , r * cos θ         -- z
      |]

e_i_j := ∂/∂ X_j x~i

-- Metric tensors
g_i_j := generateTensor (\x y -> V.* e_x_# e_y_#) [2, 2]
g~i~j := M.inverse g_#_#

assertEqual "Metric tensor"
  g_#_#
  [| [| r^2, 0 |], [| 0, r^2 * (sin θ)^2 |] |]_#_#
assertEqual "Metric tensor"
  g~#~#
  [| [| 1 / r^2, 0 |], [| 0, 1 / (r^2 * (sin θ)^2) |] |]~#~#

-- Christoffel symbols
Γ_i_j_k := (1 / 2) * (∂/∂ g_i_k x~j + ∂/∂ g_i_j x~k - ∂/∂ g_j_k x~i)

assertEqual "Christoffel symbols of the first kind"
  Γ_1_#_#
  [| [| 0, 0 |], [| 0, -1 * r^2 * (sin θ) * (cos θ) |] |]_#_#
assertEqual "Christoffel symbols of the first kind"
  Γ_2_#_#
  [| [| 0, r^2 * (sin θ) * (cos θ) |], [| r^2 * (sin θ) * (cos θ), 0 |] |]_#_#

Γ~i_j_k := withSymbols [m]
  g~i~m . Γ_m_j_k

assertEqual "Christoffel symbols of the second kind"
  Γ~1_#_#
  [| [| 0, 0 |], [| 0, -1 * sin θ * cos θ |] |]_#_#
assertEqual "Christoffel symbols of the second kind"
  Γ~2_#_#
  [| [| 0, (cos θ) / (sin θ) |], [| (cos θ) / (sin θ), 0 |] |]_#_#

-- Riemann curvature
R~i_j_k_l := withSymbols [m]
  ∂/∂ Γ~i_j_l x~k - ∂/∂ Γ~i_j_k x~l + Γ~m_j_l . Γ~i_m_k - Γ~m_j_k . Γ~i_m_l

assertEqual "riemann curvature"
  R~#_#_1_1
  [| [| 0, 0 |], [| 0, 0 |] |]~#_#
assertEqual "riemann curvature"
  R~#_#_1_2
  [| [| 0, (sin θ)^2 |], [| -1, 0 |] |]~#_#
assertEqual "riemann curvature"
  R~#_#_2_1
  [| [| 0, -1 * (sin θ)^2 |], [| 1, 0 |] |]~#_#
assertEqual "riemann curvature"
  R~#_#_2_2
  [| [| 0, 0 |], [| 0, 0 |] |]~#_#

-- Ricci curvature
Ric_i_j := withSymbols [m]
  sum (contract R~m_i_m_j)

-- Scalar curvature
scalarCurvature := withSymbols [i, j]
  g~i~j . Ric_i_j

assertEqual "scalar curvature"
  scalarCurvature
  (2 / r^2)
