Creating your own pdf#

A core feature of zfit is the ability to create custom pdfs and functions in an simple and straightforward way.

There are two main possibilities to create a custom pdf, an easier for most use-cases and an advanced way..

The simple way#

While the same works for functions, an example with a PDF is shown here.

import numpy as np
import zfit
from zfit import z
/home/docs/checkouts/readthedocs.org/user_builds/zfit/envs/0.14.0/lib/python3.10/site-packages/zfit/__init__.py:63: UserWarning: TensorFlow warnings are by default suppressed by zfit. In order to show them, set the environment variable ZFIT_DISABLE_TF_WARNINGS=0. In order to suppress the TensorFlow warnings AND this warning, set ZFIT_DISABLE_TF_WARNINGS=1.
  warnings.warn(
/home/docs/checkouts/readthedocs.org/user_builds/zfit/envs/0.14.0/lib/python3.10/site-packages/tensorflow_addons/utils/tfa_eol_msg.py:23: UserWarning: 

TensorFlow Addons (TFA) has ended development and introduction of new features.
TFA has entered a minimal maintenance and release mode until a planned end of life in May 2024.
Please modify downstream libraries to take dependencies from other repositories in our TensorFlow community (e.g. Keras, Keras-CV, and Keras-NLP). 

For more information see: https://github.com/tensorflow/addons/issues/2807 

  warnings.warn(

The first way is the most simple and should only be used for the trivial cases, i.e. if you’re not familiar with Python classes (especially not with the __init__ method).

class MyGauss(zfit.pdf.ZPDF):
    _N_OBS = 1  # dimension, can be omitted
    _PARAMS = ['mean', 'std']  # the name of the parameters

    def _unnormalized_pdf(self, x):
        x = z.unstack_x(x)  # returns a list with the columns: do x, y, z = z.unstack_x(x) for 3D
        mean = self.params['mean']
        std = self.params['std']
        return z.exp(- ((x - mean) / std) ** 2)

Done. Now we can use our pdf already!

The slightly more general way involves overwritting the __init__ and gives you all the possible flexibility: to use custom parameters, to preprocess them etc.

Here we inherit from BasePDF

class MyGauss(zfit.pdf.BasePDF):

    def __init__(self, mean, std, obs, extended=None, norm=None, name=None):
        params = {'mean': mean,  # 'mean' is the name as it will be named in the PDF, mean is just the parameter to create the PDF
                  'std': std
                  }
        super().__init__(obs=obs, params=params, extended=extended, norm=norm,
                         name=name)

    def _unnormalized_pdf(self, x):
        x = z.unstack_x(x)
        mean = self.params['mean']
        std = self.params['std']
        return z.exp(- ((x - mean) / std) ** 2)
obs = zfit.Space('obs1', limits=(-3, 6))

data_np = np.random.random(size=1000)
data = zfit.data.Data.from_numpy(array=data_np, obs=obs)

Create two parameters and an instance of your own pdf

mean = zfit.Parameter("mean", 1.)
std = zfit.Parameter("std", 1.)
my_gauss = MyGauss(obs=obs, mean=mean, std=std)
probs = my_gauss.pdf(data)
print(probs[:20])
tf.Tensor(
[0.44226663 0.47667551 0.52671696 0.23459464 0.385608   0.40739544
 0.53826981 0.38089114 0.56335927 0.36379703 0.24401293 0.43852521
 0.52412687 0.22208587 0.56211924 0.25714124 0.37203857 0.2385682
 0.55888934 0.38588498], shape=(20,), dtype=float64)

If we want to make sure it’s a numpy array, we can use zfit.run

We could improve our PDF by registering an integral

def gauss_integral_from_any_to_any(limits, params, model):
    lower, upper = limits.limit1d
    mean = params['mean']
    std = params['std']
    # write your integral here
    return 42.  # dummy integral, must be a scalar!
limits = zfit.Space(axes=0, limits=(zfit.Space.ANY_LOWER, zfit.Space.ANY_UPPER))
MyGauss.register_analytic_integral(func=gauss_integral_from_any_to_any, limits=limits)

n-dimensional#

Advanced Custom PDF#

Subclass BasePDF. The _unnormalized_pdf has to be overriden and, in addition, the __init__.

Any of the public main methods (pdf, integrate, partial_integrate etc.) can always be overriden by implementing the function with a leading underscore, e.g. implement _pdf to directly controls pdf, the API is the same as the public function without the name. In case, during execution of your own method, it is found to be a bad idea to have overridden the default methods, throwing a NotImplementedError will restore the default behavior.

# TOBEDONE