Using the RooCustomizer to create multiple PDFs that share a lot of properties, but have unique parameters for each category. As an extra complication, some of the new parameters need to be functions of a mass parameter.

Author: Stephan Hageboeck, CERN
This notebook tutorial was automatically generated with ROOTBOOK-izer from the macro found in the ROOT repository on Monday, August 08, 2022 at 09:40 AM.

Define a proto model that will be used as the template for each category

In [1]:
RooRealVar E("Energy","Energy",0,3000);

RooRealVar meanG("meanG","meanG", 100., 0., 3000.);
RooRealVar sigmaG("sigmaG","sigmaG", 3.);
RooGaussian gauss("gauss", "gauss", E, meanG, sigmaG);

RooRealVar pol1("pol1", "Constant of the polynomial", 1, -10, 10);
RooPolynomial linear("linear", "linear", E, pol1);

RooRealVar yieldSig("yieldSig", "yieldSig", 1, 0, 1.E4);
RooRealVar yieldBkg("yieldBkg", "yieldBkg", 1, 0, 1.E4);

RooAddPdf model("model", "S + B model",
    RooArgList(yieldSig, yieldBkg));

std::cout << "The proto model before customisation:" << std::endl;
model.Print("T"); // "T" prints the model as a tree
[#0] WARNING:InputArguments -- The parameter 'sigmaG' with range [-1e+30, 1e+30] of the RooGaussian 'gauss' exceeds the safe range of (0, inf). Advise to limit its range.
The proto model before customisation:
0x7f85800331f8 RooAddPdf::model = 750.5/1 [Auto,Clean] 
  0x7f8580031bb8/V- RooGaussian::gauss = 0 [Auto,Dirty] 
    0x7f8580031000/V- RooRealVar::Energy = 1500
    0x7f85800313e8/V- RooRealVar::meanG = 100
    0x7f85800317d0/V- RooRealVar::sigmaG = 3
  0x7f8580032a28/V- RooRealVar::yieldSig = 1
  0x7f85800324f0/V- RooPolynomial::linear = 1501 [Auto,Dirty] 
    0x7f8580031000/V- RooRealVar::Energy = 1500
    0x7f8580032108/V- RooRealVar::pol1 = 1
  0x7f8580032e10/V- RooRealVar::yieldBkg = 1

Build the categories

In [2]:
RooCategory sample("sample","sample");
sample["Sample1"] = 1;
sample["Sample2"] = 2;
sample["Sample3"] = 3;
input_line_52:2:2: warning: 'sample' shadows a declaration with the same name in the 'std' namespace; use '::sample' to reference this declaration
 RooCategory sample("sample","sample");

Start to customise the proto model that was defined above.

We need two sets for bookkeeping of PDF nodes:

In [3]:
RooArgSet newLeafs;           // This set collects leafs that are created in the process.
RooArgSet allCustomiserNodes; // This set lists leafs that have been used in a replacement operation.
  1. Each sample should have its own mean for the gaussian The customiser will make copies of meanG for each category. These will all appear in the set newLeafs, which will own the new nodes.
In [4]:
RooCustomizer cust(model, sample, newLeafs, &allCustomiserNodes);
cust.splitArg(meanG, sample);
input_line_54:2:28: error: reference to 'sample' is ambiguous
 RooCustomizer cust(model, sample, newLeafs, &allCustomiserNodes);
input_line_52:2:14: note: candidate found by name lookup is '__cling_N522::sample'
 RooCategory sample("sample","sample");
/usr/include/c++/9/bits/stl_algo.h:5855:5: note: candidate found by name lookup is 'std::sample'
    sample(_PopulationIterator __first, _PopulationIterator __last,
input_line_54:3:22: error: reference to 'sample' is ambiguous
cust.splitArg(meanG, sample);
input_line_52:2:14: note: candidate found by name lookup is '__cling_N522::sample'
 RooCategory sample("sample","sample");
/usr/include/c++/9/bits/stl_algo.h:5855:5: note: candidate found by name lookup is 'std::sample'
    sample(_PopulationIterator __first, _PopulationIterator __last,
  1. Each sample should have its own signal yield, but there is an extra complication: We need the yields 1 and 2 to be a function of the variable "mass". For this, we pre-define nodes with exacly the names that the customiser would have created automatically, that is, "_", and we register them in the set of customiser nodes. The customiser will pick them up instead of creating new ones. If we don't provide one (e.g. for "yieldSig_Sample3"), it will be created automatically by cloning yieldSig.
In [5]:
RooRealVar mass("M", "M", 1, 0, 12000);
RooFormulaVar yield1("yieldSig_Sample1", "Signal yield in the first sample", "M/3.360779", mass);
RooFormulaVar yield2("yieldSig_Sample2", "Signal yield in the second sample", "M/2", mass);

Instruct the customiser to replace all yieldSig nodes for each sample:

In [6]:
cust.splitArg(yieldSig, sample);
input_line_62:2:26: error: reference to 'sample' is ambiguous
 cust.splitArg(yieldSig, sample);
input_line_52:2:14: note: candidate found by name lookup is '__cling_N522::sample'
 RooCategory sample("sample","sample");
/usr/include/c++/9/bits/stl_algo.h:5855:5: note: candidate found by name lookup is 'std::sample'
    sample(_PopulationIterator __first, _PopulationIterator __last,

Now we can start building the PDFs for all categories:

In [7]:
auto pdf1 = cust.build("Sample1");
auto pdf2 = cust.build("Sample2");
auto pdf3 = cust.build("Sample3");
input_line_63:2:2: error: Syntax error
 auto pdf1 = cust.build("Sample1");
FunctionDecl 0x7f856634d220 <input_line_63:1:1, line:6:1> line:1:6 __cling_Un1Qu326 'void (void *)'
|-ParmVarDecl 0x7f856634d168 <col:23, col:29> col:29 vpClingValue 'void *'
|-CompoundStmt 0x7f856634d938 <col:43, line:6:1>
| |-DeclStmt 0x7f856634d508 <line:2:2, col:35>
| | `-VarDecl 0x7f856634d300 <col:2, col:34> col:7 pdf1 'auto' cinit
| |   `-CallExpr 0x7f856634d4e0 <col:14, col:34> '<dependent type>'
| |     |-CXXDependentScopeMemberExpr 0x7f856634d478 <col:14, col:19> '<dependent type>' lvalue .build
| |     | `-DeclRefExpr 0x7f856634d438 <col:14> '<dependent type>' lvalue Var 0x7f856634d370 'cust' '<dependent type>'
| |     `-StringLiteral 0x7f856634d4c0 <col:25> 'const char [8]' lvalue "Sample1"
| |-DeclStmt 0x7f856634d710 <line:3:1, col:34>
| | `-VarDecl 0x7f856634d538 <col:1, col:33> col:6 pdf2 'auto' cinit
| |   `-CallExpr 0x7f856634d6e8 <col:13, col:33> '<dependent type>'
| |     |-CXXDependentScopeMemberExpr 0x7f856634d680 <col:13, col:18> '<dependent type>' lvalue .build
| |     | `-DeclRefExpr 0x7f856634d640 <col:13> '<dependent type>' lvalue Var 0x7f856634d5a8 'cust' '<dependent type>'
| |     `-StringLiteral 0x7f856634d6c8 <col:24> 'const char [8]' lvalue "Sample2"
| |-DeclStmt 0x7f856634d918 <line:4:1, col:34>
| | `-VarDecl 0x7f856634d740 <col:1, col:33> col:6 pdf3 'auto' cinit
| |   `-CallExpr 0x7f856634d8f0 <col:13, col:33> '<dependent type>'
| |     |-CXXDependentScopeMemberExpr 0x7f856634d888 <col:13, col:18> '<dependent type>' lvalue .build
| |     | `-DeclRefExpr 0x7f856634d848 <col:13> '<dependent type>' lvalue Var 0x7f856634d7b0 'cust' '<dependent type>'
| |     `-StringLiteral 0x7f856634d8d0 <col:24> 'const char [8]' lvalue "Sample3"
| `-NullStmt 0x7f856634d930 <line:5:1>
|-AnnotateAttr 0x7f856634d3d8 <<invalid sloc>> R"ATTRDUMP(__ResolveAtRuntime)ATTRDUMP"
|-AnnotateAttr 0x7f856634d610 <<invalid sloc>> R"ATTRDUMP(__ResolveAtRuntime)ATTRDUMP"
`-AnnotateAttr 0x7f856634d818 <<invalid sloc>> R"ATTRDUMP(__ResolveAtRuntime)ATTRDUMP"

And we inspect the two PDFs

In [8]:
std::cout << "\nPDF 1 with a yield depending on M:" << std::endl;
std::cout << "\nPDF 2 with a yield depending on M:" << std::endl;
std::cout << "\nPDF 3 with a free yield:" << std::endl;

std::cout << "\nThe following leafs have been created automatically while customising:" << std::endl;
PDF 1 with a yield depending on M:
input_line_65:2:3: error: use of undeclared identifier 'pdf1'
Error in <HandleInterpreterException>: Error evaluating expression (pdf1->Print("T"))
Execution of your code was aborted.

If we needed to set reasonable values for the means of the gaussians, this could be done as follows:

In [9]:
auto& meanG1 = static_cast<RooRealVar&>(allCustomiserNodes["meanG_Sample1"]);
auto& meanG2 = static_cast<RooRealVar&>(allCustomiserNodes["meanG_Sample2"]);

std::cout << "\nThe following leafs have been used while customising"
  << "\n\t(partial overlap with the set of automatically created leaves."
  << "\n\ta new customiser for a different PDF could reuse them if necessary.):" << std::endl;
[#0] ERROR:InputArguments -- RooArgSet::operator[]() ERROR: no element named meanG_Sample1 in set