Ко всем библиотекам · scripts

scripts/ellipsoidal_collapse.py

Bond-Myers (1996) ellipsoidal collapse of a homogeneous ellipsoid in a matter-dominated background, external tide in linear theory, Zel'dovich initial conditions (lambda_1 >= lambda_2 >= lambda_3, delta = sum lambda, e = (l1 - l3)/(2…

74 строк · 4.6 KB · pbhgr @ 9e8e13d · как текст

"""Bond-Myers (1996) ellipsoidal collapse of a homogeneous ellipsoid in a matter-dominated background, external tide
in linear theory, Zel'dovich initial conditions (lambda_1 >= lambda_2 >= lambda_3, delta = sum lambda,
e = (l1 - l3)/(2 delta), p = (l1 + l3 - 2 l2)/(2 delta)).  Each axis is frozen when it has collapsed to f_freeze of
its maximum; the collapse times t_1 <= t_2 <= t_3 are reported relative to the spherical collapse time of the same
delta (e = p = 0).  Then the distribution of (e, p) for peaks of height nu (Sheth-Mo-Tormen 2001, eq. A3) gives the
distribution of t_3/t_sph, i.e. the expected shift of the deadline map with oblateness/prolateness.
Usage: python3 ellipsoidal_collapse.py [nu=4] [f_freeze=0.01]"""
import sys, numpy as np
from scipy.integrate import solve_ivp, quad
nu = float(sys.argv[1]) if len(sys.argv) > 1 else 4.0
f_freeze = float(sys.argv[2]) if len(sys.argv) > 2 else 0.01

from scipy.special import elliprd
def b_coeffs(R):
    """Interior shape coefficients b_i = R1 R2 R3 int_0^inf dtau / ((R_i^2+tau) prod_j sqrt(R_j^2+tau)) - 2/3
    = (2/3) R1 R2 R3 R_D(R_j^2, R_k^2, R_i^2) - 2/3 (Carlson's R_D)."""
    R = np.asarray(R, dtype=float); pr = np.prod(R); x = R**2
    return np.array([2.0 / 3.0 * pr * elliprd(x[1], x[2], x[0]) - 2.0 / 3.0,
                     2.0 / 3.0 * pr * elliprd(x[0], x[2], x[1]) - 2.0 / 3.0,
                     2.0 / 3.0 * pr * elliprd(x[0], x[1], x[2]) - 2.0 / 3.0])

def collapse_times(e, p, delta_i=1e-2, t_i=1.0):
    """EdS: a = (t/t_i)^{2/3}, rho_b = 1/(6 pi G t^2) with G = 1.  Returns (t1, t2, t3) in units of t_i."""
    lam = delta_i * np.array([(1 + 3 * e + p) / 3, (1 - 2 * p) / 3, (1 - 3 * e + p) / 3])   # l1 >= l2 >= l3
    R0 = 1.0 - lam                                        # Zel'dovich at t_i (a_i = 1), comoving unit size
    V0 = (2.0 / (3.0 * t_i)) * (1.0 - 2.0 * lam)          # dR/dt = H (1 - 2 lambda) for D ~ a
    frozen = [False] * 3; Rfreeze = [None] * 3; tcol = [np.nan] * 3; Rmax = R0.copy()
    def rhs(t, y):
        R = np.maximum(y[:3].copy(), 1e-9); V = y[3:].copy()
        for i in range(3):
            if frozen[i]: R[i] = Rfreeze[i]
        a = (t / t_i) ** (2.0 / 3.0); rho_b = 1.0 / (6 * np.pi * t**2)
        delta = a**3 / np.prod(R) - 1.0                   # Zel'dovich volume already carries delta_i
        b = b_coeffs(R)
        lam_ext = (lam - delta_i / 3) * a                 # linear external tide
        acc = -4 * np.pi * rho_b * R * ((1 + delta) / 3 + 0.5 * b * delta + lam_ext)
        for i in range(3):
            if frozen[i]: acc[i] = 0.0; V[i] = 0.0
        return np.concatenate([V, acc])
    y = np.concatenate([R0, V0]); t = t_i
    t_end = 1e5 * t_i
    while t < t_end and not all(frozen):
        def ev(tt, yy):
            return min(yy[i] - f_freeze * Rmax[i] for i in range(3) if not frozen[i])
        ev.terminal = True; ev.direction = -1
        sol = solve_ivp(rhs, (t, t_end), y, events=ev, rtol=1e-8, atol=1e-12, method='DOP853', max_step=0.02 * t + 1e-9)
        t = sol.t[-1]; y = sol.y[:, -1]
        Rmax = np.maximum(Rmax, y[:3])
        if sol.status == 1:
            i = int(np.argmin([y[j] - f_freeze * Rmax[j] if not frozen[j] else np.inf for j in range(3)]))
            frozen[i] = True; Rfreeze[i] = y[i]; tcol[i] = t
        else:
            break
    return np.array(tcol) / t_i

t_sph = collapse_times(0.0, 0.0)[2]
print(f"nu={nu}, f_freeze={f_freeze}: spherical collapse (top-hat) at t_sph = {t_sph:.1f} t_i for delta_i = 1e-2 (top-hat expectation: t_i (1.686/delta_i)^1.5 = {(1.686/1e-2)**1.5:.0f} t_i)")
print("e     p     | t1/t_sph  t2/t_sph  t3/t_sph")
grid = []
for e in (0.0, 0.05, 0.1, 0.15, 0.2, 0.3):
    for p in (-0.5 * e, 0.0, 0.5 * e) if e > 0 else (0.0,):
        tc = collapse_times(e, p) / t_sph
        grid.append((e, p, tc)); print(f"{e:4.2f} {p:5.2f} | {tc[0]:8.3f} {tc[1]:8.3f} {tc[2]:8.3f}")
# Zel'dovich (e, p) distribution for a peak of height nu (Sheth, Mo & Tormen 2001, eq. A3)
def p_ep(e, p):
    return 1125.0 / np.sqrt(10 * np.pi) * e * (e**2 - p**2) * nu**5 * np.exp(-2.5 * nu**2 * (3 * e**2 + p**2))
es = np.linspace(0.002, 0.6, 300); pe = np.array([quad(lambda p: p_ep(e, p), -e, e)[0] for e in es])
pe /= np.trapezoid(pe, es); cdf = np.cumsum(pe) * (es[1] - es[0])
e_med, e_90 = es[np.searchsorted(cdf, 0.5)], es[np.searchsorted(cdf, 0.9)]
print(f"\nZel'dovich ellipticity for nu={nu}: median e = {e_med:.3f}, 90th percentile e = {e_90:.3f}, most probable e ~ {es[np.argmax(pe)]:.3f}")
for lab, ee in (("median", e_med), ("90 %", e_90)):
    tc = collapse_times(ee, 0.0) / t_sph
    print(f"  e = {ee:.3f} (p = 0): t1/t_sph = {tc[0]:.3f}, t3/t_sph = {tc[2]:.3f}")