evermore for CMS#

If you are coming from the CMS experiment, you are probably familiar with the \(\Combine\) project.

In the following, you will find a brief comparison how modifier types of \(\Combine\) can be implemented in evermore.

Simple Example (Combine)#

This is a simple one-bin example of a signal process scaled by an unconstrained modifier \(\mu\) and a background process with a normalization uncertainty (lnN).

datacard.txt #
# run this datacard with the combine tool
imax 1  number of channels
jmax 1  number of processes -1
kmax *  number of nuisance parameters (sources of systematical uncertainties)
-------
bin                   bin1
observation           51
-------
bin                   bin1    bin1
process               signal  background
process               0       1
rate                  12      50
-------
bkg_norm    lnN       -       0.9/1.1
import jax
import jax.numpy as jnp
import evermore as evm


jax.config.update("jax_enable_x64", True)

params = {"mu": evm.Parameter(value=1.0), "bkg_norm": evm.NormalParameter(value=0.0)}

hists = {"signal": jnp.array([12.0]), "background": jnp.array([50.0])}

data = jnp.array([51.0])


def model(params: dict, hists: dict) -> jnp.ndarray:
    mu_modifier = params["mu"].scale()
    syst_modifier = params["bkg_norm"].scale_log_asymmetric(up=1.1, down=0.9)
    return mu_modifier(hists["signal"]) + syst_modifier(hists["background"])


# eval model to get expectation
model(params, hists)
# -> Array([62.], dtype=float64)

model({"mu": evm.Parameter(value=0.5), "bkg_norm": evm.NormalParameter(value=1.12)}, hists)
# -> Array([61.63265822], dtype=float64)

Modifier/Effect Types#

For a more detailed overview of modifier/effect types in \(\Combine\), please refer to the \(\Combine\) documentation.

Normalization Effects (lnN)#

See normalization effects.

Symmetric exponential#

datacard.txt #
[...]
-------
norm_sys   lnN   1.1
import jax.numpy as jnp
import evermore as evm


param = evm.NormalParameter()

norm_sys = evm.Modifier(
    parameter=param,
    effect=evm.effect.SymmetricExponential(kappa=1.1),
)

# or short-hand:
norm_sys = param.scale_log_symmetric(kappa=1.1)

Asymmetric exponential#

datacard.txt #
[...]
-------
norm_sys   lnN   0.9/1.1
import jax.numpy as jnp
import evermore as evm


param = evm.NormalParameter()

norm_sys = evm.Modifier(
    parameter=param,
    effect=evm.effect.AsymmetricExponential(up=1.1, down=0.9),
)

# or short-hand:
norm_sys = param.scale_log_asymmetric(up=1.1, down=0.9)

Shape Morphing Effects (shape)#

See shape morphing effects.

datacard.txt #
shapes * * shapes.root $PROCESS $PROCESS_$SYSTEMATIC
[...]
-------
shape_sys   shape   1.0
import jax.numpy as jnp
import evermore as evm


param = evm.NormalParameter()

shape_sys = evm.Modifier(
    parameter=param,
    effect=evm.effect.VerticalTemplateMorphing(
        up_template=[...],  # histogram from `shapes.root`, matching `$PROCESS $PROCESS_$SYSTEMATIC` ("Up") pattern
        down_template=[...],  # histogram from `shapes.root`, matching `$PROCESS $PROCESS_$SYSTEMATIC` ("Down") pattern
    )
)

# or short-hand:
shape_sys = param.morphing(
    up_template=[...],  # histogram from `shapes.root`, matching `$PROCESS $PROCESS_$SYSTEMATIC` ("Up") pattern
    down_template=[...],  # histogram from `shapes.root`, matching `$PROCESS $PROCESS_$SYSTEMATIC` ("Down") pattern
)

Statistical Uncertainties (autoMCstats)#

See autoMCstats.

datacard.txt #
# run this datacard with the combine tool
imax 1  number of channels
jmax 1  number of processes -1
kmax *  number of nuisance parameters (sources of systematical uncertainties)
-------
bin                   bin1
observation           51
-------
bin                   bin1        bin1        bin1
process               signal      bkg1        bkg2
process               0           1           2
rate                  12          50          30
-------
bin1 autoMCStats 10 [include-signal = 0] [hist-mode = 1]

Please note that evermore is treating statistical uncertainties through Gaussian rather than Poisson modifiers, comparable to the behavior of pyhf.

import jax
import jax.numpy as jnp
import evermore as evm


# `hists` corresponds to sumw of the TH1 histograms
hists = {"signal": jnp.array([12]), "bkg1": jnp.array([50]), "bkg2": jnp.array([30])}

# `histsw2` corresponds to sumw2 of the TH1 histograms
histsw2 = {"signal": jnp.array([12]), "bkg1": jnp.array([50]), "bkg2": jnp.array([30])}


# setup modifiers (Barlow-Beeston-full)
staterrors = jax.tree.map(
  evm.staterror.StatErrors,
  hists,
  histsw2,
)

# apply the modifier scaling to the histograms
modified_signal_hist = staterrors["signal"](hists["signal"])
modified_bkg1_hist = staterrors["bkg1"](hists["bkg1"])
modified_bkg2_hist = staterrors["bkg2"](hists["bkg2"])

Rate Parameters (rateParam)#

See rateParam.

datacard.txt #
[...]
-------
sys   rateParam   bin1   process1  1.0   [0.0,2.0]
import jax.numpy as jnp
import evermore as evm


param = evm.Parameter(value=1, lower=0, upper=2)

sys = evm.Modifier(
    parameter=param,
    effect=evm.effect.Linear(slope=1.0, offset=0.0),
)

# or short-hand:
sys = param.scale()