Skip to content

Solver Adapters

cplots plots accept raw arrays, but they also accept solver adapter objects — wrappers that speak the cplots data model directly. Write the adapter once; use every plot type without manually converting arrays.

The abstraction lives in cplots.Observables.


The Observables interface

Observables is an abstract base class. Subclass it, implement three methods, and your solver becomes a first-class cplots data source.

Method Returns Required
background(**kwargs) BackgroundData yes
pk(z, **kwargs) PKData yes
cl(**kwargs) dict[str, ClData] yes
primordial(**kwargs) PKData no — raises NotImplementedError
thermodynamics(**kwargs) BackgroundData no — raises NotImplementedError

Minimal custom adapter

import numpy as np
import cplots
from cplots import Observables, BackgroundData, PKData, ClData

class MyCAMB(Observables):
    def __init__(self, results):
        self._r = results

    def background(self, **kwargs) -> BackgroundData:
        z, H = self._r.get_background()
        return BackgroundData(redshift=z, quantities={"H": H})

    def pk(self, z: float = 0.0, **kwargs) -> PKData:
        k, pk = self._r.get_matter_power(z=z)
        return PKData(x=k, y=pk, redshift=z)

    def cl(self, **kwargs) -> dict[str, ClData]:
        ell, cl_tt = self._r.get_cmb_cls()
        return {"tt": ClData(x=ell, y=cl_tt)}

out = MyCAMB(camb_results)
cplots.PKSpectrum(out.pk(z=0.0)).show()

The solver never knows about backends. The plots never know about the solver. The adapter is the only place that changes when you switch solvers.


Using ClassOutput

ClassOutput is the built-in adapter for CLASS via the classy Python package.

Optional dependency

ClassOutput requires classy. Install it with:

pip install cplots[classy]
or separately with pip install classy.

Setup

ClassOutput wraps a pre-computed classy.Class instance. Always call cosmo.compute() before wrapping.

from classy import Class
import cplots

cosmo = Class()
cosmo.set({
    "output": "tCl pCl lCl mPk",
    "lensing": "yes",
    "P_k_max_h/Mpc": 1.0,
    "z_max_pk": 2.0,
})
cosmo.compute()

out = cplots.ClassOutput(cosmo)

background()

Returns H(z), luminosity distance, angular diameter distance, and comoving distance by default.

bg = out.background()
cplots.BackgroundEvolution(bg).show()

Request specific quantities by name — the keys are exactly the column headers from cosmo.get_background():

bg = out.background(
    quantities=["H [1/Mpc]", "lum. dist."],
    labels={"H [1/Mpc]": "H(z)", "lum. dist.": r"$d_L$ [Mpc]"},
)

Default quantities: "H [1/Mpc]", "lum. dist.", "ang.diam.dist.", "comov. dist.".

pk()

Returns the matter power spectrum at a given redshift.

pk = out.pk(z=0.0, k_min=1e-3, k_max=1.0, n_k=300)
cplots.PKSpectrum(pk).show()

Key parameters:

Parameter Default Notes
z 0.0 Redshift
k_min 1e-4 Minimum wavenumber
k_max 1.0 Maximum wavenumber
n_k 200 Number of k points (log-spaced)
nonlinear False Requires non_linear in CLASS params
h_units True k in h/Mpc, P(k) in (Mpc/h)³

With h_units=True (the default), k is in h/Mpc and P(k) is in (Mpc/h)³ — the standard cosmological convention.

cl()

Returns CMB angular power spectra as a dict mapping spectrum name to ClData.

cls = out.cl(spectra=["tt", "ee", "te"], lensed=True)
cplots.ClSpectrum(cls).show()

Key parameters:

Parameter Default Notes
lensed True Lensed or unlensed spectra
spectra ["tt"] Any subset of "tt", "ee", "bb", "te", "pp"
l_max 2500 Maximum multipole
ell_factor True Apply ℓ(ℓ+1)/2π scaling
units "muK2" "muK2", "K2", or "dimensionless"

primordial()

Returns the primordial scalar power spectrum P_s(k). k is always in 1/Mpc.

prim = out.primordial()
cplots.PKSpectrum(prim).show()

No extra CLASS output flag is needed — cosmo.get_primordial() is always available after cosmo.compute().

thermodynamics()

Returns thermodynamic background quantities. The default is the visibility function "g [Mpc^-1]".

thermo = out.thermodynamics()
cplots.BackgroundEvolution(thermo).show()

Request additional columns with quantities:

thermo = out.thermodynamics(quantities=["g [Mpc^-1]", "Tb [K]"])

Using CambOutput

CambOutput is the built-in adapter for CAMB via the camb Python package.

Optional dependency

CambOutput requires camb. Install it with:

pip install cplots[camb]
or separately with pip install camb.

Setup

CambOutput wraps a pre-computed camb.CAMBdata results object. Always call camb.get_results(pars) before wrapping.

import camb
import cplots

