Observable quantities
This page shows observable quantities that can be derived from solutions of the Einstein-Boltzmann system, such as power spectra and distances.
Primordial power spectra
SymBoltz.spectrum_primordial — Functionspectrum_primordial(k, h, As, ns=1.0; kp = 0.05/u"Mpc")Compute the primordial power spectrum
\[P₀(k) = 2π² Aₛ (k/kₚ)^{nₛ-1} / k³\]
with spectral amplitude As, spectral index ns and pivot scale wavenumber kp at the wavenumber(s) k.
Example
using SymBoltz, Unitful, UnitfulAstro, Plots
M = SymBoltz.ΛCDM()
pars = Dict(M.g.h => 0.7, M.I.As => 2e-9, M.I.ns => 0.95)
ks = 10 .^ range(-5, +1, length=100) / u"Mpc"
Ps = spectrum_primordial(ks, M, pars)
plot(log10.(ks*u"Mpc"), log10.(Ps/u"Mpc^3"); xlabel = "log10(k/Mpc⁻¹)", ylabel = "log10(P/Mpc³)")Matter power spectra
SymBoltz.spectrum_matter — Functionspectrum_matter(sol::CosmologySolution, k, τ = sol[τ][end]; species = [:c, :b, :h])Compute the power spectrum
\[P(k,τ) = P₀(k) |Δ(k,τ)|²\]
of the total gauge-invariant overdensity
\[Δ = δ + (3ℋ/k²) θ = (∑ₛδρ)/(∑ₛρₛ) + (3ℋ/k²) (∑ₛ(ρₛ+Pₛ)θₛ) / (∑ₛ(ρₛ+Pₛ))\]
for the given species at wavenumber(s) k and conformal time(s) tau (final, if omitted) from the solution sol. By default, the species are cold dark matter, baryons and massive neutrinos, which are matter-like at late times in the ΛCDM model.
spectrum_matter(prob::CosmologyProblem, k, τ = nothing; species = [:c, :b, :h], kwargs...)Solve the problem prob with exact wavenumber(s) k, and then compute the power spectrum with the solution sol.
SymBoltz.spectrum_matter_nonlinear — Functionspectrum_matter_nonlinear(sol::CosmologySolution, k)Compute the nonlinear matter power spectrum from the cosmology solution sol at wavenumber(s) k using halofit implemented in MatterPower.jl.
Example
using SymBoltz, Unitful, UnitfulAstro, Plots
M = SymBoltz.ΛCDM()
pars = SymBoltz.parameters_Planck18(M)
prob = CosmologyProblem(M, pars)
ks = 10 .^ range(-5, +2, length=200) / u"Mpc"
sol = solve(prob, ks)
# Linear power spectrum
Ps = spectrum_matter(sol, ks)
plot(log10.(ks*u"Mpc"), log10.(Ps/u"Mpc^3"); xlabel = "log10(k/Mpc⁻¹)", ylabel = "log10(P/Mpc³)", label = "linear (SymBoltz)")
# Nonlinear power spectrum (from halofit)
Ps = spectrum_matter_nonlinear(sol, ks)
plot!(log10.(ks*u"Mpc"), log10.(Ps/u"Mpc^3"); label = "non-linear (halofit)", legend_position = :bottomleft)CMB power spectra
SymBoltz.spectrum_cmb — Functionspectrum_cmb(ΘlAs::AbstractMatrix, ΘlBs::AbstractMatrix, P0s::AbstractVector, ls::AbstractVector, ks::AbstractVector; integrator = TrapezoidalRule(), normalization = :Cl)Compute the angular power spectrum
\[Cₗᴬᴮ = (2/π) ∫\mathrm{d}k \, k² P₀(k) Θₗᴬ(k,τ₀) Θₗᴮ(k,τ₀)\]
for the given ls. If normalization == :Dl, compute $Dₗ = Cₗ l (l+1) / 2π$ instead.
spectrum_cmb(modes::AbstractVector{<:Symbol}, prob::CosmologyProblem, jl::SphericalBesselCache; normalization = :Cl, unit = nothing, kτ0s = 0.1*jl.l[begin]:2π/2:10*jl.l[end], xs = 0.0:0.0008:1.0, l_limber = 50, integrator = TrapezoidalRule(), bgopts = (alg = Rodas4P(), reltol = 1e-9, abstol = 1e-9), ptopts = (alg = KenCarp4(), reltol = 1e-8, abstol = 1e-8), sourceopts = (rtol = 1e-3, atol = 0.9), verbose = false, kwargs...)Compute angular CMB power spectra $Cₗᴬᴮ$ at angular wavenumbers ls from the cosmological problem prob. The requested modes are specified as a vector of symbols in the form :AB, where A and B are T (temperature), E (E-mode polarization) or ψ (lensing). If unit is nothing the spectra are of dimensionless temperature fluctuations relative to the present photon temperature; while if unit is a temperature unit the spectra are of dimensionful temperature fluctuations. Returns a matrix of $Cₗ$ if normalization is :Cl, or $Dₗ = l(l+1)/2π$ if normalization is :Dl.
The lensing line-of-sight integral uses the Limber approximation for l ≥ l_limber.
Examples
using SymBoltz, Unitful
M = ΛCDM()
pars = parameters_Planck18(M)
prob = CosmologyProblem(M, pars)
ls = 10:10:1000
jl = SphericalBesselCache(ls)
modes = [:TT, :TE, :ψψ, :ψT]
Dls = spectrum_cmb(modes, prob, jl; normalization = :Dl, unit = u"μK")spectrum_cmb(modes::AbstractVector, prob::CosmologyProblem, jl::SphericalBesselCache, ls::AbstractVector; kwargs...)Same, but compute the spectrum properly only for jl.l and then interpolate the results to all ls.
Example
using SymBoltz, Plots
M = SymBoltz.ΛCDM()
pars = SymBoltz.parameters_Planck18(M)
prob = CosmologyProblem(M, pars)
ls = 25:25:3000 # 25, 50, ..., 3000
jl = SphericalBesselCache(ls)
modes = [:TT, :EE, :TE, :ψψ, :ψT, :ψE]
Dls = spectrum_cmb(modes, prob, jl; normalization = :Dl)
Plots.plot(ls, log10.(abs.(Dls)); xlabel = "l", ylabel = "lg(Dₗ)", label = permutedims(String.(modes)))Two-point correlation function
SymBoltz.correlation_function — Functioncorrelation_function(sol::CosmologySolution; N = 2048, spline = true)Compute the two-point correlation function in real space by Fourier transforming the matter power spectrum of sol with N points the FFTLog algorithm implemented in TwoFAST. Returns N radii and correlation function values (e.g. r, ξ).
Example
using SymBoltz, Unitful, UnitfulAstro, Plots
M = SymBoltz.ΛCDM()
pars = SymBoltz.parameters_Planck18(M)
prob = CosmologyProblem(M, pars)
ks = 10 .^ range(-5, +3, length=300) / u"Mpc"
sol = solve(prob, ks)
rs, ξs = correlation_function(sol)
rs = rs / (SymBoltz.k0*sol.bg.ps[:h]) * u"Mpc" # TODO: auto units
plot(rs, @. ξs * rs^2; xlims = (0, 200), xlabel = "r", ylabel = "r² ξ")Matter density fluctuations
SymBoltz.variance_matter — Functionvariance_matter(sol::CosmologySolution, R)Compute the variance $⟨δ²⟩$ of the linear matter density field with a top-hat filter with radius R. Wraps the implementation in MatterPower.jl.
SymBoltz.stddev_matter — Functionstddev_matter(sol::CosmologySolution, R)Compute the standard deviation $√(⟨δ²⟩)$ of the linear matter density field with a top-hat filter with radius R.
using SymBoltz, Unitful, UnitfulAstro, Plots
M = SymBoltz.ΛCDM()
pars = SymBoltz.parameters_Planck18(M)
prob = CosmologyProblem(M, pars)
ks = 10 .^ range(-5, +3, length=300) / u"Mpc"
sol = solve(prob, ks)
h = sol[M.g.h]
Rs = 10 .^ range(0, 2, length=100) * u"Mpc"
σs = stddev_matter.(sol, Rs)
plot(log10.(Rs/(u"Mpc"/h)), log10.(σs); xlabel = "lg(R / (Mpc/h))", ylabel = "lg(σ)", label = nothing)
R8 = 8 * u"Mpc"/h
σ8 = stddev_matter(sol, R8)
scatter!((log10(R8/(u"Mpc"/h)), log10(σ8)), series_annotation = text(" σ₈ = $(round(σ8; digits=3))", :left), label = nothing)Luminosity distance
SymBoltz.distance_luminosity — Functiondistance_luminosity(sol::CosmologySolution, ivs = sol.bg.t, τ0 = sol[sol.prob.M.τ0])Compute luminosity distances
\[d_L = \frac{r}{a} = \chi \, \mathrm{sinc} (\sqrt{K} (τ₀-τ)),\]
at the independent variable values ivs relative to the (present) time τ0.
using SymBoltz, Plots
M = SymBoltz.RMΛ(K = SymBoltz.curvature(SymBoltz.metric()))
pars = Dict(
M.r.Ω₀ => 5e-5,
M.m.Ω₀ => 0.3,
M.K.Ω₀ => 0.1,
M.r.T₀ => NaN,
M.g.h => 0.7
)
prob = CosmologyProblem(M, pars)
sol = solve(prob)
zs = 0.0:1.0:10.0
τs = SymBoltz.timeseries(sol, M.g.z, zs) # times at given redshifts
dLs = SymBoltz.distance_luminosity(sol, τs) / SymBoltz.Gpc
plot(zs, dLs; marker=:dot, xlabel="z", ylabel="dL / Gpc", label=nothing)Sound horizon (BAO scale)
SymBoltz.sound_horizon — Functionsound_horizon(sol::CosmologySolution)Cumulatively integrate the sound horizon
\[ rₛ(τ) = ∫_0^τ dτ cₛ = ∫_0^τ \frac{dτ}{√(3(1+3ρ_b/4ρ_γ))}\]
to the time steps of the solution sol.
using SymBoltz, Plots
M = SymBoltz.ΛCDM()
pars = SymBoltz.parameters_Planck18(M)
prob = CosmologyProblem(M, pars)
sol = solve(prob)
τs = sol[M.τ]
rs = sound_horizon(sol)
plot(τs, rs; xlabel = "τ / H₀⁻¹", ylabel = "rₛ / (c/H₀)")Source functions
SymBoltz.source_grid — Functionsource_grid(sol::CosmologySolution, Ss::AbstractVector, τs)Evaluate and return source functions $S(τ,k)$ from the solution sol. The source functions are given by symbolic expressions Ss, and evaluated on a grid with conformal times τs and the same wavenumbers as sol is solved for.
source_grid(Ss_coarse::AbstractArray, ks_coarse, ks_fine; ktransform = identity)Interpolate values Ss_coarse of source functions $S(τ,k)$ from a coarse wavenumber grid ks_coarse to a fine grid ks_fine. The interpolation is linear in ktransform(k) (e.g. identity for interpolation in $k$ or log for interpolation in $\ln k$. Conformal times are unchanged.
source_grid(prob::CosmologyProblem, Ss::AbstractArray, τs, ks; bgopts = (), ptopts = (), thread = true, verbose = false)Compute and evaluate source functions $S(τ,k)$ with symbolic expressions Ss on a grid with conformal times τs and wavenumbers ks from the problem prob.
The options bgopts and ptopts are passed to the background and perturbation solves.
SymBoltz.source_grid_adaptive — Functionsource_grid_adaptive(prob::CosmologyProblem, Ss::AbstractVector, τs, ks; bgopts = (), ptopts = (), sort = true, kwargs...)Adaptively compute and evaluate source functions $S(τ,k)$ with symbolic expressions Ss on a grid with fixed conformal times τs, but adaptively refined grid of wavenumbers from the problem prob. The source functions are first evaluated on the (coarse) initial grid ks. Each subinterval $(k₁, k₂)$ of ks is then adaptively refined until the linear interpolation $Sᵢ = (S(k₁)+S(k₂))/2$ to the midpoint $k=(k₁+k₂)/2$ approximates the actual value $S(k)$ there within some tolerance. The comparison $Sᵢ ≈ S$ is done with isapprox(Sᵢ, S; kwargs...), where S and Sᵢ are vectors with the (conformal) timeseries of the source function for that wavenumber. It receives the keyword arguments kwargs passed to this function, so atol, rtol and/or norm can be specified to tune the tolerance.
Returns the refined wavenumbers sorted in ascending order and a grid with the corresponding source function values. If not sort, the wavenumbers and source function values are instead left in the order in which they were inserted in the refinement process.
The options bgopts and ptopts are passed to the background and perturbation solves.
using SymBoltz, Plots, DataInterpolations
M = ΛCDM(h = nothing, ν = nothing)
pars = parameters_Planck18(M)
prob = CosmologyProblem(M, pars)
sol = solve(prob)
τs = sol[M.τ] # conformal times in background solution
ks = [1.0, 2000.0] # initial coarse grid
ks, Ss = source_grid_adaptive(prob, [M.ST0], τs, ks; atol = 5.0)
iτ = argmax(sol[M.b.v]) # index of decoupling time
iτs = iτ-75:iτ+75 # indices around decoupling
p1 = surface(ks, τs[iτs], Ss[1, iτs, :]; camera = (45, 25), xlabel = "k", ylabel = "τ", zlabel = "S", colorbar = false)
lgas = -6.0:0.2:0.0
τs = LinearInterpolation(sol[M.τ], sol[log10(M.g.a)])(lgas) # τ at given lg(a)
ks = 5.0:5.0:100.0
Ss = source_grid(prob, [M.g.Ψ], τs, ks)
p2 = wireframe(ks, lgas, Ss[1, :, :]; camera = (75, 20), xlabel = "k", ylabel = "lg(a)", zlabel = "Φ")
plot(p1, p2)Line-of-sight integration
SymBoltz.los_integrate — Functionlos_integrate(Ss::AbstractMatrix{T}, ls::AbstractVector, τs::AbstractVector, ks::AbstractVector, jl::SphericalBesselCache; l_limber = typemax(Int), integrator = TrapezoidalRule(), verbose = false) where {T <: Real}For the given ls and ks, compute the line-of-sight-integrals
\[Iₗ(k) = ∫dτ S(k,τ) jₗ(k(τ₀-τ))\]
over the source function values Ss against the spherical Bessel functions $jₗ(x)$ cached in jl. The element Ss[i,j] holds the source function value $S(kᵢ, τⱼ)$. The limber approximation
\[Iₗ ≈ √(π/(2l+1)) S(k,τ₀-(l+1/2)/k)\]
is used for l ≥ l_limber.