<jupyter_start><jupyter_text>Adding custom PDFsIn this section it will be explained how to create new PDFs and register them within MinKit.All the different PDFs in this package are built from XML files.This allows to automatically implement the operations independently of the backend.The only function that is really needed is that to calculate the value of the probability density function.Optionally, one can also specify the integral of the PDF between two points.If so, the normalization and the evaluation on binned data samples are automatically defined, which will boost both processes since no numerical calculations need to be done.<jupyter_code>%matplotlib inline
import matplotlib.pyplot as plt
import minkit
import numpy as np
import os
import tempfile<jupyter_output><empty_output><jupyter_text>Creating a PDF from a formulaIn MinKit it is possible to create a custom PDF from a simple (one line) formula, as follows:<jupyter_code>x = minkit.Parameter('x', bounds=(-1.5 * np.pi, +1.5 * np.pi))
a = minkit.Parameter('a', 1, bounds=(0.9, 1.1))
b = minkit.Parameter('b', 0, bounds=(0.01, 1))

formula = 'pow(cos(a * x + b), 2)'

pdf = minkit.FormulaPDF.unidimensional('pdf', formula, x, [a, b])

data = pdf.generate(10000)

fig, ax = plt.subplots(1, 1, figsize=(6, 4))

values, edges = minkit.data_plotting_arrays(data)

centers = 0.5 * (edges[1:] + edges[:-1])

ax.hist(centers, bins=edges, weights=values, histtype='step')
ax.set_xlabel(x.name);<jupyter_output><empty_output><jupyter_text>We can also provide the primitive, so the integral can be computed.<jupyter_code>primitive = '(2 * (a * x + b) + sin(2 * (a * x + b))) / (4 * a)'

pdf_with_primitive = minkit.FormulaPDF.unidimensional('pdf', formula, x, [a, b], primitive)

print(f'Normalization (numerical): {pdf.norm():.4f}')
print(f'Normalization (analytical): {pdf_with_primitive.norm():.4f}')<jupyter_output>Normalization (numerical): 4.7124
Normalization (analytical): 4.7124<jupyter_text>In the formula strings, only functions exposed by the *math.h* header are defined. MinKit creates the source code from both the formula and the primitive and compiles it at runtime.  Defining complex PDFsIn order to add a new PDF to MinKit, where the evaluation function consists on more than one line, we must create a source XML file, and tell the MinKit package to add its directory to the list of directories to search for PDFs. For this example, we will create a C++ file from python on a temporary directory.<jupyter_code>tmpdir = tempfile.TemporaryDirectory()

with open(os.path.join(tmpdir.name, 'CustomPDF.xml'), 'wt') as f:
    f.write('''
<PDF data="x" parameters="k">
  <preamble/>
  <function>
      return exp(k * x);
  </function>
  <integral bounds="xmin xmax">
      return 1. / k * (exp(k * xmax) - exp(k * xmin));
  </integral>
</PDF>
        ''')<jupyter_output><empty_output><jupyter_text>The root of the XML tree must contain the fields *data*, referring to the data parameters to use; and *parameters* or *variable_parameters*, refferring to the parameters that the PDF uses. The latter must contain two names. The first rules the number of variable parameters and the second is the name of the pointer to the array of values. In addition, the XML parser accepts the following fields:* *function* (required): refers to the code used to evaluate the PDF. All the mathematical functions must be available in C.* *preamble* (optional): any kind of definition that needs to be added after the *include* directives.* *integral* (optional): this field is optional since not all the PDFs have an analytical expression for the integral. It is recommended to fill this field when possible, since otherwise MinKit relies on numerical calculations to evaluate binned samples and calculate the normalization. This field must be composed of two parts: *bounds*, defining the names for the limits of the integration; and *code*, where the calculation of the integral is done. If working with more than one data parameter, *bounds* must contain the lower and upper bounds for each parameter, included consecutively (xmin, xmax, ymin, ymax, ...).* *primitive* (optional): if provided, this function is used to calculate the integral of the function. If it is provided together with *integral*, the latter will be used.Something very important to remember is that since we are dealing with XML format, the symbols , &, " and ' are protected. We must use the escaped versions. Now we have to register the PDF. In order to be used in python, we must tell minkit to look for PDFs in the temporary directory.<jupyter_code>minkit.add_pdf_src(tmpdir.name)<jupyter_output><empty_output><jupyter_text>Now we have to build a python object to represent the PDF. This is directly done when inheriting from "minkit.SourcePDF", which will automatically set our PDF.<jupyter_code>@minkit.register_pdf
