CosmoMC Weighted Chains (BK18 baseline likelihood analysis)

The figure below is to be compared to Figure 4 from BICEP/Keck paper XIII.

After loading the appropriate columns from the set of MCMC chains and thinning, performing the kernel density estimation is as simple as:

K_r  = kde(chain_r;  weights = chain_weight, lo =  0.0,            boundary = :closedleft)
K_Ad = kde(chain_Ad; weights = chain_weight, lo =  0.0,            boundary = :closedleft)
K_As = kde(chain_As; weights = chain_weight, lo =  0.0,            boundary = :closedleft)
K_βd = kde(chain_βd; weights = chain_weight, lo =  0.8, hi =  2.4)
K_βs = kde(chain_βs; weights = chain_weight, lo = -4.5, hi = -2.0)
K_αd = kde(chain_αd; weights = chain_weight, lo = -1.0, hi =  0.0, boundary = :closed)
K_αs = kde(chain_αs; weights = chain_weight, lo = -1.0, hi =  0.0, boundary = :closed)
K_ε  = kde(chain_ε;  weights = chain_weight, lo = -1.0, hi =  1.0, boundary = :closed)

Note that because this package does not support constructing 2D density estimates, the 68% and 95% confidence "ellipses" in the lower-left triangle of the plot has been replaced with simple 2D histograms. The hex bin sizes have been chosen using 2× the automatically-determined bandwidths of the corresponding 1D curves.

Source Code

download code download Project.toml download Manifest.toml

using CSV
import KernelDensityEstimation as KDE
using .KDE


# !!!!!!!!!!
# !! NOTE !!
# !!!!!!!!!!
#
# These files must be downloaded manually and the correct folder extracted from
# http://bicepkeck.org/BK18_datarelease/chains.tgz
#
# Be warned that this file is approximately 63GiB, though we use only the smaller of the
# two datasets which expands to about 4.7GiB.

srcdir = joinpath(@__DIR__, "BK18_04_BK18lf_freebdust")
filebase = joinpath(srcdir, "BK18_BK18lf_freebdust")

# interpret the parameters file; first column are column names of the chain file, and
# the second column gives corresponding LaTeX pretty names
paramnames = CSV.read(filebase * ".paramnames", CSV.Tables.matrix; delim = '\t', header = false)

# load the chains
# keep only the MCMC parameters, not all of the derived parameters (adding the two
#   implicit columns for weights and neg-loglike)
nkeep = 2 + findlast(!endswith("*"), paramnames[:, 1])
chains = mapreduce(vcat, 1:16) do num
    CSV.read(filebase * "_$num.txt", CSV.Tables.matrix;
            header = false, delim=' ', ignorerepeated = true,
            select = 1:nkeep)
end

# use the parameter names file to find the correct column in the chains matrix
paramidx(name) = findfirst(==(name), @view paramnames[:, 1])
thinning = 50  # empirically chosen based on examining plot,
               #   lines(autocor(params[:, paramidx("r")]))

# get weights and the parameters portion of the matrix
chain_weight = @view chains[1:thinning:end, 1];
params = @view chains[1:thinning:end, 3:end];
# then extract vectors for each of the parameters we'll plot
chain_r = @view params[:, paramidx("r")];
chain_Ad = @view params[:, paramidx("BBdust")];
chain_As = @view params[:, paramidx("BBsync")];
chain_βd = @view params[:, paramidx("BBbetadust")];
chain_βs = @view params[:, paramidx("BBbetasync")];
chain_αd = @view params[:, paramidx("BBalphadust")];
chain_αs = @view params[:, paramidx("BBalphasync")];
chain_ε = @view params[:, paramidx("BBdustsynccorr")];

# run kernel density estimation on each chain
# N.B. the parameter limits in the `filebase * ".ranges"` was examined to determine lo/hi
K_r  = kde(chain_r;  weights = chain_weight, lo =  0.0,            boundary = :closedleft)
K_Ad = kde(chain_Ad; weights = chain_weight, lo =  0.0,            boundary = :closedleft)
K_As = kde(chain_As; weights = chain_weight, lo =  0.0,            boundary = :closedleft)
K_βd = kde(chain_βd; weights = chain_weight, lo =  0.8, hi =  2.4)
K_βs = kde(chain_βs; weights = chain_weight, lo = -4.5, hi = -2.0)
K_αd = kde(chain_αd; weights = chain_weight, lo = -1.0, hi =  0.0, boundary = :closed)
K_αs = kde(chain_αs; weights = chain_weight, lo = -1.0, hi =  0.0, boundary = :closed)
K_ε  = kde(chain_ε;  weights = chain_weight, lo = -1.0, hi =  1.0, boundary = :closed)


