Using the validphys API

Introduction

The API functionality allows the validphys/reportengine machinery to be readily leveraged in a development setting such as a Jupyter notebook. Any action available to validphys can be invoked by Python code, with the same parameters as a runcard.

For example:

from validphys.api import API

figs = API.plot_pdfs(pdfs=["NNPDF40_nlo_as_01180"], Q=2)
for f, _ in figs:
    f.show()

The API object provides a high level interface to the validphys code. Note that the user doesn’t need to know exactly how to load the PDF, create the relevant grid to be plotted and then pass that to the plotting function, this is all handled by the underlying code of reportengine. This abstraction provides a convenient way to explore the functionality of validphys (or any other reportengine application) as well as to develop further functionality for validphys itself.

All the actions available to validphys are translated into methods of the API object. The arguments are the same as the parameters that the validphys runcard would need to evaluate the action.

Generic Example

An important use case of this functionality is the development Consider that you wanted to develop a provider which depends on some expensive providers, defined somewhere in the validphys modules,

def expensive_provider1(pdf:PDF, Q, theoryid):
    ...

def expensive_provider2(experiments, ...):
    ...

Now in a notebook we can do

from validphys.api import API

expensive1 = API.expensive_provider1(pdf="NNPDF40_nlo_as_01180", Q=100, theoryid=208)
expensive2 = API.expensive_provider2(dataset_inputs={"from_": "fit"}, fit="NNPDF40_nlo_as_01180")

We can then define and test our new function (e.g. in a separate notebook cell),

def developing_provider(expensive_provider1, expensive_provider2):
    ...

test_output = developing_provider(expensive1, expensive2)

expensive1 and expensive2 have already been evaluated using the validphys machinery, and we just had to declare the validphys2 runcard inputs in order to use those providers. The output of these expensive function is now saved. So for the remainder of our notebook session we don’t need to re-run the expensive providers every time we wish to change something with our developing_provider. Clearly this massively reduces the time to develop and test the new provider since, the expensive providers which the new developing_provider depends on are cached for the rest of the jupyter session.

For expensive_provider2 the input was slightly more complicated. When using the API remember that the input is exactly the same as a validphys2 runcard. The runcards are in a yaml format which is then parsed as a dict. If it seems more intuitive one can utilise this when declaring the inputs for the API providers, for example:

input2 = {
    "experiments": {
        "from_": "fit"
    },
    "fit": "NNPDF40_nlo_as_01180"
}
expensive2 = API.expensive_provider2(**input2)

The input2 dictionary is visually almost identical the corresponding validphys2 runcard, we just need to be careful the separate items with commas, that all dict keys are strings and that the typing is correct for the various inputs, we can always look up the appropriate typing by using the validphys --help functionality.

Creating figures in the validphys style

If a figure is created using the api, as with the first example:

from validphys.api import API

fig = API.some_plot(...)
fig.show()

you might notice that the style of the plot is very different to those produce by validphys. If you want to use the same style as validphys then consider using the following commands at the top of your script or notebook:

import matplotlib
from validphys import mplstyles
matplotlib.style.use(str(mplstyles.smallstyle))

also consider using fig.tight_layout() which reportengine uses before saving figures. For the example used earlier we would then have

import matplotlib
from validphys import mplstyles
matplotlib.style.use(str(mplstyles.smallstyle))

from validphys.api import API

figs = API.plot_pdfs(pdfs=["NNPDF40_nlo_as_01180"], Q=2)
for f, _ in figs:
    f.tight_layout()
    f.show()

Mixing declarative input with custom resources (NOTE: Experimental)

For some actions it is possible to mix declarative input with custom resources.

Take for example xplotting_grid, which minimally requires us to specify pdf, Q. We see from validphys --help xplotting_grid that it depends on the provider xgrid which in turn returns a tuple of (scale, x_array). Using the API we could specify our own custom xgrid input, but then rely on the API to collect the other relevant resources, for example:

import numpy as np
from validphys.api import API

new_xgrid = ("linear", np.array([0.1, 0.2])
pdf_grid = API.xplotting_grid(pdf="NNPDF40_nlo_as_01180", Q=2, xgrid=new_xgrid)

The API offers flexibility to mix declarative inputs such as pdf=<name of pdf> with python objects xgrid=(<string>, <numpy.ndarray>), note that this is very dependent on the provider in question and is not guaranteed to work all the time.