TF1 with string lambda function generates rogue 'Formula::SetParName' errors


I have noticed that when using the TF1 option to specify the function as a self contained lambda function as a string, subsequent calls to set parameter names generates errors like

Error in <TFormula::SetParName>: Parameter p0 is not defined.

Even though the call appears to work and set the parameter name. The following trivial script illustrates the issue

  // Setting parameter names for this function causes errors
  //TF1 fFitF( "F1", "[&](double *x, double *p) { return p[0] + x[0]*p[1]; }", -3, 3, 2 );

  // Setting parameter names here is OK
  TF1 fFitF( "F1", "pol1", -3, 3 );

  fFitF.SetParameter( 0, 1 );
  fFitF.SetParameter( 1, 1 );

  fFitF.SetParName( 0, "MyConstant" );
  fFitF.SetParName( 1, "MySlope" );
  std::cout << "f(0.1) = " << fFitF.Eval(0.1) << std::endl;
  std::cout << "p[0] name = " << fFitF.GetParName(0) << std::endl;
  std::cout << "p[1] name = " << fFitF.GetParName(1) << std::endl;

Using the built in pol1 function generates no errors. Switching to the equivalent lambda version gives the errors, even though the names are still correctly set.

I am using ROOT from the lcg 88 release on cvmfs

 > which root

It simply seems the errors are incorrectly being issued ?


Any thoughts ? Is it a bug, should I submit a JIRA ticket?

You have your lambda function wrapped in quotes. Without quotes seems to work fine:

  TF1 fFitF( "F1", [&](double *x, double *p) { return p[0] + x[0]*p[1]; }, -3, 3, 2 );
$ root test.C 
root [0] 
Processing test.C...
f(0.1) = 1.1
p[0] name = MyConstant
p[1] name = MySlope
root [1] 

The quotes are intentional. Without them you have create a local lambda function and are passing that to the TF1 constructor. Hence you are using a different constructor to the one I am using.

I am specifically using the one I am as I need the TF1 to be self contained, and not rely on an external lambda function to operator. If you where to return your function from a method (which I do) then your function will be ill defined once the scope of the method is closed, as then the lambda will no longer exist. In my case it does.


case 3.

That clarifies your question, you want to use a lambda function with the case 3 constructor.

On the other hand, it is not clear to me that the lambda function goes out of scope as you indicate (Iā€™m not an expert on lambda functions though). Take this example:

TF1* getFunc() {
  return new TF1( "F1", [&](double *x, double *p) { return p[0] + x[0]*p[1]; }, -3, 3, 2 );

void test() {
  TF1 *fFitF = getFunc();

  fFitF->SetParameter( 0, 1 );
  fFitF->SetParameter( 1, 1 );

  fFitF->SetParName( 0, "MyConstant" );
  fFitF->SetParName( 1, "MySlope" );

  std::cout << "f(0->1) = " << fFitF->Eval(0.1) << std::endl;
  std::cout << "p[0] name = " << fFitF->GetParName(0) << std::endl;
  std::cout << "p[1] name = " << fFitF->GetParName(1) << std::endl;
$ root test.C 
root [0] 
Processing test.C...
f(0->1) = 1.1
p[0] name = MyConstant
p[1] name = MySlope
root [1]

Rather than

TF1 fFitF( "F1", "[&](double *x, double *p) { return p[0] + x[0]*p[1]; }", -3, 3, 2 );

why not

TF1 fFitF( "F1", "p[0] + x[0]*p[1]", -3, 3, 2 );


pcanal - The actual code I have posted here is just an example test case. The real example I have involves a much more complicated lambda function, with a number of lines of code etc. It cannot be expressed in the simple form you suggest.

Also, the fact another form for the test cases exists does not negate the fact using the inline lambda function, which is a form that is explicitly documented as a (nice) new option in the link I post above, generates rogue errors. The issue is still there regardless of this fact.

smith - yes, your lambda is going out of scope. You are likely just being lucky with the fact the memory allocated for it has likely not be over written by the time you use it, a very short while later on. Your code is equivalent to

TF1* getFunc() {
auto f = [&](double *x, double *p) { return p[0] + x[0]*p[1]; };
return new TF1( ā€œF1ā€, f, -3, 3, 2 );

and when written like that it should be obvious the lambda has left scope once the method has returned.

As is often the case.


The error in SetParName is wrongly issued, the TF1 built on a lambda using the string should work fine.
I will commit now a fix for this wrong message produced.

The advantage of using lambda with strings(based on TFormula) is that they can be stored in a file and one can captures objects in the lambda. On the other hand one needs to be sure that when reading from a file these capture objects are available before.


1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.