Issue in 2D model unbinned data construction #483
-
Bugs I'm trying to move an analysis model build with RooFit to zfit, import numpy as np
import pandas as pd
import zfit as zf
import zfit.z as zb
import zfit.z.numpy
class Simple2DModel(zf.pdf.ZPDF):
_N_OBS = 2
_PARAMS = ("a", "b", "c", "sigma")
def _unnormalized_pdf(self, x):
x, y = x.unstack_x()
mean = self.params["c"] + self.params["b"] * x + self.params["a"] * zb.square(x)
model = zf.pdf.Gauss(mean, self.params["sigma"], y)
return model
a = zf.Parameter("a", 0, -1, 1)
b = zf.Parameter("b", 0.1, 0, 1)
c = zf.Parameter("c", 3, 0, 10)
sigma = zf.Parameter("sigma", 0.1, 0.0001, 0.5)
x = zf.Space("x", (0, 10))
y = zf.Space("y", (0, 100))
space = x * y
model = Simple2DModel(space, "model", a=a, b=b, c=c, sigma=sigma)
rng = np.random.default_rng()
xdata = rng.uniform(0, 10, 1<<15)
mu = 0.001 * xdata ** 2 + 0.12 * xdata + 4
ydata = rng.normal(mu, 0.2341, 1<<15)
df = pd.DataFrame({
"x": xdata, "y": ydata
})
fit_data = zf.Data.from_pandas(df, space)
nll = zf.loss.UnbinnedNLL(model=model, data=fit_data) Current BehaviourThe example can be run in the terminal and throw the following error:
I don't know enough about how the lib works to understand the full traceback 😓 Context (Environment)
|
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 3 replies
-
Hi @zazbone thanks for reporting this! This should be used slightly different, I would say the best way is probably to use
(assuming that mean is meant to be the mean of the gaussian and y what is usually the data) Does that do the trick? |
Beta Was this translation helpful? Give feedback.
-
Hi Jonas, Ok, it works, so it is not mandatory to use only backend independent functions from zfit.z ? In my use case, the model that I use is a PDF sum (with a fraction) of a crystal ball and a Gaussian dist. Thanks |
Beta Was this translation helpful? Give feedback.
-
(moved this to be a discussion)
It is not, but recomendable. You could implement the Gauss just yourself (exp(...)).
They are. If you're interested to use them (and you could also use them in this case), you can create a functor, but you can't directly make auto-compositions of variables such as RooFit does (on the other hand, anything custom is rather simple to implement).
No, the SumPDF is the way to go. Behind the scenes, everything works with TensorFlow and using the builtin PDFs is by far the best way to go if you can! The problem is maybe that RooFit mixes the model building with the functions, i.e. you can easily transform a variable with a function and directly use that as a variable - in zfit this works for parameters but not if you want to normalize over it (as this changes a lot of the normalization logic). |
Beta Was this translation helpful? Give feedback.
-
Hi, sorry for the late response. I tried to create a single model class with that information, but unfortunately, I wasn't successful. As a result, I resorted to using a sum of 2 PDFs instead. This is what it looks like if you're interested. import zfit as zf
from zfit import z as zb
from zfit.models.physics import crystalball_func
class ParametrizedGaussian(zf.pdf.ZPDF):
"""Implementation of partialy reconstructed STEREO neutrino events"""
_N_OBS = 2
_PARAMS = (
ENERGY_SHIFT,
MEAN0,
MEAN1,
MEAN2,
RESOLUTION0,
RESOLUTION1,
RESOLUTION_G2,
)
def _unnormalized_pdf(self, space):
etrue, ereco = space.unstack_x()
delta_etrue = etrue - self.params[ENERGY_SHIFT]
mean = (
self.params[MEAN0]
+ self.params[MEAN1] * delta_etrue
+ self.params[MEAN2] * zb.square(delta_etrue)
)
sigma = zb.sqrt(
self.params[RESOLUTION0] * mean
+ self.params[RESOLUTION1]
+ self.params[RESOLUTION_G2] * zb.square(mean)
)
return zb.exp(-0.5 * zb.square(ereco - mean) / zb.square(sigma))
class ParametrizedCrystalBall(zf.pdf.ZPDF):
"""Implementation of reconstructed and badly reconstructed STEREO neutrino events"""
_N_OBS = 2
_PARAMS = (
MEAN0,
MEAN1,
MEAN2,
RESOLUTION0,
RESOLUTION1,
RESOLUTION_CB2,
ALPHA0,
ALPHA1,
ALPHA2,
POW_CB,
)
def _unnormalized_pdf(self, space):
etrue, ereco = space.unstack_x()
mean = (
self.params[MEAN0]
+ self.params[MEAN1] * etrue
+ self.params[MEAN2] * zb.square(etrue)
)
sigma = zb.sqrt(
self.params[RESOLUTION0] * mean
+ self.params[RESOLUTION1]
+ self.params[RESOLUTION_CB2] * zb.square(mean)
)
alpha = (
self.params[MEAN0]
+ self.params[MEAN1] * mean
+ self.params[MEAN2] * zb.square(mean)
)
return crystalball_func(ereco, mean, sigma, alpha, self.params[POW_CB])
def model(space, name, params):
gaussian = ParametrizedGaussian(
space,
name="STEREO gaussian",
**{
name: var
for name, var in params.items()
if name in ParametrizedGaussian._PARAMS
}
)
crystal_ball = ParametrizedCrystalBall(
space,
name="STEREO crystal ball",
**{
name: var
for name, var in params.items()
if name in ParametrizedCrystalBall._PARAMS
}
)
return zf.pdf.SumPDF(
(gaussian, crystal_ball), params[MODEL_FRAC], obs=space, name=name
) I could only try with the Minuit minimizer due to the lack of a CUDA capable GPU. However, it reproduces RooFit results perfectly. The first step was a success! Thanks a lot. |
Beta Was this translation helpful? Give feedback.
(moved this to be a discussion)
It is not, but recomendable. You could implement the Gauss just yourself (exp(...)).
They are. If you're interested to use them (and you could also use them in this case), you can create a functor, but you can't directly make auto-compositions of variables such as RooFit does (on the other hand, anything custom is rather simple to implement).