Source code for CosmoFit.cosmology.models.rvm

"""
Running Vacuum Model (RVM).
"""

from __future__ import annotations

import numpy as np

from CosmoFit.typing import Array, Redshift

from CosmoFit.cosmology.core import Cosmology


[docs] class RunningVacuum(Cosmology): r""" Running Vacuum Model: a cosmological "constant" that runs with the expansion rate, Lambda(H) = c0 + 3 nu H^2 Solving the Friedmann and continuity equations together with this gives a closed form with no numerical integration: E(z)^2 = 1 + Omega_k [(1+z)^2 - 1] + (Omega_m / (1 - nu)) [(1+z)^{3(1-nu)} - 1] and ``nu = 0`` recovers curved LCDM exactly. The idea comes from quantum field theory in curved spacetime, where the vacuum energy density is a running quantity obeying a renormalization-group equation, and ``nu`` is the beta-function coefficient -- expected to be ``|nu| ~ 10^-3`` from a one-loop estimate, which is why the default bounds here are tight compared to the wide-open priors on ``w0``/``wa``. It is one of the few dark-energy models whose extra parameter has a *predicted magnitude* rather than an arbitrary one, so a fit that returns ``nu ~ 10^-3`` means something quite different from one that returns ``nu ~ 0.1``. Mechanically the model works by making matter dilute slightly differently from ``(1+z)^3`` -- the exponent is ``3(1 - nu)`` -- because vacuum and matter exchange energy. That is a different lever from any ``w(z)`` parametrization, which leaves the matter scaling alone, and it means ``nu`` is constrained by anything sensitive to the matter density's redshift evolution, growth data included. Notes ----- ``nu = 1`` is a coordinate singularity of the closed form above (the ``1/(1 - nu)`` prefactor), far outside any physical prior; :meth:`E` falls back to the ``nu -> 1`` limit there rather than dividing by zero. This is the simplest member of the RVM family. Fuller versions add an ``Hdot`` term (``Lambda = c0 + 3 nu H^2 + alpha Hdot``) or higher powers of ``H`` relevant to inflation; neither is implemented. References ---------- Sola (2013), J. Phys. Conf. Ser. 453, 012015, arXiv:1306.1527 (review). Sola, Gomez-Valent & de Cruz Perez (2017), ApJ 836, 43, arXiv:1602.02103 (cosmological constraints). """ MODEL_NAME = "RunningVacuum" MODEL_LABEL = r"Running vacuum" EXTRA_PARAMS = { "nu": { "default": 0.0, "bounds": (-0.1, 0.1), "label": r"$\nu$", }, } # --------------------------------------------------------- def _matter_term(self, z): r""" ``(Omega_m / (1 - nu)) [(1+z)^{3(1-nu)} - 1]``. """ z = np.asarray(z, dtype=float) nu = self.nu if np.isclose(nu, 1.0): # lim_{nu->1} [(1+z)^{3(1-nu)} - 1] / (1 - nu) # = 3 ln(1+z) return 3.0 * self.Omega_m * np.log1p(z) return ( self.Omega_m / (1.0 - nu) ) * ( (1.0 + z) ** (3.0 * (1.0 - nu)) - 1.0 ) # --------------------------------------------------------- def E(self, z: Redshift) -> Array: z = np.asarray(z, dtype=float) return np.sqrt( 1.0 + self.Omega_k * ((1.0 + z) ** 2 - 1.0) + self._matter_term(z) ) # --------------------------------------------------------- def dEdz(self, z: Redshift) -> Array: z = np.asarray(z, dtype=float) nu = self.nu if np.isclose(nu, 1.0): d_matter = 3.0 * self.Omega_m / (1.0 + z) else: # The 1/(1-nu) prefactor and the 3(1-nu) exponent # cancel exactly, which is why this is simply # 3 Omega_m (1+z)^{2-3nu}. d_matter = ( 3.0 * self.Omega_m * (1.0 + z) ** (2.0 - 3.0 * nu) ) return ( ( 2.0 * self.Omega_k * (1.0 + z) + d_matter ) / (2.0 * self.E(z)) ) # --------------------------------------------------------- def Omega_de(self, z: Redshift) -> Array: r""" The running vacuum density, Omega_Lambda(z) = E(z)^2 - Omega_m(z) - Omega_k(z) where the matter term is ``Omega_m (1+z)^{3(1-nu)}``, *not* ``Omega_m (1+z)^3`` -- that modified scaling is the whole content of the model, and using the LCDM one here would silently report the wrong split between the two components while leaving ``E(z)`` correct. """ z = np.asarray(z, dtype=float) matter = self.Omega_m * (1.0 + z) ** ( 3.0 * (1.0 - self.nu) ) return ( self.E(z) ** 2 - matter - self.Omega_k * (1.0 + z) ** 2 ) # --------------------------------------------------------- def Omega_matter(self, z: Redshift) -> Array: r""" ``Omega_m (1+z)^{3(1-nu)}`` -- matter dilutes more slowly than in LCDM because the running vacuum is feeding it. Overriding this is what makes ``nu`` visible to the linear growth equation; without it the growth source term would use LCDM's matter scaling while ``E(z)`` used the RVM one, which is internally inconsistent rather than merely approximate. """ z = np.asarray(z, dtype=float) return self.Omega_m * (1.0 + z) ** ( 3.0 * (1.0 - self.nu) )