createProfile very slow

Here I am trying to do what the createProfile method is supposed to do. The manual method is ~3 time faster, why?

import ROOT
import numpy as np
from array import array
from time import time

NPOINTS = 10000

x = ROOT.RooRealVar('x', 'x', -5, 5)
mean = ROOT.RooRealVar('mean', 'mean', 0, -1, 1)
sigma = ROOT.RooRealVar('sigma', 'sigma', 1, 0.1, 2)
gaus = ROOT.RooGaussian('gaus', 'gaus', x, mean, sigma)
data = gaus.generate(ROOT.RooArgSet(x), 100)
nll = gaus.createNLL(data)

start = time()
nll_profile = nll.createProfile(ROOT.RooArgSet(mean))
frame_mean = mean.frame(ROOT.RooFit.Bins(NPOINTS))
nll_profile.plotOn(frame_mean, ROOT.RooFit.Precision(1))
time1 = time() - start

start = time()
m = ROOT.RooMinuit(nll)
nll_minimum = nll.getVal()

xs, ys = np.linspace(-1, 1, NPOINTS + 1), []
for x in xs:
    ys.append(nll.getVal() - nll_minimum)
time2 = time() - start

gr = ROOT.TGraph(len(xs), array('f', xs), array('f', ys))
canvas = ROOT.TCanvas()
frame_mean.addObject(gr, 'P')

print "time automatic: ", time1
print "time manual:    ", time2


The difference in speed is largely caused by the difference in sampling strategy:
a function.plotOn(frame) does not sample NPOINT points, but follows an adaptive
algorithm that measures how far the function deviates from a straight line between
two points. Thus, it samples at least 2*NPOINT points and usually more to obtain a
higher precision in areas with strong curvature.

For expensive functions like RooProfileLL this can be undesirable. For such cases
you can control sampling behaviour explicitly by adding a RooFit::Precision(1) argument
to the plotOn() call which will effectively disable adaptive sampling (but will still
sample 2*NPOINT times - so you may want to take that factor 2 into account in your
choice of NPOINT).

NB: The default plotting precision is 1e-3, which means that the deviation of a straight line
between two curve points and the function at the halfway point can be at most 1e-3
times the scale of the vertical axis of the Rooplot.


Dear Wouter,

as you can see from my code, I am already asking to have a fixed number of points in both the two methods.

In fact if I print

print frame_mean.findObject("nll_gaus_gausData_Profile[mean]_Norm[mean]").GetN()

it returns 10006. I don’t now why I get 6 points in addition, but it is not important.

Both methods uses 10000 points. The createProfile method is 2,3 times slower.

I attach a plot with 5 points. The blue line is the one from createProfile, the black points are my scan.


Sorry, I had completely missed that in your code!

In that case it doesn’t make sense indeed. I’ll look into it and get back to you shortly.

(NB: you have a few extra points in the graph just outside the plot range. These point make sure
that when the graph is drawn with a filled style the entire area under the plot is properly filled)



OK. So it turns out I had written the code such that regardless of the supplied precision value, the
adaptive sampler will always test the midway point to see if it exceeds the precision requirement.
For a precision value of 1, it will of course never, but it still doubles the number of function
evaluations as for every bin, the function value is not only evaluated at the boundary value, but
also at the midway point (but then not plotted there since it is within the requested precision).
(I don’t add the tested midway points to the curve as this is not so useful for the (default) small
precision value)

I’ve now added the possibility to indicate negative precision value to indicate that no attempt at
adaptive sampling should be made. Thus if you pick up the head of the 5.34 patches branch
you will be able to say Precision(-1) and I confirm a speed up the plotting by a factor of two.

Concerning the residual small difference in speed: RooProfileLL is configured by default to always start the profiling minimization from the global minimum for each point, whereas you start from previous scan point.
The latter is computationally somewhat more efficient, but exposes you to potential ambiguities
(the minimizer might find a different minimum depending on the starting point). The usefulness of this
tradeoff depends very much on the complexity of your likelihood. You can reconfigure RooProfileLL to
adapt the ‘start from the last point’ strategy using the RooProfileLL::alwaysStartFromMin(Bool_t) method.