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 <type_traits>
24#include <unordered_map>
25
26namespace {
27
28bool startsWith(std::string_view str, std::string_view prefix)
29{
30 return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix);
31}
32
33} // namespace
34
35namespace RooFit {
36namespace Experimental {
37
38/// @brief Adds (or overwrites) the string representing the result of a node.
39/// @param key The name of the node to add the result for.
40/// @param value The new name to assign/overwrite.
41void CodegenContext::addResult(const char *key, std::string const &value)
42{
43 const TNamed *namePtr = RooNameReg::known(key);
44 if (namePtr)
45 addResult(namePtr, value);
46}
47
48void CodegenContext::addResult(TNamed const *key, std::string const &value)
49{
50 _nodeNames[key] = value;
51}
52
53/// @brief Gets the result for the given node using the node name. This node also performs the necessary
54/// code generation through recursive calls to 'translate'. A call to this function modifies the already
55/// existing code body.
56/// @param key The node to get the result string for.
57/// @return String representing the result of this node.
58std::string const &CodegenContext::getResult(RooAbsArg const &arg)
59{
60 // If the result has already been recorded, just return the result.
61 // It is usually the responsibility of each translate function to assign
62 // the proper result to its class. Hence, if a result has already been recorded
63 // for a particular node, it means the node has already been 'translate'd and we
64 // dont need to visit it again.
65 auto found = _nodeNames.find(arg.namePtr());
66 if (found != _nodeNames.end())
67 return found->second;
68
69 // The result for vector observables should already be in the map if you
70 // opened the loop scope. This is just to check if we did not request the
71 // result of a vector-valued observable outside of the scope of a loop.
72 auto foundVecObs = _vecObsIndices.find(arg.namePtr());
73 if (foundVecObs != _vecObsIndices.end()) {
74 throw std::runtime_error("You requested the result of a vector observable outside a loop scope for it!");
75 }
76
77 auto RAII(OutputScopeRangeComment(&arg));
78
79 // Now, recursively call translate into the current argument to load the correct result.
80 codegen(const_cast<RooAbsArg &>(arg), *this);
81
82 return _nodeNames.at(arg.namePtr());
83}
84
85/// @brief Adds the given string to the string block that will be emitted at the top of the squashed function. Useful
86/// for variable declarations.
87/// @param str The string to add to the global scope.
88void CodegenContext::addToGlobalScope(std::string const &str)
89{
90 // Introduce proper indentation for multiline strings.
91 _code[0] += str;
92}
93
94/// @brief Since the squashed code represents all observables as a single flattened array, it is important
95/// to keep track of the start index for a vector valued observable which can later be expanded to access the correct
96/// element. For example, a vector valued variable x with 10 entries will be squashed to obs[start_idx + i].
97/// @param key The name of the node representing the vector valued observable.
98/// @param idx The start index (or relative position of the observable in the set of all observables).
99void CodegenContext::addVecObs(const char *key, int idx)
100{
101 const TNamed *namePtr = RooNameReg::known(key);
102 if (namePtr)
103 _vecObsIndices[namePtr] = idx;
104}
105
106/// @brief Adds the input string to the squashed code body. If a class implements a translate function that wants to
107/// emit something to the squashed code body, it must call this function with the code it wants to emit. In case of
108/// loops, automatically determines if code needs to be stored inside or outside loop scope.
109/// @param klass The class requesting this addition, usually 'this'.
110/// @param in String to add to the squashed code.
111void CodegenContext::addToCodeBody(RooAbsArg const *klass, std::string const &in)
112{
113 // If we are in a loop and the value is scope independent, save it at the top of the loop.
114 // else, just save it in the current scope.
116}
117
118/// @brief A variation of the previous addToCodeBody that takes in a bool value that determines
119/// if input is independent. This overload exists because there might other ways to determine if
120/// a value/collection of values is scope independent.
121/// @param in String to add to the squashed code.
122/// @param isScopeIndep The value determining if the input is scope dependent.
123void CodegenContext::addToCodeBody(std::string const &in, bool isScopeIndep /* = false */)
124{
125 TString indented = in;
126 indented = indented.Strip(TString::kBoth); // trim
127
128 std::string indent_str = "";
129 for (unsigned i = 0; i < _indent; ++i)
130 indent_str += " ";
131 indented = indented.Prepend(indent_str);
132
133 // FIXME: Multiline input.
134 // indent_str += "\n";
135 // indented = indented.ReplaceAll("\n", indent_str);
136
137 // If we are in a loop and the value is scope independent, save it at the top of the loop.
138 // else, just save it in the current scope.
139 if (_code.size() > 2 && isScopeIndep) {
140 _code[_code.size() - 2] += indented;
141 } else {
142 _code.back() += indented;
143 }
144}
145
146/// @brief Create a RAII scope for iterating over vector observables. You can't use the result of vector observables
147/// outside these loop scopes.
148/// @param in A pointer to the calling class, used to determine the loop dependent variables.
149std::unique_ptr<CodegenContext::LoopScope> CodegenContext::beginLoop(RooAbsArg const *in)
150{
151 pushScope();
152 unsigned loopLevel = _code.size() - 2; // substract global + function scope.
153 std::string idx = "loopIdx" + std::to_string(loopLevel);
154
155 std::vector<TNamed const *> vars;
156 // set the results of the vector observables
157 for (auto const &it : _vecObsIndices) {
158 if (!in->dependsOn(it.first))
159 continue;
160
161 vars.push_back(it.first);
162 _nodeNames[it.first] = "obs[" + std::to_string(it.second) + " + " + idx + "]";
163 }
164
165 // TODO: we are using the size of the first loop variable to the the number
166 // of iterations, but it should be made sure that all loop vars are either
167 // scalar or have the same size.
168 std::size_t numEntries = 1;
169 for (auto &it : vars) {
170 std::size_t n = outputSize(it);
171 if (n > 1 && numEntries > 1 && n != numEntries) {
172 throw std::runtime_error("Trying to loop over variables with different sizes!");
173 }
174 numEntries = std::max(n, numEntries);
175 }
176
177 // Make sure that the name of this variable doesn't clash with other stuff
178 addToCodeBody(in, "for(int " + idx + " = 0; " + idx + " < " + std::to_string(numEntries) + "; " + idx + "++) {\n");
179
180 return std::make_unique<LoopScope>(*this, std::move(vars));
181}
182
184{
185 addToCodeBody("}\n");
186
187 // clear the results of the loop variables if they were vector observables
188 for (auto const &ptr : scope.vars()) {
189 if (_vecObsIndices.find(ptr) != _vecObsIndices.end())
190 _nodeNames.erase(ptr);
191 }
192 popScope();
193}
194
195/// @brief Get a unique variable name to be used in the generated code.
197{
198 return "t" + std::to_string(_tmpVarIdx++);
199}
200
201/// @brief A function to save an expression that includes/depends on the result of the input node.
202/// @param in The node on which the valueToSave depends on/belongs to.
203/// @param valueToSave The actual string value to save as a temporary.
204void CodegenContext::addResult(RooAbsArg const *in, std::string const &valueToSave)
205{
206 // std::string savedName = RooFit::Detail::makeValidVarName(in->GetName());
207 std::string savedName = getTmpVarName();
208
209 // Only save values if they contain operations.
210 bool hasOperations = valueToSave.find_first_of(":-+/*") != std::string::npos;
211
212 // If the name is not empty and this value is worth saving, save it to the correct scope.
213 // otherwise, just return the actual value itself
214 if (hasOperations) {
215 // If this is a scalar result, it will go just outside the loop because
216 // it doesn't need to be recomputed inside loops.
217 std::string outVarDecl = "const double " + savedName + " = " + valueToSave + ";\n";
218 addToCodeBody(in, outVarDecl);
219 } else {
220 savedName = valueToSave;
221 }
222
223 addResult(in->namePtr(), savedName);
224}
225
226/// @brief Function to save a RooListProxy as an array in the squashed code.
227/// @param in The list to convert to array.
228/// @return Name of the array that stores the input list in the squashed code.
230{
231 if (in.empty()) {
232 return "nullptr";
233 }
234
235 auto it = _listNames.find(in.uniqueId().value());
236 if (it != _listNames.end())
237 return it->second;
238
239 std::string savedName = getTmpVarName();
240 bool canSaveOutside = true;
241
242 std::stringstream declStrm;
243 declStrm << "double " << savedName << "[] = {";
244 for (const auto arg : in) {
245 declStrm << getResult(*arg) << ",";
246 canSaveOutside = canSaveOutside && isScopeIndependent(arg);
247 }
248 declStrm.seekp(-1, declStrm.cur);
249 declStrm << "};\n";
250
251 addToCodeBody(declStrm.str(), canSaveOutside);
252
253 _listNames.insert({in.uniqueId().value(), savedName});
254 return savedName;
255}
256
257std::string CodegenContext::buildArg(std::span<const double> arr)
258{
259 unsigned int n = arr.size();
260 std::string offset = std::to_string(_xlArr.size());
261 _xlArr.reserve(_xlArr.size() + n);
262 for (unsigned int i = 0; i < n; i++) {
263 _xlArr.push_back(arr[i]);
264 }
265 return "xlArr + " + offset;
266}
267
268CodegenContext::ScopeRAII::ScopeRAII(RooAbsArg const *arg, CodegenContext &ctx) : _ctx(ctx), _arg(arg)
269{
270 std::ostringstream os;
271 Option_t *opts = nullptr;
273 _fn = os.str();
274 const std::string info = "// Begin -- " + _fn;
275 _ctx._indent++;
276 _ctx.addToCodeBody(_arg, info);
277}
278
280{
281 const std::string info = "// End -- " + _fn + "\n";
282 _ctx.addToCodeBody(_arg, info);
283 _ctx._indent--;
284}
285
287{
288 _code.push_back("");
289}
290
292{
293 std::string active_scope = _code.back();
294 _code.pop_back();
295 _code.back() += active_scope;
296}
297
299{
300 return !in->isReducerNode() && outputSize(in->namePtr()) == 1;
301}
302
303/// @brief Register a function that is only know to the interpreter to the context.
304/// This is useful to dump the standalone C++ code for the computation graph.
305void CodegenContext::collectFunction(std::string const &name)
306{
307 _collectedFunctions.emplace_back(name);
308}
309
310/// @brief Assemble and return the final code with the return expression and global statements.
311/// @param returnExpr The string representation of what the squashed function should return, usually the head node.
312/// @return The name of the declared function.
313std::string
314CodegenContext::buildFunction(RooAbsArg const &arg, std::map<RooFit::Detail::DataKey, std::size_t> const &outputSizes)
315{
316 CodegenContext ctx;
317 ctx.pushScope(); // push our global scope.
320 // We only want to take over parameters and observables
321 for (auto const &item : _nodeNames) {
322 if (startsWith(item.second, "params[") || startsWith(item.second, "obs[")) {
323 ctx._nodeNames.insert(item);
324 }
325 }
326 ctx._xlArr = _xlArr;
328
329 static int iCodegen = 0;
330 auto funcName = "roo_codegen_" + std::to_string(iCodegen++);
331
332 ctx.pushScope();
333 std::string funcBody = ctx.getResult(arg);
334 ctx.popScope();
335 funcBody = ctx._code[0] + "\n return " + funcBody + ";\n";
336
337 // Declare the function
338 std::stringstream bodyWithSigStrm;
339 bodyWithSigStrm << "double " << funcName << "(double* params, double const* obs, double const* xlArr) {\n"
340 << funcBody << "\n}";
341 ctx._collectedFunctions.emplace_back(funcName);
342 if (!gInterpreter->Declare(bodyWithSigStrm.str().c_str())) {
343 std::stringstream errorMsg;
344 errorMsg << "Function " << funcName << " could not be compiled. See above for details.";
345 oocoutE(nullptr, InputArguments) << errorMsg.str() << std::endl;
346 throw std::runtime_error(errorMsg.str().c_str());
347 }
348
349 _xlArr = ctx._xlArr;
351
352 return funcName;
353}
354
355void declareDispatcherCode(std::string const &funcName)
356{
357 std::string dispatcherCode = R"(
358namespace RooFit {
359namespace Experimental {
360
361template <class Arg_t, int P>
362auto FUNC_NAME(Arg_t &arg, CodegenContext &ctx, Prio<P> p)
363{
364 if constexpr (std::is_same<Prio<P>, PrioLowest>::value) {
365 return FUNC_NAME(arg, ctx);
366 } else {
367 return FUNC_NAME(arg, ctx, p.next());
368 }
369}
370
371template <class Arg_t>
372struct Caller_FUNC_NAME {
373
374 static auto call(RooAbsArg &arg, CodegenContext &ctx)
375 {
376 return FUNC_NAME(static_cast<Arg_t &>(arg), ctx, PrioHighest{});
377 }
378};
379
380} // namespace Experimental
381} // namespace RooFit
382 )";
383
384 RooFit::Detail::replaceAll(dispatcherCode, "FUNC_NAME", funcName);
385 gInterpreter->Declare(dispatcherCode.c_str());
386}
387
389{
390 static bool codeDeclared = false;
391 if (!codeDeclared) {
392 declareDispatcherCode("codegenImpl");
393 codeDeclared = true;
394 }
395
396 using Func = void (*)(RooAbsArg &, CodegenContext &);
397
398 Func func;
399
400 TClass *tclass = arg.IsA();
401
402 // Cache the overload resolutions
403 static std::unordered_map<TClass *, Func> dispatchMap;
404
405 auto found = dispatchMap.find(tclass);
406
407 if (found != dispatchMap.end()) {
408 func = found->second;
409 } else {
410 // Can probably done with CppInterop in the future to avoid string manipulation.
411 std::stringstream cmd;
412 cmd << "&RooFit::Experimental::Caller_codegenImpl<" << tclass->GetName() << ">::call;";
413 func = reinterpret_cast<Func>(gInterpreter->ProcessLine(cmd.str().c_str()));
414 dispatchMap[tclass] = func;
415 }
416
417 return func(arg, ctx);
418}
419
420} // namespace Experimental
421} // namespace RooFit
bool startsWith(std::string_view str, std::string_view prefix)
#define oocoutE(o, a)
const char Option_t
Definition RtypesCore.h:66
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:110
#define gInterpreter
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:77
bool dependsOn(const RooAbsCollection &serverList, const RooAbsArg *ignoreArg=nullptr, bool valueOnly=false) const
Test whether we depend on (ie, are served by) any object in the specified collection.
const TNamed * namePtr() const
De-duplicated pointer to this object's name.
Definition RooAbsArg.h:504
Int_t defaultPrintContents(Option_t *opt) const override
Define default contents to print.
virtual bool isReducerNode() const
Definition RooAbsArg.h:518
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.
std::vector< TNamed const * > const & vars() const
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.
std::map< RooFit::Detail::DataKey, std::size_t > _nodeOutputSizes
Map of node output sizes.
std::string buildFunction(RooAbsArg const &arg, std::map< RooFit::Detail::DataKey, std::size_t > const &outputSizes={})
Assemble and return the final code with the return expression and global statements.
void endLoop(LoopScope const &scope)
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::unordered_map< const TNamed *, std::string > _nodeNames
Map of node names to their result strings.
std::size_t outputSize(RooFit::Detail::DataKey key) const
Figure out the output size of a node.
ScopeRAII OutputScopeRangeComment(RooAbsArg const *arg)
std::string buildArg(RooAbsCollection const &x)
Function to save a RooListProxy as an array in the squashed code.
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:81
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:47
TClass * IsA() const override
Definition TNamed.h:58
Basic string class.
Definition TString.h:139
TSubString Strip(EStripType s=kTrailing, char c=' ') const
Return a substring of self stripped at beginning and/or end.
Definition TString.cxx:1163
@ kBoth
Definition TString.h:276
TString & Prepend(const char *cs)
Definition TString.h:673
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:64
@ InputArguments
ScopeRAII(RooAbsArg const *arg, CodegenContext &ctx)
constexpr Value_t value() const
Return numerical value of ID.
Definition UniqueId.h:59