TMinuit: SetFCN() odd behaviour

Hello all.

This is either a bug in PyROOT or somewhere upstream, but I don’t think it is the expected functionality. The matter concerns the TMinuit class in Python, more specifically its method SetFCN. I would like to create several minimizers for different functions in PyROOT and set a different FCN for each. This can be done apparently by setting the FCN with SetFCN. However, if I run MIGRAD on any of these minimizers, they always use the FCN which was defined last, instead of the FCN which was assigned to them by SetFCN.

Consider the following code. I have defined two functions with appreciably different minima:

import ROOT
from array import array as arr

# two different functions to be minimized with Minuit
def FCN1(number_of_parameters, derivatives, f, parameters, internal_flag):
    # has a minimum at (3.14, -2.71)
    desired_minimum = (3.14, -2.71)
    f[0] = (parameters[0] - desired_minimum[0]) ** 2 + (parameters[1] - desired_minimum[1]) ** 2 + 1.0

def FCN2(number_of_parameters, derivatives, f, parameters, internal_flag):
    # has a minimum at (-9.91, 3.42)
    desired_minimum = (-9.91, 3.42)
    f[0] = (parameters[0] - desired_minimum[0]) ** 2 + (parameters[1] - desired_minimum[1]) ** 2 + 1.0

If I define a TMinuit instance for each and I assign each FCN to the minimizer via SetFCN then run MIGRAD on both, I get the minimum (-9.91, 3.42), which is true only for FCN2 (the one defined last). If, however, I post-pone the SetFCN for the second minimizer, I get a correct answer.


error_code = ROOT.Long(0)
par0, par1 = ROOT.Double(0), ROOT.Double(0)
par0e, par1e = ROOT.Double(0), ROOT.Double(0)


# create a TMinuit minimizer for each FCN
minuit1 = ROOT.TMinuit(2)
minuit2 = ROOT.TMinuit(2)

# Set the FCN for the minimizers
minuit1.SetFCN(FCN1)
minuit2.SetFCN(FCN2) # if this is moved below, everything works as expected

# minimize FCN1
minuit1.mnparm(0, 'a', 0.0, 0.01, 0, 0, error_code)
minuit1.mnparm(1, 'b', 0.0, 0.01, 0, 0, error_code)
minuit1.mnexcm("MIGRAD", arr('d', [6000, 0.1]), 2, error_code)
minuit1.mnexcm("HESSE", arr('d', [6000]), 1, error_code)
minuit1.GetParameter(0, par0, par0e)
minuit1.GetParameter(1, par1, par1e)
print par0, par1 # These final fit parameters are incorrect: (-9.91, 3.42), should be (3.14, -2.71)

#minuit2.SetFCN(FCN2) # if this were inserted here from above, everything would be OK

# minimize FCN2
minuit2.mnparm(0, 'a', 0.0, 0.01, 0, 0, error_code)
minuit2.mnparm(1, 'b', 0.0, 0.01, 0, 0, error_code)
minuit2.mnexcm("MIGRAD", arr('d', [6000, 0.1]), 2, error_code)
minuit2.mnexcm("HESSE", arr('d', [6000]), 1, error_code)
minuit2.GetParameter(0, par0, par0e)
minuit2.GetParameter(1, par1, par1e)
print par0, par1 # these are the correct parameters: (-9.91, 3.42)

Is there any way this is the expected behaviour? I mean, if I run: object.SetFCN(function)I expect the FCN to be a property of that object, not somehow set globally, otherwise the syntax should be:

If not, is a fix possible or is this not a PyROOT problem, but more deeply rooted no pun intended ?

Hi,

sorry for the late reply, but I’m traveling (and will be for a couple more weeks).

Seems to be a limitation of SetFCN. When called interactively (i.e. through a generated function), it relies on gMinuit to locate that generated function. SetFCN sets gMinuit to the ‘this’ of the last TMinuit instance that has it’s SetFCN called, to allow gMinuit->GetMethodCall() to retrieve it later.

At issue is the same as is typical in these cases in PyROOT (TFn classes): CINT does not allow through the callback interface to identify a closure (there’s no parameter for such input to the callback). In PyROOT, this gets cheated around by generating a fake return type and using the tagnum (CINT internal type identifier) to find the closure through a std::map of tagnum to closure. In SetFCN, the choice was made to use gMinuit for the same purpose. Of course, that then means that there can be only one FCN active at any one time.

Note that on the PyROOT side, this is fixed with Cling. I presume that TMinuit can then be solved as well (it isn’t, currently).

In principle then, if you set ROOT.gMinuit = minuit1 just before the fit (and reset to minuit2 for the second), it should all work … (but I haven’t tried :slight_smile: ).

Cheers,
Wim