"""Inflationary potentials."""
import sys
from abc import ABC, abstractmethod
from functools import cached_property
from warnings import warn
import numpy as np
from scipy.special import lambertw
from scipy.interpolate import interp1d
from scipy.optimize import root_scalar
from primpy.units import pi
from primpy.exceptionhandling import PrimpyError, PrimpyWarning
EPS = sys.float_info.epsilon
[docs]
class InflationaryPotential(ABC):
"""Base class for inflaton potential and derivatives."""
@abstractmethod
def __init__(self, **pot_kwargs):
self.Lambda = pot_kwargs.pop('Lambda', 1)
if 'A_s' in pot_kwargs:
if self.Lambda != 1:
warn(PrimpyWarning("When specifying `A_s` alongside `Lambda`, the latter input "
"will be ignored and instead inferred from `A_s` using the "
"slow-roll approximation."))
if 'N_star' not in pot_kwargs and 'phi_star' not in pot_kwargs:
raise PrimpyError("When specifying `A_s`, need to also specify either `N_star` or "
"`phi_star`.")
A_s = pot_kwargs.pop('A_s')
N_star = pot_kwargs.pop('N_star', None)
phi_star = pot_kwargs.pop('phi_star', None)
self.Lambda, _, _ = self.sr_As2Lambda(A_s=A_s, N_star=N_star, phi_star=phi_star)
for key in pot_kwargs:
raise Exception("%s does not accept kwarg %s" % (self.name, key))
@property
@abstractmethod
def tag(self):
"""3 letter tag identifying the type of inflationary potential."""
@property
@abstractmethod
def name(self):
"""Name of the inflationary potential."""
@property
@abstractmethod
def tex(self):
"""Tex string useful for labelling the inflationary potential."""
@property
@abstractmethod
def perturbation_ic(self):
"""Set of well scaling initial conditions for perturbation module."""
[docs]
@abstractmethod
def V(self, phi):
"""Inflationary potential `V(phi)`.
Parameters
----------
phi : float or np.ndarray
Inflaton field `phi`.
Returns
-------
V : float or np.ndarray
Inflationary potential `V(phi)`.
"""
[docs]
@abstractmethod
def dV(self, phi):
"""First derivative `V'(phi)` with respect to inflaton `phi`.
Parameters
----------
phi : float or np.ndarray
Inflaton field `phi`.
Returns
-------
dV : float or np.ndarray
1st derivative of inflationary potential: `V'(phi)`.
"""
[docs]
@abstractmethod
def d2V(self, phi):
"""Second derivative `V''(phi)` with respect to inflaton `phi`.
Parameters
----------
phi : float or np.ndarray
Inflaton field `phi`.
Returns
-------
d2V : float or np.ndarray
2nd derivative of inflationary potential: `V''(phi)`.
"""
[docs]
@abstractmethod
def d3V(self, phi):
"""Third derivative `V'''(phi)` with respect to inflaton `phi`.
Parameters
----------
phi : float or np.ndarray
Inflaton field `phi`.
Returns
-------
d3V : float or np.ndarray
3rd derivative of inflationary potential: `V'''(phi)`.
"""
[docs]
@abstractmethod
def d4V(self, phi):
"""Fourth derivative `V''''(phi)` with respect to inflaton `phi`.
Parameters
----------
phi : float or np.ndarray
Inflaton field `phi`.
Returns
-------
d4V : float or np.ndarray
4th derivative of inflationary potential: `V''''(phi)`.
"""
[docs]
@abstractmethod
def inv_V(self, V):
"""Inverse function `phi(V)` with respect to potential `V`.
Parameters
----------
V : float or np.ndarray
Inflationary potential `V`.
Returns
-------
phi : float or np.ndarray
Inflaton field `phi`.
"""
# TODO:
# @abstractmethod
# def sr_n_s(self):
# """Slow-roll approximation for the spectral index."""
# TODO:
# @abstractmethod
# def sr_n_s(self):
# """Slow-roll approximation for the tensor-to-scalar ratio."""
[docs]
@abstractmethod
def get_epsilon_1V(self, phi):
"""Get 1st potential slow-roll parameter."""
[docs]
@abstractmethod
def get_epsilon_2V(self, phi):
"""Get 2nd potential slow-roll parameter."""
[docs]
@abstractmethod
def get_epsilon_3V(self, phi):
"""Get 3rd potential slow-roll parameter."""
[docs]
@abstractmethod
def get_epsilon_4V(self, phi):
"""Get 4th potential slow-roll parameter."""
[docs]
def get_epsilon_1(self, phi):
"""Approximation of 1st Hubble flow parameter with potential slow-roll parameters."""
e1V = self.get_epsilon_1V(phi=phi)
e2V = self.get_epsilon_2V(phi=phi)
e3V = self.get_epsilon_3V(phi=phi)
return e1V - e1V * e2V / 3 - e1V**2 * e2V / 9 + 5/36 * e1V * e2V**2 + e1V * e2V * e3V / 9
[docs]
def get_epsilon_2(self, phi):
"""Approximation of 2nd Hubble flow parameter with potential slow-roll parameters."""
e1V = self.get_epsilon_1V(phi=phi)
e2V = self.get_epsilon_2V(phi=phi)
e3V = self.get_epsilon_3V(phi=phi)
e4V = self.get_epsilon_4V(phi=phi)
return (e2V - 1/6*e2V**2 - 1/3*e2V*e3V - 1/6*e1V*e2V**2 + 1/18*e2V**3 - 1/9*e1V*e2V*e3V
+ 5/18*e2V**2*e3V + 1/9*e2V*e3V**2 + 1/9*e2V*e3V*e4V)
[docs]
def get_epsilon_3(self, phi):
"""Approximation of 3rd Hubble flow parameter with potential slow-roll parameters."""
e1V = self.get_epsilon_1V(phi=phi)
e2V = self.get_epsilon_2V(phi=phi)
e3V = self.get_epsilon_3V(phi=phi)
e4V = self.get_epsilon_4V(phi=phi)
e5V = 0
return (e3V - 1/3*e2V*e3V - 1/3*e3V*e4V - 1/6*e1V*e2V**2 - 1/3*e1V*e2V*e3V
+ 1/6*e2V**2*e3V + 5/18*e2V*e3V**2 - 1/9*e1V*e3V*e4V + 5/18*e2V*e3V*e4V
+ 1/9*e3V**2*e4V + 1/9*e3V*e4V**2 + 1/9*e3V*e4V*e5V)
[docs]
def get_epsilon_4(self, phi):
"""Approximation of 4th Hubble flow parameter with potential slow-roll parameters."""
e2V = self.get_epsilon_2V(phi=phi)
e3V = self.get_epsilon_3V(phi=phi)
e4V = self.get_epsilon_4V(phi=phi)
e5V = 0
return e4V - 1/3*e2V*e3V - 1/6*e2V*e4V - 1/3*e4V*e5V
@abstractmethod
@cached_property
def phi_end(self):
"""Inflaton field value at the end of inflation.
Value inferred from `epsilon_1V = 1` assuming a positive `phi_end`.
"""
[docs]
@abstractmethod
def sr_phi2N(self, phi):
"""Convert from inflaton field `phi` to e-folds `N` assuming slow-roll approximation."""
[docs]
@abstractmethod
def sr_N2phi(self, N):
"""Convert from inflaton field `phi` to e-folds `N` assuming slow-roll approximation."""
[docs]
def sr_As2Lambda(self, A_s, phi_star, N_star, **pot_kwargs):
"""Get potential amplitude `Lambda` from PPS amplitude `A_s` using slow-roll approximation.
Find the inflaton amplitude `Lambda` (4th root of potential amplitude)
that produces the desired amplitude `A_s` of the primordial power
spectrum using the slow-roll approximation.
Note that either `phi_star` or `N_star` have to be passed as `None`, and will be
calculated subsequently from the respective other.
Parameters
----------
A_s : float
Target amplitude `A_s` of scalar primordial power spectrum
(at the pivot scale `k=0.05 Mpc^{-1}`).
phi_star : float
Inflaton field value at the time of horizon crossing of the pivot scale.
N_star : float
Number of e-folds of inflation remaining at the time of horizon crossing of
the pivot scale.
Returns
-------
Lambda : float
Potential amplitude parameter `Lambda` corresponding approximately to `A_s`.
phi_star : float
Estimated inflaton field value at the time of horizon crossing of the pivot scale,
inferred from `N_star`. (Exact if passed as input.)
N_star : float
Estimated number of e-folds of inflation remaining at the time of horizon crossing of
the pivot scale, inferred from `phi_star`. (Exact if passed as input.)
"""
if N_star is None:
N_star = self.sr_phi2N(phi_star)
elif phi_star is None:
phi_star = self.sr_N2phi(N_star)
else:
raise Exception("Need to specify either N_star or phi_star. "
"The respective other should be None.")
e1 = self.get_epsilon_1V(phi=phi_star)
H2 = self.V(phi=phi_star)/3 # slow-roll approximation at leading order
A_s0 = H2 / (8*pi**2*e1)
Lambda = self.Lambda * (A_s / A_s0)**(1/4)
return Lambda, phi_star, N_star
[docs]
class MonomialPotential(InflationaryPotential):
"""Monomial potential: `V(phi) = Lambda**4 * phi**p`."""
tag = 'mnp'
name = 'MonomialPotential'
tex = r'$\phi^p$'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
self.p = pot_kwargs.pop('p')
super().__init__(**pot_kwargs)
[docs]
def V(self, phi): # noqa: D102
return self.Lambda**4 * np.abs(phi)**self.p
[docs]
def dV(self, phi): # noqa: D102
return self.Lambda**4 * self.p * np.abs(phi)**(self.p - 1) * np.sign(phi)
[docs]
def d2V(self, phi): # noqa: D102
return self.Lambda**4 * self.p * (self.p - 1) * np.abs(phi)**self.p / phi**2
[docs]
def d3V(self, phi): # noqa: D102
p = self.p
return self.Lambda**4 * p * (p**2 - 3*p + 2) * np.abs(phi)**(p-3) * np.sign(phi)
[docs]
def d4V(self, phi): # noqa: D102
p = self.p
return self.Lambda**4 * p * (p**3 - 6*p**2 + 11*p - 6) * np.abs(phi)**(p-4)
[docs]
def inv_V(self, V): # noqa: D102
return (V / self.Lambda**4)**(1/self.p)
[docs]
def get_epsilon_1V(self, phi): # noqa: D102
return self.p**2 / (2 * phi**2)
[docs]
def get_epsilon_2V(self, phi): # noqa: D102
return 2 * self.p / phi**2
[docs]
def get_epsilon_3V(self, phi): # noqa: D102
return 2 * self.p / phi**2
[docs]
def get_epsilon_4V(self, phi): # noqa: D102
return 2 * self.p / phi**2
@cached_property
def phi_end(self): # noqa: D102
return np.sqrt(2) * self.p / 2
[docs]
def sr_phi2N(self, phi): # noqa: D102
return -self.p / 4 + phi**2 / (2 * self.p)
[docs]
def sr_N2phi(self, N): # noqa: D102
return np.sqrt(2 * self.p) * np.sqrt(4 * N + self.p) / 2
[docs]
@staticmethod
def sr_Nstar2ns(N_star, **pot_kwargs):
"""Slow-roll approximation for inferring `n_s` from `N_star`."""
p = pot_kwargs.pop('p')
return 1 - p / (2 * N_star) - 1 / N_star
[docs]
@staticmethod
def sr_ns2Nstar(n_s, **pot_kwargs):
"""Slow-roll approximation for inferring `N_star` from `n_s`."""
p = pot_kwargs.pop('p')
return (2 + p) / (2 * (1 - n_s))
[docs]
@staticmethod
def sr_Nstar2r(N_star, **pot_kwargs):
"""Slow-roll approximation for inferring `r` from `N_star`."""
p = pot_kwargs.pop('p')
return 16 * p / (4 * N_star + p)
[docs]
@staticmethod
def sr_r2Nstar(r, **pot_kwargs):
"""Slow-roll approximation for inferring `N_star` from `r`."""
p = pot_kwargs.pop('p')
return p * (16 - r) / (4 * r)
[docs]
class LinearPotential(MonomialPotential):
"""Linear potential: `V(phi) = Lambda**4 * phi`."""
tag = 'mn1'
name = 'LinearPotential'
tex = r'$\phi^1$'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
super().__init__(p=1, **pot_kwargs)
[docs]
@staticmethod
def sr_Nstar2ns(N_star, **pot_params): # noqa: D102
return MonomialPotential.sr_Nstar2ns(N_star=N_star, p=1)
[docs]
@staticmethod
def sr_ns2Nstar(n_s, **pot_params): # noqa: D102
return MonomialPotential.sr_ns2Nstar(n_s=n_s, p=1)
[docs]
@staticmethod
def sr_Nstar2r(N_star, **pot_params): # noqa: D102
return MonomialPotential.sr_Nstar2r(N_star=N_star, p=1)
[docs]
@staticmethod
def sr_r2Nstar(r, **pot_params): # noqa: D102
return MonomialPotential.sr_r2Nstar(r=r, p=1)
[docs]
class QuadraticPotential(MonomialPotential):
"""Quadratic potential: `V(phi) = Lambda**4 * phi**2`."""
tag = 'mn2'
name = 'QuadraticPotential'
tex = r'$\phi^2$'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
if 'mass' in pot_kwargs:
raise ValueError("'mass' was dropped use 'Lambda' instead: Lambda**4=mass**2")
super().__init__(p=2, **pot_kwargs)
[docs]
def V(self, phi): # noqa: D102
return self.Lambda**4 * phi**2
[docs]
def dV(self, phi): # noqa: D102
return 2 * self.Lambda**4 * phi
[docs]
def d2V(self, phi): # noqa: D102
return 2 * self.Lambda**4
[docs]
def d3V(self, phi): # noqa: D102
return 0
[docs]
def d4V(self, phi): # noqa: D102
return 0
[docs]
def inv_V(self, V): # noqa: D102
return np.sqrt(V) / self.Lambda**2
[docs]
@staticmethod
def sr_Nstar2ns(N_star, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_Nstar2ns(N_star=N_star, p=2)
[docs]
@staticmethod
def sr_ns2Nstar(n_s, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_ns2Nstar(n_s=n_s, p=2)
[docs]
@staticmethod
def sr_Nstar2r(N_star, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_Nstar2r(N_star=N_star, p=2)
[docs]
@staticmethod
def sr_r2Nstar(r, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_r2Nstar(r=r, p=2)
[docs]
class CubicPotential(MonomialPotential):
"""Linear potential: `V(phi) = Lambda**4 * phi`."""
tag = 'mn3'
name = 'CubicPotential'
tex = r'$\phi^3$'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
super().__init__(p=3, **pot_kwargs)
[docs]
@staticmethod
def sr_Nstar2ns(N_star, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_Nstar2ns(N_star=N_star, p=3)
[docs]
@staticmethod
def sr_ns2Nstar(n_s, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_ns2Nstar(n_s=n_s, p=3)
[docs]
@staticmethod
def sr_Nstar2r(N_star, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_Nstar2r(N_star=N_star, p=3)
[docs]
@staticmethod
def sr_r2Nstar(r, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_r2Nstar(r=r, p=3)
[docs]
class QuarticPotential(MonomialPotential):
"""Linear potential: `V(phi) = Lambda**4 * phi`."""
tag = 'mn4'
name = 'QuarticPotential'
tex = r'$\phi^4$'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
super().__init__(p=4, **pot_kwargs)
[docs]
@staticmethod
def sr_Nstar2ns(N_star, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_Nstar2ns(N_star=N_star, p=4)
[docs]
@staticmethod
def sr_ns2Nstar(n_s, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_ns2Nstar(n_s=n_s, p=4)
[docs]
@staticmethod
def sr_Nstar2r(N_star, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_Nstar2r(N_star=N_star, p=4)
[docs]
@staticmethod
def sr_r2Nstar(r, **pot_kwargs): # noqa: D102
return MonomialPotential.sr_r2Nstar(r=r, p=4)
[docs]
class StarobinskyPotential(InflationaryPotential):
"""Starobinsky potential: `V(phi) = Lambda**4 * (1 - exp(-sqrt(2/3) * phi))**2`."""
tag = 'stb'
name = 'StarobinskyPotential'
tex = r'Starobinsky'
gamma = np.sqrt(2 / 3)
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
super().__init__(**pot_kwargs)
[docs]
def V(self, phi): # noqa: D102
return self.Lambda**4 * (1 - np.exp(-StarobinskyPotential.gamma * phi))**2
[docs]
def dV(self, phi): # noqa: D102
gamma = StarobinskyPotential.gamma
return self.Lambda**4 * 2 * gamma * np.exp(-2 * gamma * phi) * (np.exp(gamma * phi) - 1)
[docs]
def d2V(self, phi): # noqa: D102
gamma = StarobinskyPotential.gamma
return self.Lambda**4 * 2 * gamma**2 * np.exp(-2 * gamma * phi) * (2 - np.exp(gamma * phi))
[docs]
def d3V(self, phi): # noqa: D102
gamma = StarobinskyPotential.gamma
return self.Lambda**4 * 2 * gamma**3 * np.exp(-2 * gamma * phi) * (np.exp(gamma * phi) - 4)
[docs]
def d4V(self, phi): # noqa: D102
gamma = StarobinskyPotential.gamma
return 2 * self.Lambda**4 * gamma**4 * (8 - np.exp(gamma * phi)) * np.exp(-2 * gamma * phi)
[docs]
def inv_V(self, V): # noqa: D102
return -np.log(1 - np.sqrt(V) / self.Lambda**2) / StarobinskyPotential.gamma
[docs]
def get_epsilon_1V(self, phi): # noqa: D102
gamma = StarobinskyPotential.gamma
return 2 * gamma**2 / (1 - np.exp(gamma * phi))**2
[docs]
def get_epsilon_2V(self, phi): # noqa: D102
gamma = StarobinskyPotential.gamma
return gamma**2 / np.sinh(gamma * phi / 2)**2
[docs]
def get_epsilon_3V(self, phi): # noqa: D102
gamma = StarobinskyPotential.gamma
return 2 * gamma**2 * (np.exp(gamma * phi) + 1) / (np.exp(gamma * phi) - 1)**2
[docs]
def get_epsilon_4V(self, phi): # noqa: D102
gamma = StarobinskyPotential.gamma
return gamma**2 * (np.exp(gamma*phi)+3) / (2*(np.exp(gamma*phi)+1)*np.sinh(gamma*phi/2)**2)
@cached_property
def phi_end(self): # noqa: D102
gamma = StarobinskyPotential.gamma
return np.log(np.sqrt(2) * gamma + 1) / gamma
[docs]
def sr_phi2N(self, phi): # noqa: D102
g = StarobinskyPotential.gamma
phi_end = self.phi_end
return (-phi + phi_end) / (2*g) + (np.exp(g * phi) - np.exp(g * phi_end)) / (2*g**2)
[docs]
def sr_N2phi(self, N): # noqa: D102
g = StarobinskyPotential.gamma
phi_end = self.phi_end
return np.real_if_close(
-2*N*g + phi_end - np.exp(g*phi_end)/g
- lambertw(-np.exp(-2*N*g**2)*np.exp(g*phi_end)*np.exp(-np.exp(g*phi_end)), -1) / g
)
[docs]
@staticmethod
def sr_Nstar2ns(N_star):
"""Slow-roll approximation for inferring `n_s` from `N_star`."""
gamma = StarobinskyPotential.gamma
num = 2 * N_star * gamma**2 + np.sqrt(2) * gamma + 2
den = N_star * gamma * (N_star * gamma + np.sqrt(2))
return 1 - num / den
[docs]
@staticmethod
def sr_ns2Nstar(n_s):
"""Slow-roll approximation for inferring `N_star` from `n_s`."""
gamma = StarobinskyPotential.gamma
num = 2*gamma - np.sqrt(2) * (1-n_s) + np.sqrt(2*(1-n_s)**2 + 8*(1-n_s) + 4*gamma**2)
den = 2 * gamma * (1-n_s)
return num / den
[docs]
@staticmethod
def sr_Nstar2r(N_star):
"""Slow-roll approximation for inferring `r` from `N_star`."""
gamma = StarobinskyPotential.gamma
return 32 / (2*N_star*gamma + np.sqrt(2))**2
[docs]
@staticmethod
def sr_r2Nstar(r):
"""Slow-roll approximation for inferring `N_star` from `r`."""
gamma = StarobinskyPotential.gamma
return np.sqrt(2) * (4 - np.sqrt(r)) / (2 * gamma * np.sqrt(r))
[docs]
@staticmethod
def phi2efolds(phi):
"""Get e-folds `N` from inflaton `phi`.
Find the number of e-folds `N` till end of inflation from inflaton `phi`
using the slow-roll approximation.
Parameters
----------
phi : float or np.ndarray
Inflaton field `phi`.
Returns
-------
N : float or np.ndarray
Number of e-folds `N` until end of inflation.
"""
warn(DeprecationWarning("This method will be removed in future versions, it is superseded "
"by `sr_phi2N`."))
gamma = StarobinskyPotential.gamma
phi_end = np.log(1 + np.sqrt(2) * gamma) / gamma # =~ 0.9402
return (np.exp(gamma * phi) - np.exp(gamma * phi_end)
- gamma * (phi - phi_end)) / (2 * gamma**2)
[docs]
class NaturalPotential(InflationaryPotential):
"""Natural inflation potential: `V(phi) = Lambda**4 * (1 - cos(pi*phi/phi0))`.
Natural inflation with phi0 = pi * f where f is the standard parameter
used in definitions of natural inflation.
Here we use phi0 the position of the maximum and we have a minus in our
definition such that the minimum is at zero instead of the maximum.
"""
tag = 'nat'
name = 'NaturalPotential'
tex = r'Natural'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
self.phi0 = pot_kwargs.pop('phi0')
super().__init__(**pot_kwargs)
[docs]
def V(self, phi): # noqa: D102
return self.Lambda**4 / 2 * (1 - np.cos(pi * phi / self.phi0))
[docs]
def dV(self, phi): # noqa: D102
return self.Lambda**4 / 2 * np.sin(pi * phi / self.phi0) * pi / self.phi0
[docs]
def d2V(self, phi): # noqa: D102
return self.Lambda**4 / 2 * np.cos(pi * phi / self.phi0) * (pi / self.phi0)**2
[docs]
def d3V(self, phi): # noqa: D102
return -self.Lambda**4 / 2 * np.sin(pi * phi / self.phi0) * (pi / self.phi0)**3
[docs]
def d4V(self, phi): # noqa: D102
return -self.Lambda**4 / 2 * np.cos(pi * phi / self.phi0) * (pi / self.phi0)**4
[docs]
def inv_V(self, V): # noqa: D102
return np.arccos(1 - 2 * V / self.Lambda**4) * self.phi0 / pi
[docs]
def get_epsilon_1V(self, phi): # noqa: D102
phi0 = self.phi0
return pi**2 * np.sin(pi*phi/phi0)**2 / (2 * phi0**2 * (np.cos(pi*phi/phi0) - 1)**2)
[docs]
def get_epsilon_2V(self, phi): # noqa: D102
phi0 = self.phi0
return -2 * pi**2 / (phi0**2 * (np.cos(pi*phi/phi0) - 1))
[docs]
def get_epsilon_3V(self, phi): # noqa: D102
phi0 = self.phi0
return pi**2 * np.sin(pi*phi/phi0)**2 / (phi0**2 * (np.cos(pi*phi/phi0) - 1)**2)
[docs]
def get_epsilon_4V(self, phi): # noqa: D102
phi0 = self.phi0
return -2 * pi**2 / (phi0**2 * (np.cos(pi*phi/phi0) - 1))
@cached_property
def phi_end(self): # noqa: D102
return 2 * self.phi0 * np.arctan(np.sqrt(2) * pi / (2 * self.phi0)) / pi
[docs]
def sr_phi2N(self, phi): # noqa: D102
phi0 = self.phi0
phi_end = self.phi_end
return phi0**2/pi**2 * (+ np.log(np.tan(pi*phi/(2*phi0))**2 + 1)
- np.log(np.tan(pi*phi_end/(2*phi0))**2 + 1))
[docs]
def sr_N2phi(self, N): # noqa: D102
phi0 = self.phi0
phi_end = self.phi_end
return 2*phi0/pi * np.arctan(np.sqrt((2*np.exp(pi**2*N/phi0**2)-np.cos(pi*phi_end/phi0)-1)
/ (np.cos(pi*phi_end/phi0) + 1)))
[docs]
@staticmethod
def sr_Nstar2ns(N_star, **pot_kwargs):
"""Slow-roll approximation for the spectral index `n_s`."""
phi0 = pot_kwargs.pop('phi0')
f = phi0 / pi
num = (2 * f**2 + (2 * f**2 + 1) * np.exp(N_star / f**2))
den = (f**2 * (2 * f**2 + 1) * (np.exp(N_star / f**2) - 1))
return 1 - num / den
[docs]
@staticmethod
def sr_ns2Nstar(n_s, **pot_kwargs):
"""Slow-roll approximation for inferring `N_star` from `n_s`."""
phi0 = pot_kwargs.pop('phi0')
if phi0 < pi / np.sqrt(1-n_s):
raise PrimpyError(f"Need phi0 > pi / np.sqrt(1-n_s) for Natural inflation, but "
f"phi0={phi0}, n_s={n_s}, and pi/sqrt(1-ns)={pi/np.sqrt(1-n_s)}.")
f = phi0 / pi
return f**2 * np.log(f**2 * (2*f**2*(1-n_s)+(1-n_s)+2) / ((2*f**2+1) * (f**2*(1-n_s)-1)))
[docs]
@staticmethod
def sr_Nstar2r(N_star, **pot_kwargs):
"""Slow-roll approximation for the tensor-to-scalar ratio `r`."""
phi0 = pot_kwargs.pop('phi0')
f = phi0 / pi
return 16 / (-2 * f**2 + (2 * f**2 + 1) * np.exp(N_star / f**2))
[docs]
@staticmethod
def sr_r2Nstar(r, **pot_kwargs):
"""Slow-roll approximation for inferring `N_star` from `r`."""
phi0 = pot_kwargs.pop('phi0')
f = phi0 / pi
return f**2 * np.log((2 * f**2 * r + 16) / (r * (2 * f**2 + 1)))
[docs]
@staticmethod
def phi2efolds(phi, phi0):
"""Get e-folds `N` from inflaton `phi`.
Find the number of e-folds `N` till end of inflation from inflaton `phi`
using the slow-roll approximation.
Parameters
----------
phi : float or np.ndarray
Inflaton field `phi`.
phi0 : float
Inflaton distance between local maximum and minima.
Returns
-------
N : float or np.ndarray
Number of e-folds `N` until end of inflation.
"""
warn(DeprecationWarning("This method will be removed in future versions, it is superseded "
"by `sr_phi2N`."))
assert np.all(phi < phi0)
f = phi0 / pi
return -f**2 * (np.log(1 + 1 / (2 * f**2)) + 2 * np.log(np.cos(phi / (2 * f))))
[docs]
class DoubleWellPotential(InflationaryPotential):
"""Double-Well potential: `V(phi) = Lambda**4 * (1 - (phi/phi0)**p)**2`.
Double-Well shifted such that left minimum is at zero: phi -> phi-phi0
"""
tag = 'dwp'
name = 'DoubleWellPotential'
tex = r'Double-Well (p)'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
self.phi0 = pot_kwargs.pop('phi0')
self.p = pot_kwargs.pop('p')
super().__init__(**pot_kwargs)
[docs]
def V(self, phi): # noqa: D102
phi_0_phi = 1 - phi / self.phi0
return self.Lambda**4 * (1 - phi_0_phi**self.p)**2
[docs]
def dV(self, phi): # noqa: D102
p = self.p
phi_0_phi = 1 - phi / self.phi0
pre = self.Lambda**4 * 2 * p * phi_0_phi**(p-1) / self.phi0
return pre * (1 - phi_0_phi**p)
[docs]
def d2V(self, phi): # noqa: D102
p = self.p
phi_0_phi = 1 - phi / self.phi0
pre = self.Lambda**4 * 2 * p * phi_0_phi**(p-2) / self.phi0**2
return pre * (-p + phi_0_phi**p * (2*p-1) + 1)
[docs]
def d3V(self, phi): # noqa: D102
p = self.p
phi_0_phi = 1 - phi / self.phi0
pre = self.Lambda**4 * 2 * p * phi_0_phi**(p-3) / self.phi0**3
return pre * (p**2 - 3*p + phi_0_phi**p * (-4*p**2 + 6*p - 2) + 2)
[docs]
def d4V(self, phi): # noqa: D102
p = self.p
phi_0_phi = 1 - phi / self.phi0
pre = self.Lambda**4 * 2 * p * phi_0_phi**(p-4) / self.phi0**4
return pre * (-p**3 + 6*p**2 - 11*p + phi_0_phi**p * (8*p**3-24*p**2+22*p-6) + 6)
[docs]
def inv_V(self, V): # noqa: D102
return self.phi0 - self.phi0 * (1 - np.sqrt(V) / self.Lambda**2)**(1/self.p)
[docs]
def get_epsilon_1V(self, phi): # noqa: D102
p = self.p
phi_0_phi = 1 - phi / self.phi0
return 2 * p**2 * phi_0_phi**(2*p-2) / (self.phi0**2 * (phi_0_phi**p - 1)**2)
[docs]
def get_epsilon_2V(self, phi): # noqa: D102
p = self.p
phi_0_phi = 1 - phi / self.phi0
return (4 * p * phi_0_phi**(p - 2) * (p + phi_0_phi**p - 1)
/ (self.phi0**2 * (phi_0_phi**p - 1)**2))
[docs]
def get_epsilon_3V(self, phi): # noqa: D102
p = self.p
phi_0_phi = 1 - phi / self.phi0
return (2*p*phi_0_phi**(p-2) * (p**2-3*p+2*phi_0_phi**(2*p)+phi_0_phi**p*(p**2+3*p-4)+2)
/ (self.phi0**2 * (phi_0_phi**p - 1)**2 * (p + phi_0_phi**p - 1)))
[docs]
def get_epsilon_4V(self, phi): # noqa: D102
p = self.p
phi_0_phi = 1 - phi / self.phi0
num = 2 * p * phi_0_phi**(p-2) * (p**4 - 6*p**3 + 13*p**2 - 12*p
+ 4*phi_0_phi**(4*p)
+ phi_0_phi**(3*p) * (p**3 + 3*p**2 + 12*p - 16)
+ phi_0_phi**(2*p) * (5*p**3 + 7*p**2 - 36*p + 24)
+ phi_0_phi**p * (3*p**4 - 23*p**2 + 36*p - 16) + 4)
den = self.phi0**2 * (p**3 - 4 * p**2 + 5 * p
+ 2 * phi_0_phi**(5*p)
+ phi_0_phi**(4*p) * (p**2 + 5*p - 10)
+ phi_0_phi**(3*p) * (p**3 + p**2 - 20*p + 20)
+ phi_0_phi**(2*p) * (-p**3 - 9*p**2 + 30*p - 20)
+ phi_0_phi**p * (-p**3 + 11*p**2 - 20*p + 10) - 2)
return num / den
@cached_property
def phi_end(self): # noqa: D102
def inflation_end(phi):
return self.get_epsilon_1V(phi=phi) - 1
output = root_scalar(inflation_end, bracket=(EPS * self.phi0, self.phi0))
return output.root
[docs]
def sr_phi2N(self, phi): # noqa: D102
p = self.p
phi0 = self.phi0
phi_e = self.phi_end
if p == 2:
return (phi0**2 * (-np.log(phi0 - phi) + np.log(phi0 - phi_e)) / 4
+ (phi0 - phi)**2 / 8 - (phi0 - phi_e)**2 / 8)
else:
return (+ phi0**p * (phi0 - phi)**(2 - p) / (2 * (p - 2))
- phi0**p * (phi0 - phi_e)**(2 - p) / (2 * (p - 2))
+ (phi0 - phi)**2 / 4
- (phi0 - phi_e)**2 / 4) / p
[docs]
def sr_N2phi(self, N): # noqa: D102
phi0 = self.phi0
phis = np.linspace(0, phi0, 100001)[1:-1]
Ns = self.sr_phi2N(phis)
N2phi = interp1d(Ns, phis)
return N2phi(N)
[docs]
class DoubleWell2Potential(DoubleWellPotential):
"""Quadratic Double-Well potential: `V(phi) = Lambda**4 * (1 - (phi/phi0)**2)**2`.
Double-Well shifted such that left minimum is at zero: phi -> phi-phi0
"""
tag = 'dw2'
name = 'DoubleWell2Potential'
tex = r'Double-Well (quadratic)'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
super().__init__(p=2, **pot_kwargs)
@cached_property
def phi_end(self): # noqa: D102
phi0 = self.phi0
phi_0_phi_end = np.sqrt(phi0**2 - 2 * np.sqrt(2) * np.sqrt(phi0**2 + 2) + 4) / phi0
return phi0 * (1 - phi_0_phi_end)
[docs]
@staticmethod
def phi2efolds(phi_shifted, phi0):
"""Get e-folds `N` from inflaton `phi`.
Find the number of e-folds `N` till end of inflation from inflaton `phi`
using the slow-roll approximation.
Parameters
----------
phi_shifted : float or np.ndarray
Inflaton field `phi` shifted by phi0 such that left potential
minimum is at zero.
phi0 : float
Inflaton distance between local maximum and minima.
Returns
-------
N : float or np.ndarray
Number of e-folds `N` until end of inflation.
"""
warn(DeprecationWarning("This method will be removed in future versions, it is superseded "
"by `sr_phi2N`."))
assert np.all(phi_shifted < phi0)
phi2 = (phi_shifted - phi0)**2
phi_end2 = 4 + phi0**2 - 2 * np.sqrt(4 + 2 * phi0**2)
return (phi2 - phi_end2 - phi0**2 * np.log(phi2 / phi_end2)) / 8
[docs]
class DoubleWell4Potential(DoubleWellPotential):
"""Quartic Double-Well potential: `V(phi) = Lambda**4 * (1 - (phi/phi0)**4)**2`.
Double-Well shifted such that left minimum is at zero: phi -> phi-phi0
"""
tag = 'dw4'
name = 'DoubleWell4Potential'
tex = r'Double-Well (quartic)'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
super().__init__(p=4, **pot_kwargs)
[docs]
@staticmethod
def phi_end_squared(phi0):
"""Get inflaton at end of inflation using slow-roll.
Parameters
----------
phi0 : float
Inflaton distance between local maximum and minima.
Returns
-------
phi_end2 : float
Inflaton phi squared at end of inflation. (unshifted!)
"""
a = (216 * phi0**8 + phi0**12 - 12 * np.sqrt(3. * phi0**16 * (108 + phi0**4)))**(1/3)
b = 192 + phi0**4 + phi0**8 / a + a
return (8 + np.sqrt(b) / np.sqrt(3)
- np.sqrt(128 - (a - phi0**4)**2 / (3 * a)
+ (8 * np.sqrt(3) * (128 + phi0**4)) / np.sqrt(b)))
[docs]
@classmethod
def phi2efolds(cls, phi_shifted, phi0):
"""Get e-folds `N` from inflaton `phi`.
Find the number of e-folds `N` till end of inflation from inflaton `phi`
using the slow-roll approximation.
Parameters
----------
phi_shifted : float or np.ndarray
Inflaton field `phi` shifted by phi0 such that left potential
minimum is at zero.
phi0 : float
Inflaton distance between local maximum and minima.
Returns
-------
N : float or np.ndarray
Number of e-folds `N` until end of inflation.
"""
warn(DeprecationWarning("This method will be removed in future versions, it is superseded "
"by `sr_phi2N`."))
assert np.all(phi_shifted < phi0)
phi2 = (phi_shifted - phi0)**2
phi_end2 = cls.phi_end_squared(phi0=phi0)
return (phi2 - phi_end2 + phi0**4 * (1/phi2 - 1/phi_end2)) / 16
[docs]
class TmodelPotential(InflationaryPotential):
"""T-model potential: `V(phi) = Lambda**4 * (tanh(phi / (sqrt(6) * alpha)))**(2*p)`."""
tag = 'tmp'
name = 'TmodelPotential'
tex = r'T-model'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
self.p = pot_kwargs.pop('p')
self.alpha = pot_kwargs.pop('alpha')
self.s_6_a = np.sqrt(6 * self.alpha)
super().__init__(**pot_kwargs)
[docs]
def V(self, phi): # noqa: D102
C = np.tanh(phi / self.s_6_a)
return self.Lambda**4 * C**(2 * self.p)
[docs]
def dV(self, phi): # noqa: D102
p = self.p
L1 = p * self.Lambda**4 / self.s_6_a
C = np.tanh(phi / self.s_6_a)
return 2 * L1 * (C**(2*p-1) - C**(2*p+1))
[docs]
def d2V(self, phi): # noqa: D102
p = self.p
L2 = 2 * p * self.Lambda**4 / self.s_6_a**2
C = np.tanh(phi / self.s_6_a)
return L2 * (+ C**(2*p-2) * (2 * p - 1)
- C**(2*p+0) * 4 * p
+ C**(2*p+2) * (2 * p + 1))
[docs]
def d3V(self, phi): # noqa: D102
p = self.p
L3 = 4 * p * self.Lambda**4 / self.s_6_a**3
C = np.tanh(phi / self.s_6_a)
return L3 * (+ C**(2*p-3) * (2*p**2 - 3*p + 1)
+ C**(2*p-1) * (-6*p**2 + 3*p - 1)
+ C**(2*p+1) * (6*p**2 + 3*p + 1)
+ C**(2*p+3) * (-2*p**2 - 3*p - 1))
[docs]
def d4V(self, phi): # noqa: D102
p = self.p
L4 = 4 * p * self.Lambda**4 / self.s_6_a**4
C = np.tanh(phi / self.s_6_a)
return L4 * (+ C**(2*p - 4) * (4*p**3 - 12*p**2 + 11*p - 3)
+ C**(2*p - 2) * (-16*p**3 + 24*p**2 - 16*p + 4)
+ C**(2*p + 0) * (24 * p**3 + 10 * p)
+ C**(2*p + 2) * (-16*p**3 - 24*p**2 - 16*p - 4)
+ C**(2*p + 4) * (4*p**3 + 12*p**2 + 11*p + 3))
[docs]
def inv_V(self, V): # noqa: D102
p = self.p
return self.s_6_a * np.arctanh(V**(1/(2*p)) / self.Lambda**(2/p))
[docs]
def get_epsilon_1V(self, phi): # noqa: D102
return 8 * self.p**2 / (self.s_6_a**2 * np.sinh(2 * phi / self.s_6_a)**2)
[docs]
def get_epsilon_2V(self, phi): # noqa: D102
C = np.tanh(phi / self.s_6_a)
return 4 * self.p * (1 - C**4) / (C**2 * self.s_6_a**2)
[docs]
def get_epsilon_3V(self, phi): # noqa: D102
C = np.tanh(phi / self.s_6_a)
return 4 * self.p * (-C**6 + C**4 - C**2 + 1) / (C**2 * self.s_6_a**2 * (C**2 + 1))
[docs]
def get_epsilon_4V(self, phi): # noqa: D102
C = np.tanh(phi / self.s_6_a)
return (4 * self.p * (-C**10 - C**8 + 4*C**6 - 4*C**4 + C**2 + 1)
/ (C**2 * self.s_6_a**2 * (C**6 + C**4 + C**2 + 1)))
@cached_property
def phi_end(self): # noqa: D102
return self.s_6_a/2 * np.arcsinh(2 * np.sqrt(2) * self.p / self.s_6_a)
[docs]
def sr_phi2N(self, phi): # noqa: D102
p = self.p
s_6_a = self.s_6_a
return s_6_a/(8*p) * (s_6_a * np.cosh(2*phi/s_6_a) - np.sqrt(8*p**2 + s_6_a**2))
[docs]
def sr_N2phi(self, N): # noqa: D102
p = self.p
s_6_a = self.s_6_a
return s_6_a / 2 * np.arccosh(8*p/s_6_a**2 * N + np.sqrt(8*p**2+s_6_a**2)/s_6_a)
[docs]
class RadionGaugePotential(InflationaryPotential):
"""Generalised Radion Gauge potential: `V(phi) = Lambda**4 * phi**p / (alpha + phi**p)`.
Listed in the Encyclopaedia Inflationaris under eq. (5.226).
Also related to the KKLT version of D-Brane Inflation, see eq. (6.319).
Parameters
----------
Lambda : float
Potential amplitude parameter.
p : float
Power of the inflaton field in the potential.
alpha : float
Potential parameter controlling the tensor-to-scalar ratio similar to other `alpha`
parameters in alpha-attractors.
Attributes
----------
mu : float
Alternative potential parameter, related to `alpha` as `mu**p = alpha`, allowing to express
the potential in terms of the fraction `phi/mu`. Provided here for convenience.
"""
tag = 'rgp'
name = 'RadionGaugePotential'
tex = r'Radion Gauge'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
self.p = pot_kwargs.pop('p')
self.alpha = pot_kwargs.pop('alpha')
self.mu = self.alpha**(1/self.p)
super().__init__(**pot_kwargs)
[docs]
def V(self, phi): # noqa: D102
return self.Lambda**4 * phi**self.p / (self.alpha + phi**self.p)
[docs]
def dV(self, phi): # noqa: D102
p = self.p
alpha = self.alpha
L1 = self.Lambda**4 * alpha * p * phi**(p-1)
return L1 / (alpha + phi**p)**2
[docs]
def d2V(self, phi): # noqa: D102
p = self.p
alpha = self.alpha
L2 = self.Lambda**4 * alpha * p * phi**(p-2)
return L2 * (alpha * p - alpha - p * phi**p - phi**p) / (alpha + phi**p)**3
[docs]
def d3V(self, phi): # noqa: D102
p = self.p
alpha = self.alpha
L3 = self.Lambda**4 * alpha * p * phi**(p-3)
return L3 * (p**2 * (alpha**2 - 4*alpha*phi**p + phi**(2*p))
- 3 * p * (alpha - phi**p) * (alpha + phi**p)
+ 2 * (alpha + phi**p)**2) / (alpha + phi**p)**4
[docs]
def d4V(self, phi): # noqa: D102
p = self.p
alpha = self.alpha
L4 = self.Lambda**4 * alpha * p * phi**(p-4)
return L4 * (p**3 * (alpha - phi**p) * (alpha**2 - 10*alpha*phi**p + phi**(2*p))
- 6 * p**2 * (alpha + phi**p) * (alpha**2 - 4*alpha*phi**p + phi**(2*p))
+ 11 * p * (alpha - phi**p) * (alpha + phi**p)**2
- 6 * (alpha + phi**p)**3) / (alpha + phi**p)**5
[docs]
def inv_V(self, V): # noqa: D102
return (self.alpha / (self.Lambda**4/V - 1))**(1/self.p)
[docs]
def get_epsilon_1V(self, phi): # noqa: D102
return self.alpha**2 * self.p**2 / (2 * phi**2 * (self.alpha + phi**self.p)**2)
[docs]
def get_epsilon_2V(self, phi): # noqa: D102
p = self.p
alpha = self.alpha
return 2 * alpha * p * (alpha + phi**p*(p+1)) / (phi**2 * (alpha + phi**p)**2)
[docs]
def get_epsilon_3V(self, phi): # noqa: D102
p = self.p
alpha = self.alpha
return (alpha * p * (p**2*(alpha - phi**p) * (3*alpha - phi**p)
- 3 * p * (alpha**2*p - alpha*p*phi**p - alpha*phi**p - phi**(2*p))
+ 2 * (alpha + phi**p)**2) /
(phi**2 * (alpha + phi**p)**2 * (alpha + p*phi**p + phi**p)))
[docs]
def get_epsilon_4V(self, phi): # noqa: D102
p = self.p
alpha = self.alpha
return (alpha * p * (p**4 * phi**(3*p) * (3*alpha - phi**p)
- p**3*phi**p*(alpha+phi**p) * (alpha**2-6*alpha*phi**p+6*phi**(2*p))
+ p**2*phi**p*(alpha + phi**p)**2*(3*alpha - 13*phi**p)
- 12*p*phi**p*(alpha + phi**p)**3
- 4*(alpha + phi**p)**4) /
(phi**2 * (p**3 * phi**(2*p) * (alpha - phi**p) * (alpha + phi**p)**2
+ p**2 * phi**p * (alpha - 4*phi**p) * (alpha + phi**p)**3
- 5 * p * phi**p * (alpha + phi**p)**4
- 2 * (alpha + phi**p)**5)))
@cached_property
def phi_end(self): # noqa: D102
def inflation_end(phi):
return self.get_epsilon_1V(phi=phi) - 1
out = root_scalar(inflation_end, bracket=(1e-30, self.inv_V(V=self.Lambda**4 * (1-EPS))))
return out.root
[docs]
def sr_phi2N(self, phi): # noqa: D102
p = self.p
alpha = self.alpha
phi_end = self.phi_end
return ((phi**(p+2) - phi_end**(p+2) + (phi**2 - phi_end**2) * (alpha*p/2 + alpha)) /
(alpha * p * (p+2)))
[docs]
def sr_N2phi(self, N): # noqa: D102
def root_N(phi_in):
return self.sr_phi2N(phi=phi_in) - N
out = root_scalar(root_N, bracket=(self.phi_end, self.inv_V(V=self.Lambda**4*(1-1e-15))))
phi = out.root
return phi
[docs]
class RadionGauge2Potential(RadionGaugePotential):
"""Quadratic Radion Gauge potential: `V(phi) = Lambda**4 * phi**2 / (alpha + phi**2)`.
Listed in the Encyclopaedia Inflationaris under eq. (5.226).
Also related to the KKLT version of D-Brane Inflation, see eq. (6.319).
Parameters
----------
Lambda : float
Potential amplitude parameter.
alpha : float
Potential parameter controlling the tensor-to-scalar ratio similar to other `alpha`
parameters in alpha-attractors.
Attributes
----------
mu : float
Alternative potential parameter, related to `alpha` as `mu**2 = alpha`, allowing to express
the potential in terms of the fraction `phi/mu`. Provided here for convenience.
"""
tag = 'rg2'
name = 'RadionGauge2Potential'
tex = r'Radion Gauge (p=2)'
perturbation_ic = (1, 0, 0, 1)
def __init__(self, **pot_kwargs):
super().__init__(p=2, **pot_kwargs)
@cached_property
def phi_end(self): # noqa: D102
a = self.alpha
phi_end = 3**(1/12) * np.sqrt(
3 * a**(4/3)
+ 3**(2/3) * a**(2/3) * (np.sqrt(3)*a + 9*np.sqrt(2*a+27) + 27*np.sqrt(3))**(2/3)
- 2 * 3**(5/6) * a * (np.sqrt(3)*a + 9*np.sqrt(2*a+27) + 27*np.sqrt(3))**(1/3)
) / (
3 * (np.sqrt(3)*a + 9*np.sqrt(2*a+27) + 27*np.sqrt(3))**(1/6)
)
return phi_end
[docs]
def sr_N2phi(self, N): # noqa: D102
alpha = self.alpha
phi_end = self.phi_end
return np.sqrt(-alpha + np.sqrt(8*N*alpha + alpha**2 + 2*alpha*phi_end**2 + phi_end**4))
# TODO:
# class HilltopPotential(InflationaryPotential):
# """Double-Well potential: `V(phi) = Lambda**4 * (1 - (phi/phi0)**p)**2`.
#
# Double-Well shifted such that left minimum is at zero: phi -> phi-phi0
# """
#
# tag = 'htp'
# name = 'HilltopPotential'
# tex = r'Hilltop (p)'
#
# def __init__(self, **pot_kwargs):
# self.phi0 = pot_kwargs.pop('phi0')
# self.p = pot_kwargs.pop('p')
# super(HilltopPotential, self).__init__(**pot_kwargs)
# self.prefactor = 2 * self.p * self.Lambda**4
#
# def V(self, phi):
# """`V(phi) = Lambda**4 * (1 - (phi/phi0)**p)**2`.
#
# Double-Well shifted such that left minimum is at zero: phi -> phi-phi0
# """
# phi -= self.phi0
# return self.Lambda**4 * (1 - (phi / self.phi0)**self.p)**2
#
# def dV(self, phi):
# """`V'(phi) = 2p*Lambda**4 * (-1 + (phi / phi0)**p) * phi**(p - 1) / phi0**p`.
#
# Double-Well shifted such that left minimum is at zero: phi -> phi-phi0
# """
# p = self.p
# phi0 = self.phi0
# pre = self.prefactor
# phi -= phi0
# return pre * (-1 + (phi / phi0)**p) * phi**(p - 1) / phi0**p
#
# def d2V(self, phi):
# """`V''(phi) = 2p*Lambda**4 * (1-p+(2*p-1)*(phi/phi0)**p) * phi**(p-2) / phi0**p`.
#
# Double-Well shifted such that left minimum is at zero: phi -> phi-phi0
# """
# p = self.p
# phi0 = self.phi0
# pre = self.prefactor
# phi -= phi0
# return pre * (1 - p + (2 * p - 1) * (phi / phi0)**p) * phi**(p - 2) / phi0**p
#
# def d3V(self, phi):
# """`V'''(phi) = 2p(p-1)Lambda**4 * (2-p+(4*p-2)*(phi/phi0)**p) * phi**(p-3) / phi0**p`.
#
# Double-Well shifted such that left minimum is at zero: phi -> phi-phi0
# """
# p = self.p
# phi0 = self.phi0
# pre = self.prefactor
# phi -= phi0
# return pre * (p - 1) * (2 - p + (4 * p - 2) * (phi / phi0)**p) * phi**(p - 3) / phi0**p
#
# def inv_V(self, V):
# """`phi(V) = phi0 * (1 - sqrt(V) / Lambda**2)**(1/p)`."""
# return self.phi0 * (1 - np.sqrt(V) / self.Lambda**2)**(1/self.p)
[docs]
class FeatureFunction(ABC):
"""Feature in the inflationary potential."""
[docs]
@staticmethod
@abstractmethod
def F(x, x0, a, b):
"""Feature function."""
[docs]
@staticmethod
@abstractmethod
def dF(x, x0, a, b):
"""Feature function derivative."""
[docs]
@staticmethod
@abstractmethod
def d2F(x, x0, a, b):
"""Feature function 2nd derivative."""
[docs]
@staticmethod
@abstractmethod
def d3F(x, x0, a, b):
"""Feature function 3rd derivative."""
[docs]
class GaussianDip(FeatureFunction):
"""Gaussian: `F(x) = -a * exp(-(x-x0)**2 / (2*b**2))`."""
[docs]
@staticmethod
def F(x, x0, a, b):
"""`F(x) = -a * exp(-(x-x0)**2 / (2*b**2))`."""
return -a * np.exp(-(x - x0)**2 / (2 * b**2))
[docs]
@staticmethod
def dF(x, x0, a, b):
"""`F'(x) = a/b**2 * (x-x0) * exp(-(x-x0)**2 / (2*b**2))`."""
return a / b**2 * (x - x0) * np.exp(-(x - x0)**2 / (2 * b**2))
[docs]
@staticmethod
def d2F(x, x0, a, b):
"""`F''(x) = a/b**4 * (b**2 - (x-x0)**2) * exp(-(x-x0)**2 / (2*b**2))`."""
return a / b**4 * (b**2 - (x - x0)**2) * np.exp(-(x - x0)**2 / (2 * b**2))
[docs]
@staticmethod
def d3F(x, x0, a, b):
"""`F'''(x) = a/b**6 * (x-x0) * ((x-x0)**2 - 3*b**2) * exp(-(x-x0)**2 / (2*b**2))`."""
return a / b**6 * (x - x0) * ((x - x0)**2 - 3 * b**2) * np.exp(-(x - x0)**2 / (2 * b**2))
[docs]
class TanhStep(FeatureFunction):
"""Tanh step function: `F(x) = a * tanh((x - x0) / b)`."""
[docs]
@staticmethod
def F(x, x0, a, b):
"""`F(x) = a * tanh((x-x0)/b)`."""
return a * np.tanh((x - x0) / b)
[docs]
@staticmethod
def dF(x, x0, a, b):
"""`F'(x) = a/b * (1 - tanh((x-x0)/b)**2)`."""
tanh = np.tanh((x - x0) / b)
return a / b * (1 - tanh**2)
[docs]
@staticmethod
def d2F(x, x0, a, b):
"""`F''(x) = -2*a/b**2 * tanh((x-x0)/b) * (1 - tanh((x-x0)/b)**2)`."""
tanh = np.tanh((x - x0) / b)
return -2 * a / b**2 * tanh * (1 - tanh**2)
[docs]
@staticmethod
def d3F(x, x0, a, b):
"""`F'''(x) = -2*a/b**3 * (1 - 4*tanh((x-x0)/b)**2 + 3*tanh((x-x0)/b)**4)`."""
tanh = np.tanh((x - x0) / b)
return -2 * a / b**3 * (1 - 4 * tanh**2 + 3 * tanh**4)
[docs]
class FeaturePotential(InflationaryPotential, FeatureFunction):
"""Inflationary potential with a feature: `V(phi) = V0(phi) * (1+F(phi))`."""
def __init__(self, **pot_kwargs):
self.phi_feature = pot_kwargs.pop('phi_feature') # position of feature
self.a_feature = pot_kwargs.pop('a_feature') # e.g. height or amplitude of feature
self.b_feature = pot_kwargs.pop('b_feature') # e.g. width or gradient of feature
super().__init__(**pot_kwargs)
[docs]
def V(self, phi):
"""Inflationary potential V0(phi) with a feature function F(phi).
`V(phi) = V0(phi) * (1 + F(phi))`
"""
V0 = super().V(phi)
F = super().F(phi, self.phi_feature, self.a_feature, self.b_feature)
return V0 * (1 + F)
[docs]
def dV(self, phi):
"""First derivative of the inflationary potential with a feature.
`V'(phi) = V0'(phi) * (1 + F(phi)) + V0(phi) * F'(phi)`
"""
V0 = super().V(phi)
dV0 = super().dV(phi)
F = super().F(phi, self.phi_feature, self.a_feature, self.b_feature)
dF = super().dF(phi, self.phi_feature, self.a_feature, self.b_feature)
return dV0 * (1 + F) + V0 * dF
[docs]
def d2V(self, phi):
"""Second derivative of the inflationary potential with a feature.
`V''(phi) = V0''(phi) * (1 + F(phi)) + 2*V0'(phi)*F'(phi) + V0(phi)*F''(phi)`
"""
V0 = super().V(phi)
dV0 = super().dV(phi)
d2V0 = super().d2V(phi)
F = super().F(phi, self.phi_feature, self.a_feature, self.b_feature)
dF = super().dF(phi, self.phi_feature, self.a_feature, self.b_feature)
d2F = super().d2F(phi, self.phi_feature, self.a_feature, self.b_feature)
return d2V0 * (1 + F) + 2 * dV0 * dF + V0 * d2F
[docs]
def d3V(self, phi):
r"""Third derivative of the inflationary potential with a feature.
.. math::
V'''(\phi) = V_0'''(\phi) * (1 + F(\phi))
+ 3 * V_0''(\phi) * F'(\phi)
+ 3 * V_0'(\phi) * F''(\phi)
+ V_0(\phi) * F'''(\phi)
"""
V0 = super().V(phi)
dV0 = super().dV(phi)
d2V0 = super().d2V(phi)
d3V0 = super().d3V(phi)
F = super().F(phi, self.phi_feature, self.a_feature, self.b_feature)
dF = super().dF(phi, self.phi_feature, self.a_feature, self.b_feature)
d2F = super().d2F(phi, self.phi_feature, self.a_feature, self.b_feature)
d3F = super().d3F(phi, self.phi_feature, self.a_feature, self.b_feature)
return d3V0 * (1 + F) + 3 * d2V0 * dF + 3 * dV0 * d2F + V0 * d3F
[docs]
class StarobinskyGaussianDipPotential(FeaturePotential, StarobinskyPotential, GaussianDip):
"""Starobinsky potential with a Gaussian dip."""
tag = 'sgd'
name = 'StarobinskyGaussianDipPotential'
tex = r'Starobinsky with a Gaussian dip'
[docs]
class StarobinskyTanhStepPotential(FeaturePotential, StarobinskyPotential, TanhStep):
"""Starobinsky potential with a hyperbolic tangent step."""
tag = 'sts'
name = 'StarobinskyTanhStepPotential'
tex = r'Starobinsky with a tanh step'