"""Cold dust collapse (LTB / Oppenheimer-Snyder): every shell is a radial Schwarzschild geodesic
of the mass m(r0) enclosed initially, in its own proper time; the outermost shell also follows
the exterior coordinate-time law."""
import numpy as np
import pytest
from pbhgr.spherical_ev import Grid, RunConfig, Run, solve_metric, metric_at
from pbhgr.initial_data import dust_ball, normalise_mass
from pbhgr.analytic import schwarzschild_radial_infall, eta_of_t
from scipy.optimize import brentq
M, R0 = 1.0, 10.0
def _run(n_part, K, t_end=25.0, seed=3):
rng = np.random.default_rng(seed)
g = Grid(r_out=20.0, K=K)
P = normalise_mass(g, dust_ball(M, R0, n_part, rng), M)
met0 = solve_metric(g, P)
m0 = metric_at(g, met0, P.r)[0]
r0 = P.r.copy()
run = Run(g, P, RunConfig(t_end=t_end, cfl=0.4, diag_every=50))
run.run()
return run, r0, m0
def _r_of_tau(m, r0, tau):
if m <= 0:
return r0
f = lambda e: np.sqrt(r0**3 / (8 * m)) * (e + np.sin(e)) - tau
e = brentq(f, 0.0, np.pi, xtol=1e-12)
return 0.5 * r0 * (1 + np.cos(e))
def test_interior_shells_follow_ltb_geodesics():
run, r0, m0 = _run(20000, 400)
P = run.P
# all particles still inside (no escape) and ADM mass conserved
assert run.diag[-1]["escaped_energy"] == 0.0
assert abs(run.diag[-1]["M_total"] - M) < 1e-4
sel = r0 > 0.2 * R0 # skip innermost shells where 1/r^3 resolution is poor
r_pred = np.array([_r_of_tau(m, a, tau) for m, a, tau in zip(m0[sel], r0[sel], P.tau[sel])])
rel = np.abs(P.r[sel] - r_pred) / r0[sel]
assert np.median(rel) < 5e-3
assert np.percentile(rel, 95) < 2e-2
def test_surface_follows_exterior_coordinate_time_law():
run, r0, m0 = _run(20000, 400)
i = int(np.argmax(r0))
eta = eta_of_t(M, r0[i], run.t)
r_exact = schwarzschild_radial_infall(m0[i], r0[i], eta)[0]
assert abs(run.P.r[i] - r_exact) / r0[i] < 1e-2
@pytest.mark.slow
def test_surface_error_converges_with_resolution():
errs = []
for n, K in ((5000, 100), (20000, 200), (80000, 400)):
run, r0, m0 = _run(n, K, t_end=25.0)
i = int(np.argmax(r0))
eta = eta_of_t(M, r0[i], run.t)
r_exact = schwarzschild_radial_infall(m0[i], r0[i], eta)[0]
errs.append(abs(run.P.r[i] - r_exact) / r0[i])
assert errs[2] < errs[0]