Source code for cosmofit.cosmology.models.pede

"""
Phenomenologically Emergent Dark Energy (PEDE).
"""

from __future__ import annotations

import numpy as np

from cosmofit.typing import Array, Redshift

from cosmofit.cosmology.numerics.powers import cube

from cosmofit.cosmology.core import Cosmology


#: ln(10), the conversion between the log10 that PEDE's functional
#: form is written with and the natural log its derivative needs.
_LN10 = np.log(10.0)


[docs] class PEDE(Cosmology): r""" Phenomenologically Emergent Dark Energy (Li & Shafieloo 2019). Omega_de(z) = Omega_de0 [1 - tanh(log10(1 + z))] Dark energy that is *absent* at high redshift and "emerges" toward the present: the bracket goes to 0 as z grows and to 1 at z = 0, so the model has no dark-energy density during matter domination at all. What makes it worth having in a library that already carries six dark-energy parametrizations is that it has **no free parameter for dark energy whatsoever**. CPL, JBP and BA each buy their extra flexibility with two parameters (``w0``, ``wa``); PEDE has the same number of free parameters as LCDM -- ``H0`` and ``Omega_m`` -- and a completely different expansion history. That makes the model comparison honest in a way a nested-model comparison is not: AIC/BIC penalties are identical, so a difference in chi2 is a difference in fit, full stop. Its other claim is on the Hubble tension. The effective equation of state, w(z) = -1 - (1 / (3 ln 10)) [1 + tanh(log10(1 + z))] is phantom at every redshift and equals -1.145 today, which raises the inferred ``H0`` for a CMB-anchored fit relative to LCDM. Whether that survives the full data set is exactly the kind of question this library exists to ask, not something to assert here. Notes ----- ``Omega_de0 = 1 - Omega_m - Omega_k`` as usual, so ``E(z=0) = 1`` holds identically, including for curved cases. References ---------- Li & Shafieloo (2019), "A Simple Phenomenological Emergent Dark Energy Model can Resolve the Hubble Tension", ApJ 883, L3, arXiv:1906.08275. """ MODEL_NAME = "PEDE" # --------------------------------------------------------- @staticmethod def _u(z): """ ``log10(1 + z)``, the variable PEDE's tanh is written in. """ return np.log10(1.0 + np.asarray(z, dtype=float)) # --------------------------------------------------------- def Omega_de(self, z: Redshift) -> Array: return self.Omega_de0 * ( 1.0 - np.tanh(self._u(z)) ) # --------------------------------------------------------- def E(self, z: Redshift) -> Array: z = np.asarray(z, dtype=float) return np.sqrt( self.Omega_m * cube(1.0 + z) + self.Omega_k * (1.0 + z) ** 2 + self.Omega_de(z) ) # --------------------------------------------------------- def dEdz(self, z: Redshift) -> Array: z = np.asarray(z, dtype=float) u = self._u(z) # d/dz [Omega_de0 (1 - tanh u)] with u = ln(1+z)/ln(10), # so du/dz = 1 / ((1+z) ln 10) and d(tanh u)/du = sech^2 u. d_omega_de = -( self.Omega_de0 / (np.cosh(u) ** 2) / ((1.0 + z) * _LN10) ) return ( ( 3.0 * self.Omega_m * (1.0 + z) ** 2 + 2.0 * self.Omega_k * (1.0 + z) + d_omega_de ) / (2.0 * self.E(z)) ) # --------------------------------------------------------- def w(self, z: Redshift) -> Array: r""" Effective dark-energy equation of state, w(z) = -1 - [1 + tanh(log10(1+z))] / (3 ln 10) Obtained from the continuity equation, ``w = (1/3) dln rho_de / dln(1+z) - 1``, using ``sech^2 u / (1 - tanh u) = 1 + tanh u``. Matches Li & Shafieloo's Eq. (3). Phantom (``w < -1``) at every redshift: ``-1.145`` today, tending to ``-1`` in the far future (``z -> -1``, where ``tanh -> -1``) and to ``-1 - 2/(3 ln 10) = -1.2895`` deep in the past. This is what lets the model be handed to a Boltzmann code (see :class:`~cosmology.boltzmann.CAMBBackend`), which needs an equation of state rather than only an ``E(z)``. """ return -1.0 - ( 1.0 + np.tanh(self._u(z)) ) / (3.0 * _LN10)