Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
CodegenContext.cxx
Go to the documentation of this file.
1/*
2 * Project: RooFit
3 * Authors:
4 * Garima Singh, CERN 2023
5 * Jonas Rembser, CERN 2023
6 *
7 * Copyright (c) 2023, CERN
8 *
9 * Redistribution and use in source and binary forms,
10 * with or without modification, are permitted according to the terms
11 * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
12 */
13
15#include <RooAbsArg.h>
16
17#include "RooFitImplHelpers.h"
18
19#include <TInterpreter.h>
20
21#include <algorithm>
22#include <cctype>
23#include <charconv>
24#include <fstream>
25#include <locale>
26#include <type_traits>
27#include <unordered_map>
28#include <unordered_set>
29
30namespace {
31
32bool startsWith(std::string_view str, std::string_view prefix)
33{
34 return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix);
35}
36
37} // namespace
38
39namespace RooFit {
40namespace Experimental {
41
42/// @brief Adds (or overwrites) the string representing the result of a node.
43/// @param key The name of the node to add the result for.
44/// @param value The new name to assign/overwrite.
45void CodegenContext::addResult(const char *key, std::string const &value)
46{
47 const TNamed *namePtr = RooNameReg::known(key);
48 if (namePtr)
49 addResult(namePtr, value);
50}
51
52void CodegenContext::addResult(TNamed const *key, std::string const &value)
53{
54 _nodeNames[key] = value;
55}
56
57/// @brief Gets the result for the given node using the node name. This node also performs the necessary
58/// code generation through recursive calls to 'translate'. A call to this function modifies the already
59/// existing code body.
60/// @param key The node to get the result string for.
61/// @return String representing the result of this node.
62std::string const &CodegenContext::getResult(RooAbsArg const &arg)
63{
64 // If the result has already been recorded, just return the result.
65 // It is usually the responsibility of each translate function to assign
66 // the proper result to its class. Hence, if a result has already been recorded
67 // for a particular node, it means the node has already been 'translate'd and we
68 // dont need to visit it again.
69 auto found = _nodeNames.find(arg.namePtr());
70 if (found != _nodeNames.end())
71 return found->second;
72
73 // The result for vector observables should already be in the map if you
74 // opened the loop scope. This is just to check if we did not request the
75 // result of a vector-valued observable outside of the scope of a loop.
76 auto foundVecObs = _vecObsIndices.find(arg.namePtr());
77 if (foundVecObs != _vecObsIndices.end()) {
78 throw std::runtime_error("You requested the result of a vector observable outside a loop scope for it!");
79 }
80
81 auto RAII(OutputScopeRangeComment(&arg));
82
83 // Now, recursively call translate into the current argument to load the correct result.
84 codegen(const_cast<RooAbsArg &>(arg), *this);
85
86 return _nodeNames.at(arg.namePtr());
87}
88
89/// @brief Adds the given string to the string block that will be emitted at the top of the squashed function. Useful
90/// for variable declarations.
91/// @param str The string to add to the global scope.
92void CodegenContext::addToGlobalScope(std::string const &str)
93{
94 // Introduce proper indentation for multiline strings.
95 _code[0] += str;
96}
97
98/// @brief Since the squashed code represents all observables as a single flattened array, it is important
99/// to keep track of the start index for a vector valued observable which can later be expanded to access the correct
100/// element. For example, a vector valued variable x with 10 entries will be squashed to obs[start_idx + i].
101/// @param key The name of the node representing the vector valued observable.
102/// @param idx The start index (or relative position of the observable in the set of all observables).
103void CodegenContext::addVecObs(const char *key, int idx)
104{
105 const TNamed *namePtr = RooNameReg::known(key);
106 if (namePtr)
107 _vecObsIndices[namePtr] = idx;
108}
109
111{
112 auto it = _vecObsIndices.find(arg.namePtr());
113 if (it != _vecObsIndices.end()) {
114 return it->second;
115 }
116
117 return -1; // Not found
118}
119/// @brief Adds the input string to the squashed code body. If a class implements a translate function that wants to
120/// emit something to the squashed code body, it must call this function with the code it wants to emit. In case of
121/// loops, automatically determines if code needs to be stored inside or outside loop scope.
122/// @param klass The class requesting this addition, usually 'this'.
123/// @param in String to add to the squashed code.
124void CodegenContext::addToCodeBody(RooAbsArg const *klass, std::string const &in)
125{
126 // If we are in a loop and the value is scope independent, save it at the top of the loop.
127 // else, just save it in the current scope.
129}
130
131/// @brief A variation of the previous addToCodeBody that takes in a bool value that determines
132/// if input is independent. This overload exists because there might other ways to determine if
133/// a value/collection of values is scope independent.
134/// @param in String to add to the squashed code.
135/// @param isScopeIndep The value determining if the input is scope dependent.
136void CodegenContext::addToCodeBody(std::string const &in, bool isScopeIndep /* = false */)
137{
138 TString indented = in;
139 indented = indented.Strip(TString::kBoth); // trim
140
141 std::string indent_str = "";
142 for (unsigned i = 0; i < _indent; ++i)
143 indent_str += " ";
144 indented = indented.Prepend(indent_str);
145
146 // FIXME: Multiline input.
147 // indent_str += "\n";
148 // indented = indented.ReplaceAll("\n", indent_str);
149
150 // If we are in a loop and the value is scope independent, save it at the top of the loop.
151 // else, just save it in the current scope.
152 if (_code.size() > 2 && isScopeIndep) {
153 _code[_code.size() - 2] += indented;
154 } else {
155 _code.back() += indented;
156 }
157}
158
159/// @brief Create a RAII scope for iterating over vector observables. You can't use the result of vector observables
160/// outside these loop scopes.
161/// @param in A pointer to the calling class, used to determine the loop dependent variables.
162std::unique_ptr<CodegenContext::LoopScope> CodegenContext::beginLoop(RooAbsArg const *in)
163{
164 pushScope();
165 unsigned loopLevel = _code.size() - 2; // subtract global + function scope.
166 std::string idx = "loopIdx" + std::to_string(loopLevel);
167
168 std::vector<TNamed const *> vars;
169
170 // Figure out which vector observables are in the server tree of "in" with
171 // a single depth-first traversal that visits each node only once. This is
172 // equivalent to calling RooAbsArg::dependsOn() for each vector observable,
173 // but much faster for large computation graphs: dependsOn() doesn't
174 // deduplicate the visited nodes, so its cost scales with the number of
175 // paths in the graph instead of the number of nodes.
176 std::unordered_set<TNamed const *> reachableVecObs;
177 {
178 std::unordered_set<RooAbsArg const *> visited;
179 std::vector<RooAbsArg const *> stack{in};
180 while (!stack.empty()) {
181 RooAbsArg const *arg = stack.back();
182 stack.pop_back();
183 if (!visited.insert(arg).second)
184 continue;
185 if (_vecObsIndices.find(arg->namePtr()) != _vecObsIndices.end())
186 reachableVecObs.insert(arg->namePtr());
187 for (RooAbsArg const *server : arg->servers())
188 stack.push_back(server);
189 }
190 }
191
192 // Set the results of the vector observables.
193 // TODO: we are using the size of the first loop variable to the the number
194 // of iterations, but it should be made sure that all loop vars are either
195 // scalar or have the same size.
196 int firstObsIdx = -1;
197 for (auto const &it : _vecObsIndices) {
198 if (reachableVecObs.find(it.first) == reachableVecObs.end())
199 continue;
200
201 vars.push_back(it.first);
202 _nodeNames[it.first] = "obs[static_cast<int>(obs[" + std::to_string(2 * it.second) + "]) + " + idx + "]";
203 if (firstObsIdx == -1) {
204 firstObsIdx = it.second;
205 }
206 }
207
208 if (firstObsIdx == -1) {
209 throw std::runtime_error("Trying to loop over variables that are not observables!");
210 }
211
212 // Make sure that the name of this variable doesn't clash with other stuff
213 addToCodeBody(in, "#pragma clad checkpoint loop\n");
214 addToCodeBody(in, "for(int " + idx + " = 0; " + idx + " < obs[" + std::to_string(2 * firstObsIdx + 1) + "]; " + idx +
215 "++) {\n");
216
217 return std::make_unique<LoopScope>(*this, std::move(vars));
218}
219
221{
222 addToCodeBody("}\n");
223
224 // clear the results of the loop variables if they were vector observables
225 for (auto const &ptr : scope.vars()) {
226 if (_vecObsIndices.find(ptr) != _vecObsIndices.end())
227 _nodeNames.erase(ptr);
228 }
229 popScope();
230}
231
232/// @brief Get a unique variable name to be used in the generated code.
234{
235 return "t" + std::to_string(_tmpVarIdx++);
236}
237
238/// @brief A function to save an expression that includes/depends on the result of the input node.
239/// @param in The node on which the valueToSave depends on/belongs to.
240/// @param valueToSave The actual string value to save as a temporary.
241void CodegenContext::addResult(RooAbsArg const *in, std::string const &valueToSave)
242{
243 // std::string savedName = RooFit::Detail::makeValidVarName(in->GetName());
244 std::string savedName = getTmpVarName();
245
246 // Only save values if they contain operations or they are numerals. Otherwise, we can use them directly.
247
248 // Check if string is numeric.
249 char *end;
250 std::strtod(valueToSave.c_str(), &end);
251 bool isNumeric = (*end == '\0');
252
253 const bool hasOperations = valueToSave.find_first_of(":-+/*") != std::string::npos;
254
255 // If the name is not empty and this value is worth saving, save it to the correct scope.
256 // otherwise, just return the actual value itself
257 if (hasOperations || isNumeric) {
258 std::string outVarDecl = "const double " + savedName + " = " + valueToSave + ";\n";
260 } else {
262 }
263
265}
266
267/// @brief Function to save a RooListProxy as an array in the squashed code.
268/// @param in The list to convert to array.
269/// @return Name of the array that stores the input list in the squashed code.
270std::string CodegenContext::buildArg(RooAbsCollection const &in, std::string const &arrayType)
271{
272 if (in.empty()) {
273 return "nullptr";
274 }
275
276 auto it = _listNames.find(in.uniqueId().value());
277 if (it != _listNames.end())
278 return it->second;
279
280 std::string savedName = getTmpVarName();
281 bool canSaveOutside = true;
282
283 std::stringstream declStrm;
284 declStrm << arrayType << " " << savedName << "[]{";
285 for (const auto arg : in) {
286 declStrm << getResult(*arg) << ",";
288 }
289 declStrm.seekp(-1, declStrm.cur);
290 declStrm << "};\n";
291
293
294 _listNames.insert({in.uniqueId().value(), savedName});
295 return savedName;
296}
297
298std::string CodegenContext::buildArg(std::span<const double> arr)
299{
300 unsigned int n = arr.size();
301 std::string offset = std::to_string(_xlArr.size());
302 _xlArr.reserve(_xlArr.size() + n);
303 for (unsigned int i = 0; i < n; i++) {
304 _xlArr.push_back(arr[i]);
305 }
306 return "xlArr + " + offset;
307}
308
310{
311 std::ostringstream os;
312 os.imbue(std::locale::classic()); // the generated code is C++, not locale-dependent text
313 Option_t *opts = nullptr;
315 _fn = os.str();
316 const std::string info = "// Begin -- " + _fn;
317 _ctx._indent++;
319}
320
322{
323 const std::string info = "// End -- " + _fn + "\n";
324 _ctx.addToCodeBody(_arg, info);
325 _ctx._indent--;
326}
327
329{
330 _code.push_back("");
331}
332
334{
335 std::string active_scope = _code.back();
336 _code.pop_back();
337 _code.back() += active_scope;
338}
339
341{
342 return !in->isReducerNode() && _dependsOnData.find(in) == _dependsOnData.end();
343}
344
345/// @brief Register a function that is only know to the interpreter to the context.
346/// This is useful to dump the standalone C++ code for the computation graph.
347void CodegenContext::collectFunction(std::string const &name)
348{
349 _collectedFunctions.emplace_back(name);
350}
351
352/// @brief Assemble and return the final code with the return expression and global statements.
353/// @param returnExpr The string representation of what the squashed function should return, usually the head node.
354/// @return The name of the declared function.
355std::string
356CodegenContext::buildFunction(RooAbsArg const &arg, std::unordered_set<RooFit::Detail::DataKey> const &dependsOnData)
357{
359 ctx.pushScope(); // push our global scope.
360 ctx._dependsOnData = dependsOnData;
361 ctx._vecObsIndices = _vecObsIndices;
362 // We only want to take over parameters and observables
363 for (auto const &item : _nodeNames) {
364 if (startsWith(item.second, "params[") || startsWith(item.second, "obs[")) {
365 ctx._nodeNames.insert(item);
366 }
367 }
368 ctx._xlArr = _xlArr;
369 ctx._collectedFunctions = _collectedFunctions;
370 ctx._collectedCode = _collectedCode;
371
372 static int iCodegen = 0;
373 auto funcName = "roo_codegen_" + std::to_string(iCodegen++);
374
375 ctx.pushScope();
376 std::string funcBody = ctx.getResult(arg);
377 ctx.popScope();
378 funcBody = ctx._code[0] + "\n return " + funcBody + ";\n";
379
380 // Declare the function
381 std::stringstream bodyWithSigStrm;
382 bodyWithSigStrm << "double " << funcName << "(double* params, double const* obs, double const* xlArr) {\n"
383 << "constexpr double inf = std::numeric_limits<double>::infinity();\n"
384 << funcBody << "\n}\n\n";
385 ctx._collectedFunctions.emplace_back(funcName);
386 ctx._collectedCode += bodyWithSigStrm.str();
387
388 _xlArr = ctx._xlArr;
389 _collectedFunctions = ctx._collectedFunctions;
390 _collectedCode = ctx._collectedCode;
391
392 return funcName;
393}
394
395void declareDispatcherCode(std::string const &funcName)
396{
397 std::string dispatcherCode = R"(
398namespace RooFit {
399namespace Experimental {
400
401template <class Arg_t, int P>
402auto FUNC_NAME(Arg_t &arg, CodegenContext &ctx, Prio<P> p)
403{
404 if constexpr (std::is_same<Prio<P>, PrioLowest>::value) {
405 return FUNC_NAME(arg, ctx);
406 } else {
407 return FUNC_NAME(arg, ctx, p.next());
408 }
409}
410
411template <class Arg_t>
412struct Caller_FUNC_NAME {
413
414 static auto call(RooAbsArg &arg, CodegenContext &ctx)
415 {
416 return FUNC_NAME(static_cast<Arg_t &>(arg), ctx, PrioHighest{});
417 }
418};
419
420} // namespace Experimental
421} // namespace RooFit
422 )";
423
424 RooFit::Detail::replaceAll(dispatcherCode, "FUNC_NAME", funcName);
425 gInterpreter->Declare(dispatcherCode.c_str());
426}
427
429{
430 static bool codeDeclared = false;
431 if (!codeDeclared) {
432 declareDispatcherCode("codegenImpl");
433 codeDeclared = true;
434 }
435
436 using Func = void (*)(RooAbsArg &, CodegenContext &);
437
438 Func func;
439
440 TClass *tclass = arg.IsA();
441
442 // Cache the overload resolutions
443 static std::unordered_map<TClass *, Func> dispatchMap;
444
445 auto found = dispatchMap.find(tclass);
446
447 if (found != dispatchMap.end()) {
448 func = found->second;
449 } else {
450 // Can probably done with CppInterop in the future to avoid string manipulation.
451 std::stringstream cmd;
452 cmd << "&RooFit::Experimental::Caller_codegenImpl<" << tclass->GetName() << ">::call;";
453 func = reinterpret_cast<Func>(gInterpreter->ProcessLine(cmd.str().c_str()));
454 dispatchMap[tclass] = func;
455 }
456
457 return func(arg, ctx);
458}
459
460} // namespace Experimental
461} // namespace RooFit
bool startsWith(std::string_view str, std::string_view prefix)
const char Option_t
Option string (const char)
Definition RtypesCore.h:81
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h offset
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
char name[80]
Definition TGX11.cxx:142
#define gInterpreter
const_iterator end() const
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:76
TClass * IsA() const override
Definition RooAbsArg.h:656
const TNamed * namePtr() const
De-duplicated pointer to this object's name.
Definition RooAbsArg.h:482
const RefCountList_t & servers() const
List of all servers of this object.
Definition RooAbsArg.h:145
Int_t defaultPrintContents(Option_t *opt) const override
Define default contents to print.
virtual bool isReducerNode() const
Definition RooAbsArg.h:494
Abstract container object that can hold multiple RooAbsArg objects.
RooFit::UniqueId< RooAbsCollection > const & uniqueId() const
Returns a unique ID that is different for every instantiated RooAbsCollection.
A class to manage loop scopes using the RAII technique.
A class to maintain the context for squashing of RooFit models into code.
std::unordered_map< RooFit::UniqueId< RooAbsCollection >::Value_t, std::string > _listNames
A map to keep track of list names as assigned by addResult.
void addToGlobalScope(std::string const &str)
Adds the given string to the string block that will be emitted at the top of the squashed function.
std::string const & getResult(RooAbsArg const &arg)
Gets the result for the given node using the node name.
std::string getTmpVarName() const
Get a unique variable name to be used in the generated code.
void addResult(RooAbsArg const *key, std::string const &value)
A function to save an expression that includes/depends on the result of the input node.
void addToCodeBody(RooAbsArg const *klass, std::string const &in)
Adds the input string to the squashed code body.
std::unique_ptr< LoopScope > beginLoop(RooAbsArg const *in)
Create a RAII scope for iterating over vector observables.
void collectFunction(std::string const &name)
Register a function that is only know to the interpreter to the context.
void addVecObs(const char *key, int idx)
Since the squashed code represents all observables as a single flattened array, it is important to ke...
std::unordered_map< const TNamed *, int > _vecObsIndices
A map to keep track of the observable indices if they are non scalar.
int observableIndexOf(const RooAbsArg &arg) const
std::string buildArg(RooAbsCollection const &x, std::string const &arrayType="double")
Function to save a RooListProxy as an array in the squashed code.
void endLoop(LoopScope const &scope)
std::unordered_set< RooFit::Detail::DataKey > _dependsOnData
Indicate whether a node depends on the dataset.
std::vector< std::string > _collectedFunctions
bool isScopeIndependent(RooAbsArg const *in) const
std::vector< std::string > _code
The code layered by lexical scopes used as a stack.
unsigned _indent
The indentation level for pretty-printing.
std::string buildFunction(RooAbsArg const &arg, std::unordered_set< RooFit::Detail::DataKey > const &dependsOnData={})
Assemble and return the final code with the return expression and global statements.
std::unordered_map< const TNamed *, std::string > _nodeNames
Map of node names to their result strings.
ScopeRAII OutputScopeRangeComment(RooAbsArg const *arg)
int _tmpVarIdx
Index to get unique names for temporary variables.
static const TNamed * known(const char *stringPtr)
If the name is already known, return its TNamed pointer. Otherwise return 0 (don't register the name)...
virtual StyleOption defaultPrintStyle(Option_t *opt) const
virtual void printStream(std::ostream &os, Int_t contents, StyleOption style, TString indent="") const
Print description of object on ostream, printing contents set by contents integer,...
TClass instances represent classes, structs and namespaces in the ROOT type system.
Definition TClass.h:84
The TNamed class is the base class for all named ROOT classes.
Definition TNamed.h:29
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
Basic string class.
Definition TString.h:137
@ kBoth
Definition TString.h:283
const Int_t n
Definition legend1.C:16
void replaceAll(std::string &inOut, std::string_view what, std::string_view with)
void declareDispatcherCode(std::string const &funcName)
void codegen(RooAbsArg &arg, CodegenContext &ctx)
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
Definition CodegenImpl.h:73
ScopeRAII(RooAbsArg const *arg, CodegenContext &ctx)
constexpr Value_t value() const
Return numerical value of ID.
Definition UniqueId.h:59