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 — Function
spectrum_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 = Λ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 — Function
spectrum_matter([species,] sol::CosmologySolution, k, τ = sol[τ][end])Compute the power spectrum
\[P(k,τ) = P₀(k) |Δ(k,τ)|²\]
of the total gauge-invariant overdensity
\[Δ = δ + (3ℋ/k²) θ = (∑ₛδρ)/(∑ₛρₛ) + (3ℋ/k²) (∑ₛ(ρₛ+Pₛ)θₛ) / (∑ₛ(ρₛ+Pₛ))\]
for one or more species (default: m for matter) at wavenumber(s) k and conformal time(s) τ (final, if omitted) from the solution sol.
spectrum_matter([species,] prob::CosmologyProblem, k::AbstractVector, τ = nothing; kwargs...)Solve the problem prob with exact wavenumber(s) k, and then compute the power spectrum with the solution sol.
spectrum_matter([species,] prob::CosmologyProblem, k::NTuple{2, Number}, τs = nothing; sourceopts = (atol = 4.0, rtol = 4e-3), coarse_length = 9, kwargs...)Compute the matter power spectrum on the interval $k$ with adaptively chosen wavenumbers. Returns wavenumbers and power spectrum values.
The interval is first divided into a grid with coarse_length logarithmically spaced wavenumbers. It is then adaptively refined with the absolute and relative tolerances in sourceopts.
SymBoltz.spectrum_matter_nonlinear — Function
spectrum_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
With explicitly chosen wavenumbers:
using SymBoltz, Unitful, UnitfulAstro, Plots
M = ΛCDM()
pars = parameters_Planck18(M)
prob = CosmologyProblem(M, pars)
ks = 10 .^ range(-5, +2, length=100) / u"Mpc"
sol = solve(prob, ks)
# Linear power spectrum
species = [:m, :c, :b]
Ps = spectrum_matter(species, sol, ks)
Ps = transpose(Ps)
label = permutedims("linear (SymBoltz), " .* ["matter", "cold dark matter", "baryons"])
linewidth = 2
linestyle = [:solid :dash :dot]
legend_position = :bottomleft
plot(log10.(ks*u"Mpc"), log10.(Ps/u"Mpc^3"); xlabel = "log10(k/Mpc⁻¹)", ylabel = "log10(P/Mpc³)", label, linestyle, linewidth, legend_position)
# 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), matter", linewidth, legend_position)With adaptively chosen wavenumbers on an interval:
ks, Ps = spectrum_matter(species, prob, (1e0, 1e3))
label = "$(length(ks)) × k (adaptive), " .* label
plot(log10.(ks), transpose(log10.(Ps)); xlabel = "k / (H₀/c)", ylabel = "P / (c/H₀)³", label, linestyle, linewidth, legend_position)CMB power spectra
SymBoltz.spectrum_cmb — Function
spectrum_cmb(ΘlAs::AbstractMatrix, ΘlBs::AbstractMatrix, P0s::AbstractVector, ls::AbstractVector, ks::AbstractVector; integrator = TrapezoidalRule(), normalization = :Cl, thread = true)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), coarse_length = 9, thread = true, 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.
Source functions are computed on a $k$-grid that is adaptively refined from an initial grid with size coarse_length. The refinement criterion is controlled with sourceopts.
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 = ΛCDM()
pars = 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)
plot(ls, log10.(abs.(Dls)); xlabel = "l", ylabel = "lg(Dₗ)", label = permutedims(String.(modes)))Two-point correlation function
SymBoltz.correlation_function — Function
correlation_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 = ΛCDM()
pars = 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 — Function
variance_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 — Function
stddev_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 = ΛCDM()
pars = 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 — Function
distance_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 = 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 = 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 — Function
sound_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 = ΛCDM()
pars = 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 — Function
source_grid(sol::CosmologySolution, Ss::AbstractVector, τs; thread = true)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, thread = true)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 — Function
source_grid_adaptive(prob::CosmologyProblem, Ss::AbstractVector, τs, ks[, bgsol]; bgopts = (), ptopts = (), ktransform = (identity, identity), sort = true, thread = true, verbose = false, 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.
If τs is nothing, the source function is evaluated at the final time only (today).
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.ST], τ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 — Function
los_integrate(Ss::AbstractArray{T, 3}, ls::AbstractVector, τs::AbstractVector, ks::AbstractVector, jl::SphericalBesselCache; l_limber = typemax(Int), integrator = TrapezoidalRule(), thread = true, 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,k] 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.