use num_traits::{Zero, real::Real};
use diffable::coords::Coords;
use diffable::traits::{
Dual, Euclidean, Right, Tensor,
calculus::{JetRegion, TensorProduct},
ฮน, ๐๐๐๐ฅ,
};
use crate::ad::jacobian_of;
use crate::metric::{MetricField, ScalarConst};
use crate::tensor::{
T2, T3, T4, close_to_zero, close_to_zero_scalar, flatten_3, flatten_4, from_flat_2,
from_flat_3, from_flat_4, invert, permute3, permute4, to_flat,
};
pub type Christoffel<V> = TensorProduct<TensorProduct<V, Dual<V>>, Dual<V>>;
pub type Riemann<V> = TensorProduct<Christoffel<V>, Dual<V>>;
pub type Ricci<V> = crate::metric::MetricTensor<V>;
pub(crate) fn christoffel_generic<V, const N: usize, M>(metric: &M, x: V) -> Christoffel<V>
where
V: Euclidean + Tensor<Hand = Right> + Copy,
V::F: Real + ScalarConst + ฮน<C: JetRegion<๐๐๐๐ฅ::๐>>,
M: MetricField<N>,
{
let g = metric.g(x);
let g_arr: T2<V::F, N> = from_flat_2(&to_flat(&g));
let g_inv = invert(&g_arr);
let d_flat = jacobian_of(|v| metric.g(v), &x);
let d: T3<V::F, N> = from_flat_3(&d_flat);
let d_mlk = permute3(&d, [2, 0, 1]); let d_klm = permute3(&d, [1, 2, 0]);
let mut gamma = [[[V::F::zero(); N]; N]; N];
let half = V::F::from_const(0.5);
for j in 0..N {
for k in 0..N {
for l in 0..N {
let mut s = V::F::zero();
for m in 0..N {
s = s + g_inv[j][m] * (d[m][k][l] + d_mlk[m][k][l] - d_klm[m][k][l]);
}
gamma[j][k][l] = s * half;
}
}
}
Christoffel::<V>::from_fn(|i| flatten_3(&gamma)[i])
}
pub fn christoffel_symbols<const N: usize, M: MetricField<N>>(
metric: &M,
x: &Coords<f64, N>,
) -> Christoffel<Coords<f64, N>> {
close_to_zero(christoffel_generic(metric, *x))
}
pub fn torsion_tensor<const N: usize, M: MetricField<N>>(
metric: &M,
x: &Coords<f64, N>,
) -> Christoffel<Coords<f64, N>> {
let g = christoffel_generic(metric, *x);
let flat = to_flat(&g);
let arr: T3<f64, N> = from_flat_3(&flat);
let swapped = permute3(&arr, [0, 2, 1]); let mut out = [[[0.0; N]; N]; N];
for j in 0..N {
for k in 0..N {
for l in 0..N {
out[j][k][l] = arr[j][k][l] - swapped[j][k][l];
}
}
}
close_to_zero(Christoffel::<Coords<f64, N>>::from_fn(|i| {
flatten_3(&out)[i]
}))
}
pub(crate) fn riemann_raw<const N: usize, M: MetricField<N>>(
metric: &M,
x: &Coords<f64, N>,
) -> T4<f64, N> {
let gamma_flat = to_flat(&christoffel_generic(metric, *x));
let gamma: T3<f64, N> = from_flat_3(&gamma_flat);
let d_flat = jacobian_of(|v| christoffel_generic(metric, v), x);
let dg: T4<f64, N> = from_flat_4(&d_flat);
let dg_swap = permute4(&dg, [0, 1, 3, 2]);
let mut r = [[[[0.0; N]; N]; N]; N];
for j in 0..N {
for k in 0..N {
for l in 0..N {
for m in 0..N {
let t3: f64 = (0..N).map(|s| gamma[j][s][m] * gamma[s][k][l]).sum();
let t4: f64 = (0..N).map(|s| gamma[j][s][l] * gamma[s][k][m]).sum();
r[j][k][l][m] = dg[j][k][l][m] - dg_swap[j][k][l][m] + t3 - t4;
}
}
}
}
r
}
pub fn riemann_tensor<const N: usize, M: MetricField<N>>(
metric: &M,
x: &Coords<f64, N>,
) -> Riemann<Coords<f64, N>> {
let r = riemann_raw(metric, x);
close_to_zero(Riemann::<Coords<f64, N>>::from_fn(|i| flatten_4(&r)[i]))
}
pub fn ricci_tensor<const N: usize, M: MetricField<N>>(
metric: &M,
x: &Coords<f64, N>,
) -> Ricci<Coords<f64, N>> {
let r = riemann_raw(metric, x);
let mut out = [[0.0; N]; N];
for k in 0..N {
for l in 0..N {
out[k][l] = (0..N).map(|j| r[j][k][l][j]).sum();
}
}
close_to_zero(Ricci::<Coords<f64, N>>::from_fn(|i| out[i / N][i % N]))
}
pub fn ricci_scalar<const N: usize, M: MetricField<N>>(metric: &M, x: &Coords<f64, N>) -> f64 {
let g_arr: T2<f64, N> = from_flat_2(&to_flat(&metric.g(*x)));
let g_inv = invert(&g_arr);
let ricci = to_flat(&ricci_tensor(metric, x));
let mut s = 0.0;
for k in 0..N {
for l in 0..N {
s += g_inv[k][l] * ricci[k * N + l];
}
}
close_to_zero_scalar(s)
}
pub fn kretschmann_invariant<const N: usize, M: MetricField<N>>(
metric: &M,
x: &Coords<f64, N>,
) -> f64 {
let r = riemann_raw(metric, x);
let g_arr: T2<f64, N> = from_flat_2(&to_flat(&metric.g(*x)));
let g_inv = invert(&g_arr);
let mut upper = [[[[0.0; N]; N]; N]; N];
let mut lower = [[[[0.0; N]; N]; N]; N];
for i in 0..N {
for p in 0..N {
for q in 0..N {
for rr in 0..N {
let mut s = 0.0;
for j in 0..N {
for k in 0..N {
for l in 0..N {
s += g_inv[p][j] * g_inv[q][k] * g_inv[rr][l] * r[i][j][k][l];
}
}
}
upper[i][p][q][rr] = s;
}
}
}
}
for p in 0..N {
for j in 0..N {
for k in 0..N {
for l in 0..N {
lower[p][j][k][l] = (0..N).map(|i| g_arr[p][i] * r[i][j][k][l]).sum();
}
}
}
}
let k = (0..N)
.flat_map(|i| {
(0..N).flat_map(move |j| {
(0..N).flat_map(move |k| (0..N).map(move |l| upper[i][j][k][l] * lower[i][j][k][l]))
})
})
.sum();
close_to_zero_scalar(k)
}
pub fn einstein_tensor<const N: usize, M: MetricField<N>>(
metric: &M,
x: &Coords<f64, N>,
) -> Ricci<Coords<f64, N>> {
let rt = to_flat(&ricci_tensor(metric, x));
let rs = ricci_scalar(metric, x);
let g_arr: T2<f64, N> = from_flat_2(&to_flat(&metric.g(*x)));
let mut out = [[0.0; N]; N];
for i in 0..N {
for j in 0..N {
out[i][j] = rt[i * N + j] - 0.5 * g_arr[i][j] * rs;
}
}
close_to_zero(Ricci::<Coords<f64, N>>::from_fn(|k| out[k / N][k % N]))
}
pub fn stress_energy_momentum_tensor<const N: usize, M: MetricField<N>>(
metric: &M,
x: &Coords<f64, N>,
) -> Ricci<Coords<f64, N>> {
let g = to_flat(&einstein_tensor(metric, x));
let kappa = (8.0 * std::f64::consts::PI * 6.67e-11) / 299_792_458.0_f64.powi(4);
let out: Vec<f64> = g.iter().map(|v| v / kappa).collect();
close_to_zero(Ricci::<Coords<f64, N>>::from_fn(|i| out[i]))
}