pars = camb.set_params(
    H0=67.4, ombh2=0.022, omch2=0.12,
    mnu=0.06, omk=0, tau=0.054,
    As=2.1e-9, ns=0.965,
    lmax=2500,
    redshifts=[0.0, 0.5, 1.0],
    WantTransfer=True,
)
results = camb.get_results(pars)

out = cplots.CambOutput(results)

background()

Returns H(z) in km/s/Mpc, luminosity distance, angular diameter distance, and comoving distance by default. The redshift grid defaults to np.linspace(0, 3, 300).

bg = out.background()
cplots.BackgroundEvolution(bg).show()

Request specific quantities or supply a custom redshift array:

import numpy as np

bg = out.background(
    z=np.linspace(0, 2, 500),
    quantities=["H [km/s/Mpc]", "lum. dist."],
    labels={"lum. dist.": r"$d_L$ [Mpc]"},
)

Default quantities: "H [km/s/Mpc]", "lum. dist.", "ang.diam.dist.", "comov. dist.".

H(z) units differ from CLASS

ClassOutput returns H in 1/Mpc (the CLASS internal unit). CambOutput returns H in km/s/Mpc. The quantity key reflects this: "H [1/Mpc]" vs "H [km/s/Mpc]".

pk()

Returns the matter power spectrum at a given redshift using CAMB's get_matter_power_interpolator. Because an interpolator is used, z does not need to appear in the redshifts list passed to set_params.

pk = out.pk(z=0.0, k_min=1e-4, k_max=1.0, n_k=300)
cplots.PKSpectrum(pk).show()

Key parameters:

Parameter Default Notes
z 0.0 Redshift — any value, not limited to pre-computed list
k_min 1e-4 Minimum wavenumber
k_max 1.0 Maximum wavenumber
n_k 200 Number of k points (log-spaced)
nonlinear False Use non-linear P(k)
h_units True k in h/Mpc, P(k) in (Mpc/h)³

cl()

Returns CMB angular power spectra as a dict mapping spectrum name to ClData.

cls = out.cl(spectra=["tt", "ee", "te"], lensed=True)
cplots.ClSpectrum(cls).show()

Key parameters:

Parameter Default Notes
lensed True Lensed or unlensed spectra
spectra ["tt"] Any subset of "tt", "ee", "bb", "te", "pp"
l_max None Maximum multipole; defaults to the value set in set_params
ell_factor True Apply ℓ(ℓ+1)/2π scaling
units "muK2" "muK2", "K2", or "dimensionless"

primordial()

Computed analytically from results.Params.InitPower (As, ns, pivot_scalar). No extra CAMB output flag needed.

prim = out.primordial()
cplots.PKSpectrum(prim).show()

thermodynamics()

Not available. CAMB does not expose a visibility table equivalent to CLASS's thermodynamics output.

out.thermodynamics()  # raises NotImplementedError

Side-by-side comparison

from classy import Class
import cplots

cosmo = Class()
cosmo.set({
    "output": "tCl pCl lCl mPk",
    "lensing": "yes",
    "h": 0.674,
    "Omega_b": 0.022,
    "Omega_cdm": 0.120,
    "A_s": 2.1e-9,
    "n_s": 0.965,
    "tau_reio": 0.054,
    "z_max_pk": 2.0,
})
cosmo.compute()
out = cplots.ClassOutput(cosmo)

# background — H in 1/Mpc
bg = out.background(quantities=["H [1/Mpc]", "lum. dist."])

# matter power spectrum
pk = out.pk(z=0.0)

# CMB spectra
cls = out.cl(spectra=["tt", "ee"])

# thermodynamics (CLASS only)
thermo = out.thermodynamics()
import camb
import cplots

pars = camb.set_params(
    H0=67.4, ombh2=0.022, omch2=0.12,
    mnu=0.06, omk=0, tau=0.054,
    As=2.1e-9, ns=0.965,
    lmax=2500,
    redshifts=[0.0, 0.5, 1.0],
    WantTransfer=True,
)
results = camb.get_results(pars)
out = cplots.CambOutput(results)

# background — H in km/s/Mpc
bg = out.background(quantities=["H [km/s/Mpc]", "lum. dist."])

# matter power spectrum (interpolator — z is unrestricted)
pk = out.pk(z=0.0)

# CMB spectra
cls = out.cl(spectra=["tt", "ee"])

# thermodynamics — not available for CAMB
# out.thermodynamics()  # raises NotImplementedError

Writing your own adapter

Copy this skeleton and fill in the three required methods:

import cplots
from cplots import Observables, BackgroundData, PKData, ClData

class MySolverOutput(Observables):
    def __init__(self, solver_instance):
        self._s = solver_instance

    def background(self, **kwargs) -> BackgroundData:
        # Extract z and named quantity arrays from your solver.
        raise NotImplementedError

    def pk(self, z: float = 0.0, **kwargs) -> PKData:
        # Extract k and P(k) arrays from your solver.
        raise NotImplementedError

    def cl(self, **kwargs) -> dict[str, ClData]:
        # Return a dict like {"tt": ClData(...), "ee": ClData(...)}.
        raise NotImplementedError

Register nothing — every plot type already accepts PlotData objects, and that is all the adapter needs to return.