evermore for ATLAS#

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

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

Simple Example (pyhf)#

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

import pyhf


workspace_json = {
    "channels": [
        { "name": "singlechannel",
          "samples": [
            { "name": "signal",
              "data": [12.0, 11.0],
              "modifiers": [ { "name": "mu", "type": "normfactor", "data": None} ]
            },
            { "name": "background",
              "data": [50.0, 52.0],
              "modifiers": [ {"name": "bkg_norm", "type": "normsys", "data": { "hi": 1.1, "lo": 0.9 }} ]
            }
          ]
        }
    ],
    "observations": [
        { "name": "singlechannel", "data": [51.0, 48.0] }
    ],
    "measurements": [
        { "name": "Measurement", "config": {"poi": "mu", "parameters": []} }
    ],
    "version": "1.0.0"
}

model = pyhf.Workspace(workspace_json).model()

# eval model to get expectation
model.expected_data(model.config.suggested_init(), include_auxdata=False)
# -> array([62., 63.])

model.expected_data([0.5, 1.12], include_auxdata=False)
# -> array([61.63265822, 63.35796454])
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, 11.0]), "background": jnp.array([50.0, 52.0])}

data = jnp.array([51.0, 48.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., 63.], dtype=float64)

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

Modifier types#

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

Uncorrelated Shape (shapesys)#

See shapesys.

{
  "name": "mod_name",
  "type": "shapesys",
  "data": [1.0, 1.5, 2.0]
}
from jaxtyping import Array
import jax.numpy as jnp
import evermore as evm


def fun(parameter: evm.Parameter, hist: Array) -> Array:
    return hist + parameter.get_value() * jnp.array([1.0, 1.5, 2.0])

modifier = evm.Modifier(
    parameter=evm.NormalParameter(value=[0.0, 0.0, 0.0]),
    effect=evm.effect.Lambda(fun, normalize_by="offset")
)

Correlated Shape (histosys)#

See histosys.

{
  "name": "mod_name",
  "type": "histosys",
  "data": { "hi_data": [20, 15], "lo_data": [10, 10] }
}
import jax.numpy as jnp
import evermore as evm


param = evm.NormalParameter()

modifier = evm.Modifier(
    parameter=param,
    effect=evm.effect.VerticalTemplateMorphing(
        up_template=[20, 15],
        down_template=[10, 10],
    )
)

# or short-hand:
modifier = param.morphing(
    up_template=[20, 15],
    down_template=[10, 10],
)

Normalisation Uncertainty (normsys)#

See normsys.

{
  "name": "mod_name",
  "type": "normsys",
  "data": { "hi": 1.1, "lo": 0.9 }
}
import jax.numpy as jnp
import evermore as evm


param = evm.NormalParameter()

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

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

MC Statistical Uncertainty (staterror)#

See staterror.

{
  "name": "mod_name",
  "type": "staterror",
  "data": [0.1]
}
import jax.numpy as jnp
import evermore as evm


param = evm.NormalParameter()

# exemplary histogram with yield=10 and absolute uncertainty=0.1
hist = jnp.array([10.0])
rel_unc = 0.1 / hist

modifier = evm.Modifier(
    parameter=param,
    effect=evm.effect.Linear(offset=1, slope=rel_unc),
)

# or short-hand:
modifier = param.scale(offset=1, slope=rel_unc)

Luminosity (lumi)#

This modifier type can be implemented in the same way as the normsys modifier type, see Normalisation Uncertainty (normsys). See lumi.

Unconstrained Normalisation (normfactor)#

See normfactor.

{
  "name": "mod_name",
  "type": "normfactor",
  "data": null
}
import jax.numpy as jnp
import evermore as evm


param = evm.Parameter()

modifier = evm.Modifier(
    parameter=param,
    effect=evm.effect.Linear(offset=0, slope=1),
)

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

Data-driven Shape (shapefactor)#

See shapefactor.

{
  "name": "mod_name",
  "type": "shapefactor",
  "data": null
}
import jax.numpy as jnp
import evermore as evm


# param.get_value() is the shape factor, one value per bin
hist = jnp.array([10.0, 20.0, 30.0])
param = evm.Parameter(value=[0.0, 0.0, 0.0])

modifier = evm.Modifier(
    parameter=param,
    effect=evm.effect.Linear(offset=0, slope=1),
)

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