Profile likelihoods
A profile likelihood fixes one parameter \(\theta\) on a grid and, at each grid value, finds the best fit over all the others:
Unlike a marginalised posterior, it does not depend on the prior volume of the other parameters. That matters when a posterior is pulled by prior volume (for example by \(\sum m_\nu\) near zero, or by early dark energy). For a single Gaussian parameter, \(\Delta\chi^2 \le 1\) is the 68% interval and \(\Delta\chi^2 \le 4\) is the 95% one.
How cosmonuts computes it
Every grid point and every start runs as one batched L-BFGS call, the same compiled
program as cosmonuts maximize, on CPU or GPU. It does not step along the grid one point
at a time. A 41-point profile with 4 starts per point is a single call of 164 gradient-based
optimisations.
- The global best fit is found first. Every grid point starts from it, with the profiled
parameter set to the grid value, and from
num_starts - 1draws from the prior. - Sweeps then restart each grid point from its neighbours' optima and keep whichever is
better. They repeat until no point improves (at most
sweepstimes). A grid point stuck in a local minimum shows up as a kink, and a neighbour's solution removes it. - If a grid point does better than the global best fit, it becomes the reference \(\chi^2_\mathrm{min}\).
By default the priors are left out (ignore_prior: true), which is the frequentist profile.
Set ignore_prior: false (--keep-prior on the command line) to keep them, for example
Gaussian priors on nuisance parameters.
Example: \(w_0\) from DESI DR2 BAO and the Planck PR4 CMB
Add a profile block to the run file. examples/w0wacdm_desi_dr2_cmb.yaml has one:
profile:
params: {w0: {min: -1.4, max: -0.2, n: 41}} # or w0: [values...]; two parameters for a 2-D grid
num_starts: 4 # per grid point: the best fit, then prior draws
sweeps: 3 # neighbour restarts (0: none)
cosmonuts profile examples/w0wacdm_desi_dr2_cmb.yaml
cosmonuts profile examples/w0wacdm_desi_dr2_cmb.yaml -p wa=-2.5:0.5:31 -f # another grid
uv run --group plots cosmonuts plot profile chains/examples/w0wacdm_desi_dr2_cmb
import numpy as np
from cosmonuts.config import run_profile
from cosmonuts.profile import profile
model, prof, paths = run_profile("examples/w0wacdm_desi_dr2_cmb.yaml")
# or, on any Model:
prof = profile(model, {"w0": np.linspace(-1.4, -0.2, 41)}, num_starts=4)
prof.delta_chi2 # one value per grid point
prof.interval(1.0) # (lower, upper); None on a side the grid leaves open
prof.params["wa"] # every parameter at each grid point's optimum
With an output, a run writes
<output>.profile.txt: one row per grid point, with the profiled parameter(s),chi2,delta_chi2,converged,chi2__<likelihood>for each likelihood, and then the other parameters;<output>.profile.yaml: the global best fit, \(\chi^2_\mathrm{min}\), the \(\Delta\chi^2 = 1, 4\) intervals and the settings.
Things to check
- Convergence. The table and
cosmonuts plot profilereport the grid points whose best start did not reach the gradient tolerance. Raisemax_stepsor loosentolfor them. - Warnings. A run warns (in the summary, in
.profile.yamland as PythonUserWarnings) in two cases. The first is when the sweeps stop at thesweepslimit while still improving points; raisesweeps. The second is when grid points lie above the trends of both of their neighbours (markedsuspectin the table). A true profile is the lower envelope of smooth branches, so such points are fits left in a worse local minimum. That often means the likelihood has a narrow second valley. Raisenum_starts, and look at the model there. In w0waCDM, for example, a valley at \(w_0 + w_a > 0\) appears where the CMB compression no longer holds, andexamples/w0wacdm_desi_dr2_cmb_w0pwa.yamlsamples \(w_0 + w_a < 0\) to remove it. - Grid edges. Grid values must lie strictly inside a uniform prior. An interval that reaches the grid's edge is reported as open, so widen the grid.
- Two parameters.
profile.paramswith two entries profiles over their outer product.cosmonuts plot profiledraws 1-D profiles only for now.