{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# Custom models\n",
    "\n",
    "All elements of zfit are built to be easily customized. Especially models offer many possibilities to be implemented by the user; in the end, regardless of how many models are provided by a library and of how many things are though, there is always a use-case that was not thought of. High flexibility is therefore a crucial aspect.\n",
    "\n",
    "This has disadvantages: the more freedom a model takes for itself, the less optimizations are potentially available. But this is usually not noticeable.\n",
    "\n",
    "## Creating a model\n",
    "\n",
    "Following the philosophy of zfit, there are different levels of customization. For the most simple use-case, all we need to do is to provide a function describing the shape and the name of the parameters. This can be done by overriding `_unnormalized_pdf`.\n",
    "\n",
    "To implement a mathematical function in zfit, TensorFlow or z should be used. The latter is a subset of TensorFlow and improves it in some aspects, such as automatic dtype casting, and therefore preferred to use.\n",
    "(_There are other ways to use arbitrary Python functions, they will be discussed later on_)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "import zfit\n",
    "from zfit import z\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "We can start with a simple model and implement a custom second order polynomial. Therefore we need to inherit from the right base class, the simpler one is `ZPDF`.\n",
    "\n",
    "For a minimal example, we need to override only `_unnormalized_pdf` and specify a list of parameters.\n",
    "\n",
    "`_unnormalized_pdf` gets (currently) one argument, x. This is a zfit `Data` object and should first be unstacked. If it is one dimensional - such as here - it will return a single Tensor, otherwise a list of Tensors that can directly be unpacked."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "class SecondOrderPoly(zfit.pdf.ZPDF):\n",
    "    \"\"\"Second order polynomial `a + b * x + c * x^2`\"\"\"\n",
    "    _PARAMS = ['b', 'c']  # specify which parameters to take\n",
    "\n",
    "    def _unnormalized_pdf(self, x):  # implement function, unnormalized\n",
    "        data = z.unstack_x(x)\n",
    "        b = self.params['b']\n",
    "        c = self.params['c']\n",
    "\n",
    "        return 1 + b * data + c * data ** 2\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "Note that we omitted _consciously_ any attempt to normalize the function, as this is usually done over a specific range. Also, no analytic sampling or integration has to be provided. The model handles all of this internally automatically and we have the full functionality available.\n",
    "\n",
    "First, we can instantiate the model:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "obs = zfit.Space(\"obs1\", limits=(-4, 4))\n",
    "\n",
    "b = zfit.Parameter('b', 0.2, 0.1, 10)\n",
    "custom_poly = SecondOrderPoly(obs=obs, b=b, c=1.4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "which lets us now fully access all the main methods of a model:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "integral=[0.11071254], sample=<zfit.Data: Data obs=('obs1',)>, prob=[0.16986766 0.23603968 0.31030257 0.31967185 0.09241075 0.24815852\n",
      " 0.30755462 0.24714592 0.31692639 0.33132893]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "integral = custom_poly.integrate(limits=(-1, 2))\n",
    "sample = custom_poly.sample(n=1000)\n",
    "prob = custom_poly.pdf(sample)\n",
    "print(f\"integral={integral}, sample={sample}, prob={prob[:10]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## What happened?\n",
    "\n",
    "The model tries to use analytical functions for integration and sampling _if available_, otherwise (as happened above), it falls back to the numerical methods. To improve our model, we can add an analytic integral, a common use case. This has to be the _integral over the `_unnormalized_pdf`_."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "# define the integral function\n",
    "def cdf_poly(limit, b, c):\n",
    "    return limit + 0.5 * b * limit ** 2 + 1 / 3 * c * limit ** 3\n",
    "\n",
    "def integral_func(limits, norm_range, params, model):\n",
    "\n",
    "    b = params['b']\n",
    "    c = params['c']\n",
    "\n",
    "    lower, upper = limits.limit1d\n",
    "    lower = z.convert_to_tensor(lower)  # the limits are now 1-D, for axis 1\n",
    "    upper = z.convert_to_tensor(upper)\n",
    "\n",
    "    # calculate the integral\n",
    "    integral = cdf_poly(upper, b, c) - cdf_poly(lower, b, c)\n",
    "    print(\"Integral called\")\n",
    "    return integral\n",
    "\n",
    "\n",
    "# define the space over which it is defined. Here, we use the axes\n",
    "integral_limits = zfit.Space(axes=(0,), limits=(zfit.Space.ANY, zfit.Space.ANY))\n",
    "\n",
    "SecondOrderPoly.register_analytic_integral(func=integral_func, limits=integral_limits)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "poly2 = SecondOrderPoly(obs=obs, b=b, c=1.2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.6/dist-packages/ipykernel/__main__.py:10: UserWarning: The function <function Space.limit1d at 0x7f5f1695d6a8> may does not return the actual area/limits but rather the rectangular limits. <zfit Space obs=('obs1',), axes=(0,), limits=(array([[-1.]]), array([[2.]]))> can also have functional limits that are arbitrarily defined and lay inside the rect_limits. To test if a value is inside, use `inside` or `filter`.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Integral called"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Integral called"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Integral called"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "integral=[0.11071254], sample=<zfit.Data: Data obs=('obs1',)>, prob=[0.16986766 0.23603968 0.31030257 0.31967185 0.09241075 0.24815852\n",
      " 0.30755462 0.24714592 0.31692639 0.33132893]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "integral_analytic = custom_poly.integrate(limits=(-1, 2))\n",
    "sample = custom_poly.sample(n=1000)\n",
    "prob_analytic = custom_poly.pdf(sample)\n",
    "print(f\"integral={integral}, sample={sample}, prob={prob[:10]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## Multiple dimensions and parameters with angular observables\n",
    "\n",
    "So far, we used rather simple examples and many basic shapes, such as polynomials, already have an efficient implementation within zfit. Therefore, we will now create a three dimensional PDF measuring the angular observables of a $B^+ \\rightarrow K^* l l$ decay.\n",
    "\n",
    "The implementation is not \"special\" or complicated at all, it rather shows how to deal with multiple dimensions and how to manage several parameters. It was created using the equation of the angular observables (taken from a paper).\n",
    "\n",
    "_Many thanks to Rafael Silva Coutinho for the implementation!_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "class AngularPDF(zfit.pdf.ZPDF):\n",
    "    \"\"\"Full d4Gamma/dq2dOmega for Bd -> Kst ll (l=e,mu)\n",
    "\n",
    "    Angular distribution obtained in the total PDF (using LHCb convention JHEP 02 (2016) 104)\n",
    "        i.e. the valid of the angles is given for\n",
    "            - phi: [-pi, pi]\n",
    "            - theta_K: [0, pi]\n",
    "            - theta_l: [0, pi]\n",
    "\n",
    "        The function is normalized over a finite range and therefore a PDF.\n",
    "\n",
    "        Args:\n",
    "\n",
    "            FL (`zfit.Parameter`): Fraction of longitudinal polarisation of the Kst\n",
    "            S3 (`zfit.Parameter`): A_perp^2 - A_para^2 / A_zero^2 + A_para^2 + A_perp^2 (L, R)\n",
    "            S4 (`zfit.Parameter`): RE(A_zero*^2 * A_para^2) / A_zero^2 + A_para^2 + A_perp^2 (L, R)\n",
    "            S5 (`zfit.Parameter`): RE(A_zero*^2 * A_perp^2) / A_zero^2 + A_para^2 + A_perp^2 (L, R)\n",
    "            AFB (`zfit.Parameter`): Forward-backward asymmetry of the di-lepton system (also i.e. 3/4 * S6s)\n",
    "            S7 (`zfit.Parameter`): IM(A_zero*^2 * A_para^2) / A_zero^2 + A_para^2 + A_perp^2 (L, R)\n",
    "            S8 (`zfit.Parameter`): IM(A_zero*^2 * A_perp^2) / A_zero^2 + A_para^2 + A_perp^2 (L, R)\n",
    "            S9 (`zfit.Parameter`): IM(A_perp*^2 * A_para^2) / A_zero^2 + A_para^2 + A_perp^2 (L, R)\n",
    "            obs (`zfit.Space`):\n",
    "            name (str):\n",
    "            dtype (tf.DType):\n",
    "    \"\"\"\n",
    "\n",
    "    _PARAMS = ['FL', 'S3', 'S4', 'S5', 'AFB', 'S7', 'S8', 'S9']\n",
    "    _N_OBS = 3\n",
    "\n",
    "    def _unnormalized_pdf(self, x):\n",
    "        FL = self.params['FL']\n",
    "        S3 = self.params['S3']\n",
    "        S4 = self.params['S4']\n",
    "        S5 = self.params['S5']\n",
    "        AFB = self.params['AFB']\n",
    "        S7 = self.params['S7']\n",
    "        S8 = self.params['S8']\n",
    "        S9 = self.params['S9']\n",
    "\n",
    "        costheta_l, costheta_k, phi = z.unstack_x(x)\n",
    "\n",
    "        sintheta_k = tf.sqrt(1.0 - costheta_k * costheta_k)\n",
    "        sintheta_l = tf.sqrt(1.0 - costheta_l * costheta_l)\n",
    "\n",
    "        sintheta_2k = (1.0 - costheta_k * costheta_k)\n",
    "        sintheta_2l = (1.0 - costheta_l * costheta_l)\n",
    "\n",
    "        sin2theta_k = (2.0 * sintheta_k * costheta_k)\n",
    "        cos2theta_l = (2.0 * costheta_l * costheta_l - 1.0)\n",
    "        sin2theta_l = (2.0 * sintheta_l * costheta_l)\n",
    "\n",
    "        pdf = ((3.0 / 4.0) * (1.0 - FL) * sintheta_2k +\n",
    "               FL * costheta_k * costheta_k +\n",
    "               (1.0 / 4.0) * (1.0 - FL) * sintheta_2k * cos2theta_l +\n",
    "               -1.0 * FL * costheta_k * costheta_k * cos2theta_l +\n",
    "               S3 * sintheta_2k * sintheta_2l * tf.cos(2.0 * phi) +\n",
    "               S4 * sin2theta_k * sin2theta_l * tf.cos(phi) +\n",
    "               S5 * sin2theta_k * sintheta_l * tf.cos(phi) +\n",
    "               (4.0 / 3.0) * AFB * sintheta_2k * costheta_l +\n",
    "               S7 * sin2theta_k * sintheta_l * tf.sin(phi) +\n",
    "               S8 * sin2theta_k * sin2theta_l * tf.sin(phi) +\n",
    "               S9 * sintheta_2k * sintheta_2l * tf.sin(2.0 * phi))\n",
    "\n",
    "        return pdf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "#### Multidimensional Spaces\n",
    "\n",
    "This PDF now expects multidimensional data. Therefore, we need to provide a Space in multiple dimensions. The preferred way is to use the product operations to build this space from one dimensional `Space`s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "costhetha_k = zfit.Space('costheta_k', (-1, 1))\n",
    "costhetha_l = zfit.Space('costheta_l', (-1, 1))\n",
    "phi = zfit.Space('phi', (-np.pi, np.pi))\n",
    "angular_obs = costhetha_k * costhetha_l * phi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "#### Managing parameters\n",
    "\n",
    "Luckily, we're in Python, which provides many tools out-of-the-box. Handling parameters in a `dict` can make things very easy, even for several parameters as here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "params_init = {'FL':  0.43, 'S3': -0.1, 'S4': -0.2, 'S5': -0.4, 'AFB': 0.343, 'S7': 0.001, 'S8': 0.003, 'S9': 0.002}\n",
    "params = {name: zfit.Parameter(name, val, -1, 1) for name, val in params_init.items()}\n",
    "angular_pdf = AngularPDF(obs=angular_obs, **params)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "integral=[0.11071254], sample=<zfit.Data: Data obs=('costheta_k', 'costheta_l', 'phi')>, prob=[0.16986766 0.23603968 0.31030257 0.31967185 0.09241075 0.24815852\n",
      " 0.30755462 0.24714592 0.31692639 0.33132893]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "integral_analytic = angular_pdf.integrate(limits=angular_obs)  # this should be one\n",
    "sample = angular_pdf.sample(n=1000)\n",
    "prob_analytic = angular_pdf.pdf(sample)\n",
    "print(f\"integral={integral}, sample={sample}, prob={prob[:10]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "#### Including another observable\n",
    "We built our angular PDF successfully and can use this 3 dimensional PDF now. If we want, we could also include another observable. For example, the polynomial that we created above and make it 4 dimensional. Because it's so simple, let's do that!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "full_pdf = angular_pdf * poly2\n",
    "\n",
    "# equivalently\n",
    "# full_pdf = zfit.pdf.ProductPDF([angular_pdf, poly2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "Done! This PDF is now 4 dimensional, which _had to be_, given that the observable of `poly2` is different from the observable of `angular_pdf`. If they would coincide, e.g. if `poly2` had the observable `phi`, this would now be a 3 dimensional PDF."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "obs angular: ('costheta_k', 'costheta_l', 'phi') obs poly:('obs1',) obs product: ('costheta_k', 'costheta_l', 'phi', 'obs1'))"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "print(f\"obs angular: {angular_pdf.obs} obs poly:{poly2.obs} obs product: {full_pdf.obs})\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# What happened _exactly_?\n",
    "\n",
    "The model tries to be as smart as possible and calls the most explicit function. Then it starts falling back to alternatives and uses, whenever possible, the analytic version (if available), otherwise a numerical.\n",
    "\n",
    "The rule simplified: public (sanitizes input and) calls [...] private. So e.g. `pdf` calls `_pdf` and if this is not provided, it uses the fallback that may not be optimized, but general enough to work.\n",
    "\n",
    "The rule extended (in its current implementation): public calls a series of well defined methods and hooks before it calls the private method. These intermediate _can_ be used, they mostly automatically catch certain cases and handle them for us.\n",
    "\n",
    "**To remember**: in order to have full control over a public function such as `integrate`, `pdf`, `sample` or `normalization`, the private method, e.g. `_integrate` can be overriden and is _guaranteed_ to be called before other possibilities.\n",
    "\n",
    "In the case above, `pdf` called first `_pdf` (which is not implemented), so it calls `_unnormalized_pdf` and divides this by the `normalization`. The latter also does not have an explicit implementation (`_implementation`), so it uses the fallback and calls `integrate` over the `norm_range`. Since `_integrate` is not provided, the fallback tries to perform an analytic integral, which is not available. Therefore, it integrates the `_unnormalized_prob` numerically. In all of this calls, we can hook in by overriding the mentioned, specified methods.\n",
    "\n",
    "What we did not mention: `ZPDF` is just a wrapper around the actual `BasePDF` that should be preferred in general; it simply provides a convenient `__init__`. For the next example, we will implement a multidimensional PDF and use the custom `__init__`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "#### Overriding `pdf`\n",
    "\n",
    "Before, we used `_unnormalized_pdf`, which is the common use-case. Even if we want to add an analytic integral, we can register it. Or do more fancy stuff like overriding the `_normalization`. We can however also get the full control of what our model output by directly overriding `_pdf`. The signature does not contain only `x` but additionally `norm_range`. This can have no limits (`norm_range.has_limits` is False), in which case the \"unnormalized pdf\" is requested. Otherwise, `norm_range` can have different limits and we have to take care of the proper normalization.\n",
    "\n",
    "This is usually not needed and inside zfit, all PDFs are implemented using the `_unnormalized_pdf`.\n",
    "\n",
    "Therefore, it provides mostly a possibility to implement _whatever_ is wanted, any unforeseen use-case, any kind of hack to \"just try out something\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "class CustomPDF(zfit.pdf.BasePDF):\n",
    "    \"\"\"My custom pdf with three parameters.\n",
    "\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, param1, param2, param3, obs, name=\"CustomPDF\", ):\n",
    "        # we can now do complicated stuff here if needed\n",
    "        # only thing: we have to specify explicitly here what is which parameter\n",
    "        params = {'super_param': param1,  # we can change/compose etc parameters\n",
    "                  'param2': param2, 'param3': param3}\n",
    "        super().__init__(obs, params, name=name)\n",
    "\n",
    "    def _pdf(self, x, norm_range):\n",
    "        data = z.unstack_x(x)\n",
    "        param1 = self.params['super_param']\n",
    "        param2 = self.params['param2']\n",
    "        param3 = self.params['param3']\n",
    "\n",
    "        # just a fantasy function\n",
    "        probs = 42 * param1 + (x * param3) ** param2\n",
    "        return probs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "In a similar manner, other methods can be overriden as well. We won't go into further details here, as this provides a quite advanced task. Furthermore, if stability is a large concern or such special cases need to be implemented, it is recommended to get in contact with the developers and share the idea."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## Composed PDFs\n",
    "\n",
    "So far, we only looked at creating a model that depends on parameters and data but did not include other models. This is crucial to create for example sums or products of PDFs. Instead of inheriting from `BasePDF`, we can use the `BaseFunctor` that contains a mixin which handles daughter PDFs correctly.\n",
    "\n",
    "The main difference is that we can now provide a list of PDFs that our model depends on. There can still be parameters (as for example the `fracs` for the sum) that describe the behavior of the models but they can also be omitted (e.g. for the product). _Sidenote: technically, a normal `BasePDF` can of course also have no parameters, however, since this is a constant function without dependencies, this will rarely be used in practice."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "class SquarePDF(zfit.pdf.BaseFunctor):\n",
    "    \"\"\"Example of a functor pdf that takes the log of a single PDF.\n",
    "\n",
    "    DEMONSTRATION PURPOSE ONLY, DO **NOT** USE IN REAL CASE.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, pdf1, name=\"SumOf3\"):\n",
    "        pdfs = [pdf1]  # we could have more of course, e.g. for sums\n",
    "        # no need for parameters here, so we can omit it\n",
    "        obs = pdf1.space\n",
    "        super().__init__(pdfs=pdfs, obs=obs, name=name)\n",
    "\n",
    "    def _unnormalized_pdf(self, x):\n",
    "        # we do not need to unstack x here as we want to feed it directly to the pdf1\n",
    "        pdf1 = self.pdfs[0]\n",
    "\n",
    "        return pdf1.pdf(x) ** 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
    "squarepdf = SquarePDF(pdf1=poly2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Integral called"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Integral called"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: shape=(1,), dtype=float64, numpy=array([0.22233116])>"
      ]
     },
     "execution_count": 17,
     "metadata": {
     },
     "output_type": "execute_result"
    }
   ],
   "source": [
    "squarepdf.integrate(limits=(-2, 3.2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<zfit.core.data.SampleData at 0x7f5ed609ee10>"
      ]
     },
     "execution_count": 21,
     "metadata": {
     },
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sample_square = squarepdf.sample(n=1000)\n",
    "sample_square"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Integral called"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Integral called"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: shape=(10,), dtype=float64, numpy=\n",
       "array([0.37251201, 0.42133165, 0.36299754, 0.2797808 , 0.22951389,\n",
       "       0.11745158, 0.26900377, 0.61684417, 0.17513118, 0.0453934 ])>"
      ]
     },
     "execution_count": 22,
     "metadata": {
     },
     "output_type": "execute_result"
    }
   ],
   "source": [
    "squarepdf.pdf(sample_square)[:10]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## ...and now?\n",
    "\n",
    "We've implemented a custom PDF. Maybe spent quite some time fine tuning it, debugging it. Adding an integral. And now? Time to make it available to others: [zfit-physics](https://github.com/zfit/zfit-physics). This repository is meant for community contributions. It has less requirements to contribute than to zfit core and has a low threshold. Core devs can provide you with help and you can provide the community with a PDF.\n",
    "\n",
    "Make an issue or a PR, everything is welcome!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
   ],
   "source": [
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (system-wide)",
   "language": "python",
   "metadata": {
    "cocalc": {
     "description": "Python 3 programming language",
     "priority": 100,
     "url": "https://www.python.org/"
    }
   },
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}