Import a pyhf model¶
from_pyhf converts a pyhf model or workspace into an ordinary FlatPPL module.
The bundled Rust converter performs the conversion and does not need the Python
pyhf package. model.pyhf then lays the module out the way pyhf lays out its
model, and model.logdensity("likelihood") prepares it for fits and samplers.
Start in float64¶
A pyhf likelihood sums many Poisson and constraint terms. A comparison of
absolute log likelihoods with pyhf therefore uses float64, where float32 rounding
would hide the agreement; FlatPPL itself runs in either precision. Pass
dtype="float64", compile and call inside jax.enable_x64(True), and select
pyhf’s 64-bit backend:
import json
import jax
import numpy as np
import pyhf
from flatppl import from_pyhf
pyhf.set_backend("numpy", precision="64b")
spec = pyhf.simplemodels.uncorrelated_background(
signal=[5, 10], bkg=[50, 60], bkg_uncertainty=[5, 12]
).spec
workspace = {
**spec,
"observations": [{"name": "singlechannel", "data": [52.0, 63.0]}],
"measurements": [{"name": "fit", "config": {"poi": "mu", "parameters": []}}],
"version": "1.0.0",
}
model = from_pyhf(json.dumps(workspace))
reference = pyhf.Workspace(workspace).model()
data = pyhf.Workspace(workspace).data(reference)
view = model.pyhf
pars = np.array(view.suggested_init())
with jax.enable_x64(True):
logpdf = view.logpdf(dtype="float64")
expected = view.expected_data(dtype="float64")
value, counts = float(logpdf(pars)), np.asarray(expected(pars))
np.testing.assert_allclose(value, reference.logpdf(pars, data)[0], rtol=1e-12)
np.testing.assert_allclose(counts, reference.expected_actualdata(pars), rtol=1e-12)
print(round(value, 6), counts)
-12.087693 [55. 70.]
Both functions agree with pyhf’s Model.logpdf and Model.expected_actualdata
to 1e-12. They are ordinary JAX functions of pyhf’s flat parameter vector and
work under jax.jit, jax.grad and jax.vmap. Compilation defaults to
float32, which suits sampling but not absolute comparisons with pyhf.
from_pyhf also accepts a path or an open file. Pass json.dumps(pyhf_model.spec)
to import a model already held by pyhf. Model-only documents declare each
channel’s observations as an external input. Workspaces retain their observed
counts. For workspaces with several measurements, the converter uses the first
measurement’s configuration, as pyhf does; pass measurement="name" to select
another.
Read pyhf’s layout¶
Every converted module carries each measurement’s fit settings and channel
layout as a literal record. model.pyhf reads the selected one as a
PyhfView, and model.parameters adds the same settings
to each Parameter. Neither reads the pyhf package. The
view’s parameter order, flat vector, auxiliary data and channel order are those
of pyhf.Model.config:
print(view.par_order, view.poi_index, view.suggested_bounds())
print(model.parameters["uncorr_bkguncrt"].centre, view.par_map["mu"].type)
('mu', 'uncorr_bkguncrt') 0 [(0.0, 10.0), (1e-10, 10.0), (1e-10, 10.0)]
[100. 25.] normfactor
view.expected_data(include_auxdata=True) appends the expected auxiliary data,
as pyhf’s Model.expected_data does. Each Parameter carries its constraint
family, width and centre.
Fit and sample¶
model.logdensity("likelihood") takes the likelihood over its inputs, the
parameters, as a record. The measurement’s settings supply the start, the fit
bounds and the fixed components, so the density starts at pyhf’s
suggested_init. With the default bounds=True, each parameter’s set narrows
to its fit bounds:
with jax.enable_x64(True):
fit = model.logdensity("likelihood", unconstrained=False, dtype="float64")
at_init = float(fit(fit.init))
lower, upper = fit.bounds
print(sorted(fit.names), float(fit.init["mu"]), float(upper["mu"]))
np.testing.assert_allclose(at_init, value, rtol=1e-12)
['mu', 'uncorr_bkguncrt'] 1.0 10.0
fit.potential is the negated log likelihood that Optax and iminuit minimize,
and fit.flat lays it out on one vector. In unconstrained coordinates the fit
bounds select a scaled logistic chart. Use jacobian=False there for maximum
likelihood, and keep the default for sampling. The Optax
and iminuit pages show both.
The likelihood includes the observation and auxiliary constraint terms with
their normalization constants. It matches pyhf’s full Model.logpdf, not a
likelihood ratio. Measurement bounds and initial values are fit configuration;
they are not Bayesian priors. Write a prior explicitly before sampling a
posterior.
External observations and auxiliary data¶
observations="external" leaves every channel’s counts as an input, and
auxiliary="external" reads every constraint’s centre from one record input,
pyhf_auxdata. One module then serves observed data, toys and Asimov data.
view.logpdf() then takes the external values as pyhf’s data vector: the
main counts in channel order, then the auxiliary data in view.auxdata_order.
external = from_pyhf(
json.dumps(workspace), observations="external", auxiliary="external"
)
with jax.enable_x64(True):
external_value = float(external.pyhf.logpdf(dtype="float64")(pars, data))
np.testing.assert_allclose(external_value, reference.logpdf(pars, data)[0], rtol=1e-12)
To bind the inputs before compiling instead, call Module.set. The nominal
centres are each parameter’s centre:
centres = {
p.name: p.centre for p in external.parameters.values() if p.centre is not None
}
bound = external.set(singlechannel_observed=[52.0, 63.0], pyhf_auxdata=centres)
with jax.enable_x64(True):
bound_value = float(bound.pyhf.logpdf(dtype="float64")(pars))
np.testing.assert_allclose(bound_value, value, rtol=1e-12)
Names¶
Inspect model.source and model.bindings for the generated names. Channel
names determine expected-count and observation bindings. Parameter names
outside FlatPPL’s binding grammar receive unique valid names, with a
pyhf_parameter_names record mapping them to their original strings.
model.pyhf.par_map relates pyhf’s flat parameter vector to the imported scalar
and vector parameters, by pyhf name. Do not assume that binding order equals
pyhf parameter order.
Unsupported pyhf constructs raise a conversion diagnostic instead of being silently omitted. See the Rust converter documentation for its supported import profile.