# !!!!!!!!!!
# !! NOTE !!
# !!!!!!!!!!
#
# Below is just plotting. Nothing interesting about KDEs...

using CairoMakie
Base.isinteractive() && using GLMakie

update_theme!(
    Axis = (
        xgridvisible = false,
        ygridvisible = false,
        xtickalign = 1,
        ytickalign = 1,
    ),
    fontsize = 14,
    theme_latexfonts()
)

fig = Figure(size = (800, 800))

fig.layout.alignmode = Outside(8)  # shrink exterior padding

# use two separate sub-grids
# 1. Large Panel grid for the primary parameters (lower triangle of 1D and 2D likelihoods)
fig.layout[1:3, 1:3] = large_grid = GridLayout()
# 2. Small Panel grid to show secondary "nuisance" parameters, reusing just the extra
#    space in the upper-right of the large grid
fig.layout[1:2, 2:3] = small_grid = GridLayout(alignmode = Outside(18, 0, 18, 0))

# use square panels in the top-level fig layout so that the large panel axes can be
# brought into direct contact with one another (once appropriate decorations are hidden)
for ii in 1:3
    rowsize!(fig.layout, ii, Aspect(1, 1))
end

kws_like = (; linewidth = 2, color = :black)
kws_prior = (; linewidth = 1, linestyle = :dash, color = :blue)
dust_label = L"$A_\mathrm{dust}$ @ $\ell=80$ & 353GHz [$\mu K^2$]"
sync_label = L"$A_\mathrm{sync}$ @ $\ell=80$ & 23GHz [$\mu K^2$]"

peaknorm(K::KDE.UnivariateKDE) = KDE.UnivariateKDE(K.x, K.f ./ maximum(K.f))

#############
# large grid

# main diagonal of 1D marginalized posteriors

ax_r = let ax = Axis(large_grid[1, 1], aspect = 1)
    lines!(ax, peaknorm(K_r); kws_like...)
    hidexdecorations!(ax, ticks = false)
    ax.ylabel = L"L / L_\mathrm{peak}"
    xlims!(ax, 0, 0.18)
    ylims!(ax, 0, nothing)
    ax.xticks = 0:0.04:0.16
    ax.yticks = 0:0.2:1
    ax
end

ax_Ad = let ax = Axis(large_grid[2, 2])
    lines!(ax, peaknorm(K_Ad); kws_like...)
    hidexdecorations!(ax, ticks = false)
    hideydecorations!(ax, ticks = false)
    xlims!(ax, 0, 11)
    ylims!(ax, 0, nothing)
    ax.xticks = 0:2:10
    ax.yticks = 0:0.2:1
    ax
end
ax_As = let ax = Axis(large_grid[3, 3])
    lines!(ax, peaknorm(K_As); kws_like...)
    hideydecorations!(ax, ticks = false)
    xlims!(ax, 0, 8)
    ylims!(ax, 0, nothing)
    ax.xticks = 0:2:8
    ax.yticks = 0:0.2:1
    ax.xlabel = sync_label
    ax
end

# lower triangle of parameter correlations

ax_r_Ad = let ax = Axis(large_grid[2, 1])
    # KDE defaults to bwratio = 8, so factor of 16 × Δx is just 2 × bandwidth
    cellsize = 16 .* (step(K_r.x), step(K_Ad.x))
    hexbin!(ax, chain_r, chain_Ad; weights, cellsize, colormap = Reverse(:greys))
    hidexdecorations!(ax, ticks = false)
    ax.ylabel = dust_label
    xlims!(ax, 0, 0.18)
    ylims!(ax, 0, 8.5)
    ax.xticks = 0:0.04:0.16
    ax.yticks = 0:1.5:7.5
    ax
