r"""
Post-TOV (modified TOV) equation solver with beyond-GR corrections.
This module extends the standard TOV equations to include phenomenological
modifications that parameterize deviations from General Relativity. The
modifications are implemented through additional terms in the pressure
gradient equation.
**Units:** All calculations are performed in geometric units where :math:`G = c = 1`.
**Reference:** Yagi & Yunes, Phys. Rev. D 88, 023009 (2013)
"""
from . import utils
import jax
import jax.numpy as jnp
from diffrax import diffeqsolve, ODETerm, Dopri5, SaveAt, PIDController
[docs]
def sigma_func(p, e, m, r, lambda_BL, lambda_DY, lambda_HB, gamma, alpha, beta):
r"""
Compute the non-GR correction term sigma for modified TOV equations.
This function implements various phenomenological modifications to
General Relativity that appear as additional terms in the TOV equations.
The corrections are parameterized by several coupling constants.
The sigma function includes:
- **Brans-Dicke-like**: :math:`\sigma_{\mathrm{BL}} = -\frac{\lambda_{\mathrm{BL}} r^2}{3}(\varepsilon + 3p)(\varepsilon + p)A`
- **Dynamical Chern-Simons**: :math:`\sigma_{\mathrm{DY}} = \lambda_{\mathrm{DY}} \frac{2m}{r} p`
- **Horava-like**: :math:`\sigma_{\mathrm{HB}} = -(\frac{1}{\lambda_{\mathrm{HB}}} - 1) \frac{r}{2} \frac{dp}{dr}`
- **Post-Newtonian**: :math:`\sigma_{\mathrm{PN}} = \gamma \frac{2m}{r} p \tanh(\alpha(\frac{m}{r} - \beta))`
Args:
p (float): Pressure at current radius.
e (float): Energy density at current radius.
m (float): Enclosed mass at current radius.
r (float): Current radius.
lambda_BL (float): Bowers-Liang coupling parameter.
lambda_DY (float): Doneva-Yazadjiev coupling parameter.
lambda_HB (float): Herrera-Barreto coupling parameter.
gamma (float): Post-Newtonian amplitude parameter.
alpha (float): Post-Newtonian steepness parameter.
beta (float): Post-Newtonian transition point parameter.
Returns:
float: Total sigma correction term.
"""
# Metric coefficient A = 1/(1-2m/r)
A = 1.0 / (1.0 - 2.0 * m / r)
dpdr = -(e + p) * (m + 4.0 * jnp.pi * r * r * r * p) / r / r * A
sigma = 0.0
# models reviewed in https://doi.org/10.1140/epjc/s10052-020-8361-4
# in Eq. 12, the power of epsilon should be 2
sigma += -lambda_BL * r * r / 3.0 * (e + 3.0 * p) * (e + p) * A
sigma += lambda_DY * 2.0 * m / r * p
sigma += -(1.0 / lambda_HB - 1.0) * r / 2.0 * dpdr
# post-Newtonian modification
sigma += gamma * 2.0 * m / r * p * jnp.tanh(alpha * (m / r - beta))
return sigma
[docs]
def tov_ode(h, y, eos):
# fetch the eos arrays
ps = eos["p"]
hs = eos["h"]
es = eos["e"]
dloge_dlogps = eos["dloge_dlogp"]
# actual equations
r, m, H, b = y
e = utils.interp_in_logspace(h, hs, es)
p = utils.interp_in_logspace(h, hs, ps)
dedp = e / p * jnp.interp(h, hs, dloge_dlogps)
# evalute the sigma and dsigmadp
sigma = sigma_func(
p,
e,
m,
r,
eos["lambda_BL"],
eos["lambda_DY"],
eos["lambda_HB"],
eos["gamma"],
eos["alpha"],
eos["beta"],
)
dsigmadp_fn = jax.grad(sigma_func, argnums=0) # Gradient w.r.t. p
dsigmade_fn = jax.grad(sigma_func, argnums=1) # Gradient w.r.t. p
dsigmadp = dsigmadp_fn(
p,
e,
m,
r,
eos["lambda_BL"],
eos["lambda_DY"],
eos["lambda_HB"],
eos["gamma"],
eos["alpha"],
eos["beta"],
)
dsigmadp += dedp * dsigmade_fn(
p,
e,
m,
r,
eos["lambda_BL"],
eos["lambda_DY"],
eos["lambda_HB"],
eos["gamma"],
eos["alpha"],
eos["beta"],
)
A = 1.0 / (1.0 - 2.0 * m / r)
# terms for radius and mass
dpdr = -(e + p) * (m + 4.0 * jnp.pi * r * r * r * p) / r / r * A
dpdr += -2.0 * sigma / r # adding non-GR contribution
# terms for tidal deformability
C1 = 2.0 / r + A * (2.0 * m / (r * r) + 4.0 * jnp.pi * r * (p - e))
C0 = A * (
-6 / (r * r)
+ 4.0 * jnp.pi * (e + p) * (1.0 + dedp) / (1.0 - dsigmadp)
+ 4.0 * jnp.pi * (4.0 * e + 8.0 * p)
+ 16.0 * jnp.pi * sigma
) - jnp.power(2.0 * (m + 4.0 * jnp.pi * r * r * r * p) / (r * (r - 2.0 * m)), 2.0)
drdh = (e + p) / dpdr
dmdh = 4.0 * jnp.pi * r * r * e * drdh
dHdh = b * drdh
dbdh = -(C0 * H + C1 * b) * drdh
dydt = drdh, dmdh, dHdh, dbdh
return dydt
[docs]
def calc_k2(R, M, H, b):
y = R * b / H
C = M / R
num = (
(8.0 / 5.0)
* jnp.power(1 - 2 * C, 2.0)
* jnp.power(C, 5.0)
* (2 * C * (y - 1) - y + 2)
)
den = (
2
* C
* (
4 * (y + 1) * jnp.power(C, 4)
+ (6 * y - 4) * jnp.power(C, 3)
+ (26 - 22 * y) * C * C
+ 3 * (5 * y - 8) * C
- 3 * y
+ 6
)
)
den -= (
3
* jnp.power(1 - 2 * C, 2)
* (2 * C * (y - 1) - y + 2)
* jnp.log(1.0 / (1 - 2 * C))
)
return num / den
[docs]
def tov_solver(eos, pc):
r"""
Solve the modified TOV equations for a given central pressure.
This function integrates the modified TOV equations that include beyond-GR
corrections. The integration procedure is identical to the standard TOV case,
but the differential equations include additional sigma terms.
Args:
eos (dict): Extended EOS interpolation data containing:
- **p**: Pressure array [geometric units]
- **h**: Enthalpy array [geometric units]
- **e**: Energy density array [geometric units]
- **dloge_dlogp**: Logarithmic derivative array
- **alpha, beta, gamma**: Post-Newtonian parameters
- **lambda_BL, lambda_DY, lambda_HB**: Theory modification parameters
pc (float): Central pressure [geometric units].
Returns:
tuple: A tuple containing:
- **M**: Gravitational mass [geometric units]
- **R**: Circumferential radius [geometric units]
- **k2**: Second Love number for tidal deformability
Note:
The modifications affect the stellar structure but the same integration
method and boundary conditions as the standard TOV case are used.
"""
# Extract EOS interpolation arrays
ps = eos["p"]
hs = eos["h"]
es = eos["e"]
dloge_dlogps = eos["dloge_dlogp"]
# Central values and initial conditions
hc = utils.interp_in_logspace(pc, ps, hs)
ec = utils.interp_in_logspace(hc, hs, es)
dedp_c = ec / pc * jnp.interp(hc, hs, dloge_dlogps)
dhdp_c = 1.0 / (ec + pc)
dedh_c = dedp_c / dhdp_c
# Initial values using series expansion near center
dh = -1e-3 * hc
h0 = hc + dh
r0 = jnp.sqrt(3.0 * (-dh) / 2.0 / jnp.pi / (ec + 3.0 * pc))
r0 *= 1.0 - 0.25 * (ec - 3.0 * pc - 0.6 * dedh_c) * (-dh) / (ec + 3.0 * pc)
m0 = 4.0 * jnp.pi * ec * jnp.power(r0, 3.0) / 3.0
m0 *= 1.0 - 0.6 * dedh_c * (-dh) / ec
H0 = r0 * r0
b0 = 2.0 * r0
y0 = (r0, m0, H0, b0)
sol = diffeqsolve(
ODETerm(tov_ode),
Dopri5(scan_kind="bounded"),
t0=h0,
t1=0,
dt0=dh,
y0=y0,
args=eos,
saveat=SaveAt(t1=True),
stepsize_controller=PIDController(rtol=1e-5, atol=1e-6),
)
R = sol.ys[0][-1]
M = sol.ys[1][-1]
H = sol.ys[2][-1]
b = sol.ys[3][-1]
k2 = calc_k2(R, M, H, b)
return M, R, k2