class CustomPDF(minkit.SourcePDF):
    def __init__( self, name, x, k ):
        super(CustomPDF, self).__init__(name, [x], [k])<jupyter_output><empty_output><jupyter_text>The *register_pdf* decorator is necessary if we want to save/load the PDF to/from JSON files. It is completely necessary that we call the function the same way we call the source file. The arguments to the *minkit.SourcePDF* constructor are the name of the PDF, the data parameters and the argument parameters. Now we can declare and use our function.<jupyter_code>x = minkit.Parameter('x', bounds=(0, 10))
k = minkit.Parameter('k', -0.5)
pdf = CustomPDF('pdf', x, k)
data = pdf.generate(10000)
plt.hist(data['x'].as_ndarray(), bins=100);<jupyter_output><empty_output><jupyter_text>New PDFs with a variable number of argumentsIt might happen that one wants to define a general function, which depends on a list of parameters that can vary (for example, a polynomial) from one construction to another.This can be done on a similar way to that of the previous section, providing as a third argument to *minkit.SourcePDF* the list of extra arguments.<jupyter_code>@minkit.register_pdf
class VarArgsCustomPDF(minkit.SourcePDF):
    def __init__( self, name, x, *coeffs ):
        super(VarArgsCustomPDF, self).__init__(name, [x], None, coeffs)<jupyter_output><empty_output><jupyter_text>The XML file must be modified accordingly, using the field *variable_parameters*:<jupyter_code>with open(os.path.join(tmpdir.name, 'VarArgsCustomPDF.xml'), 'wt') as f:
    f.write('''
<PDF data="x" variable_parameters="n p">
  <preamble/>
  <function>
      if ( n == 0 )
        return 1.;

      double out = x * p[n - 1];
      for ( int i = 0; i &lt; n; ++i )
        out = x * (out + p[n - i - 1]);
      return out + 1.;
  </function>
</PDF>
        ''')
    
x = minkit.Parameter('x', bounds=(0, 10))
p1 = minkit.Parameter('p1', +0.5)
pdf = VarArgsCustomPDF('pdf', x, p1)
data = pdf.generate(10000)
plt.hist(data['x'].as_ndarray(), bins=100);<jupyter_output><empty_output><jupyter_text>Building a PDF proxy from a data setIn MinKit it is also possible to create a PDF using a binned data set as a reference. This turns useful when we are not able to describe a data sample with any known distribution function. As it happens with the convolution PDFs, we can choose between using a *spline* or a linear interpolator.<jupyter_code>binned_data = data.make_binned(10)

pdf = minkit.InterpPDF.from_binned_dataset('pdf', binned_data) # build the class from a binned data set

unbinned_data = pdf.generate(10000) # generate data using the PDF proxy

fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(12, 4))

edges = binned_data.edges.as_ndarray()
values = binned_data.values.as_ndarray()

centers = 0.5 * (edges[1:] + edges[:-1])

ax0.hist(centers, bins=edges, weights=values, histtype='step', color='k', label='data');

pdf_centers, pdf_values = minkit.pdf_plotting_arrays(pdf, values, edges)

ax0.plot(pdf_centers, pdf_values, 'b-');

values, edges = minkit.data_plotting_arrays(data, bins=100)
ax1.hist(0.5 * (edges[1:] + edges[:-1]), bins=edges, weights=values, histtype='step', color='C0', label='base');
values, edges = minkit.data_plotting_arrays(unbinned_data, bins=100)
ax1.hist(0.5 * (edges[1:] + edges[:-1]), bins=edges, weights=values, histtype='step', color='C1', label='proxy');
ax1.legend(loc='upper left');<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10772210118615347, 'fraction_numerical': 0.014282256112321472, 'mean_word_length': 5.044623262618874, 'pattern_counts': {'":': 0, '<': 52, '<?xml version=': 0, '>': 52, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '605122', 'license': 'MIT-0', 'n_tokens_mistral': 2603, 'n_tokens_neox': 2509, 'n_words': 994, 'path': 'docs/source/notebooks/extension.ipynb', 'repo_name': 'mramospe/minkit', 'size': '8262'}