end
ax_r_As = let ax = Axis(large_grid[3, 1])
    # KDE defaults to bwratio = 8, so factor of 16 × Δx is just 2 × bandwidth
    cellsize = 16 .* (step(K_r.x), step(K_As.x))
    hexbin!(ax, chain_r, chain_As; weights, cellsize, colormap = Reverse(:greys))
    ax.xlabel = L"r"
    ax.ylabel = sync_label
    xlims!(ax, 0, 0.18)
    ylims!(ax, 0, 8.5)
    ax.xticks = 0:0.04:0.16
    ax.yticks = 0:1.5:7.5
    ax
end
ax_Ad_As = let ax = Axis(large_grid[3, 2])
    # KDE defaults to bwratio = 8, so factor of 16 × Δx is just 2 × bandwidth
    cellsize = 16 .* (step(K_Ad.x), step(K_As.x))
    hexbin!(ax, chain_Ad, chain_As; weights, cellsize, colormap = Reverse(:greys))
    hideydecorations!(ax, ticks = false)
    ax.xlabel = dust_label
    xlims!(ax, 0, 11)
    ylims!(ax, 0, 8.5)
    ax.xticks = 0:2:10
    ax.yticks = 0:1.5:7.5
    ax
end

# close the gaps in the grid
rowgap!(large_grid, Fixed(0.0))
colgap!(large_grid, Fixed(0.0))

#############
# small grid

ax_βd = let ax = Axis(small_grid[1, 1], aspect = 1)
    # prior
    lines!(ax, [0.8, 0.8, 2.4, 2.4], [0, 1, 1, 0]; kws_prior...)
    # posterior curve
    lines!(ax, peaknorm(K_βd); kws_like...)
    xlims!(ax, 1, 2)
    ylims!(ax, 0, nothing)
    ax.xlabel = L"\beta_d"
    ax.yticks = 0:1
    ax
end

ax_βs = let ax = Axis(small_grid[1, 2], aspect = 1)
    # prior
    let μ = -3.1, σ = 0.3
        lines!(ax, -4.5:0.01:-1.8, x -> exp(-(x - μ)^2 / (2σ^2)); kws_prior...)
    end
    # posterior curve
    lines!(ax, peaknorm(K_βs); kws_like...)
    xlims!(ax, -4.5, -1.8)
    ylims!(ax, 0, nothing)
    ax.xlabel = L"\beta_s"
    ax.xticks = -4:-2
    ax.yticks = 0:1
    ax
end

ax_ε = let ax = Axis(small_grid[1, 3], aspect = 1)
    # prior
    lines!(ax, [-1, -1, 1, 1], [0, 1, 1, 0]; kws_prior...)
    # posterior curve
    lines!(ax, peaknorm(K_ε); kws_like...)
    ylims!(ax, 0, nothing)
    ax.xlabel = L"\varepsilon"
    ax.xticks = -1:1
    ax.yticks = 0:1
    ax
end

ax_αd = let ax = Axis(small_grid[2, 3], aspect = 1)
    # prior
    lines!(ax, [-1, -1, 0, 0], [0, 1, 1, 0]; kws_prior...)
    # posterior curve
    lines!(ax, peaknorm(K_αd); kws_like...)
    ylims!(ax, 0, nothing)
    ax.xlabel = L"\alpha_d"
    ax.yticks = 0:1
    ax
end

ax_αs = let ax = Axis(small_grid[3, 3], aspect = 1)
    # prior
    lines!(ax, [-1, -1, 0, 0], [0, 1, 1, 0]; kws_prior...)
    # posterior curve
    lines!(ax, peaknorm(K_αs); kws_like...)
    ylims!(ax, 0, nothing)
    ax.xlabel = L"\alpha_s"
    ax.yticks = 0:1
    ax
end

# adjust the small grid to also have square panels
for ii in 1:3
    colsize!(small_grid, ii, Aspect(2, 1))
end
# grow the column spacing to better match the vertical spacing (which includes labels)
colgap!(small_grid, Fixed(44))
# then keep the small grid top-right aligned w.r.t. the main figure grid, since the
# forced aspect ratios require some padding be added somewhere
small_grid.valign = :top
small_grid.halign = :right

save("index.svg", fig, backend = CairoMakie)