49using std::sregex_iterator;
54void convertArobaseReferences(std::string &formula)
57 for (std::size_t i = 0; i < formula.size(); ++i) {
58 if (match && !isdigit(formula[i])) {
59 formula.insert(formula.begin() + i,
']');
62 }
else if (!match && formula[i] ==
'@') {
64 formula.insert(formula.begin() + i + 1,
'[');
74void replaceAll(std::string &inOut, std::string_view
what, std::string_view with)
76 for (std::string::size_type pos{}; inOut.npos != (pos = inOut.find(
what.data(), pos,
what.length()));
77 pos += with.length()) {
78 inOut.replace(pos,
what.length(), with.data(), with.length());
85std::vector<bool> getWordBoundaryFlags(std::string
const &s)
87 static const std::regex
r{
"\\b"};
88 std::vector<bool> out(s.size() + 1);
90 for (
auto i = std::sregex_iterator(s.begin(), s.end(),
r); i != std::sregex_iterator(); ++i) {
92 out[
m.position()] =
true;
106 std::stringstream ss;
109 return std::stod(arg.
GetName()) == std::stod(ss.str());
110 }
catch (
const std::exception &) {
111 throw std::invalid_argument(std::string(
"RooConstVar named ") + arg.
GetName() +
112 " has a name or value that cannot be converted to a valid number");
117void replaceVarNamesWithIndexStyle(std::string &formula,
RooArgList const &varList)
119 std::vector<bool> isWordBoundary = getWordBoundaryFlags(formula);
120 for (
unsigned int i = 0; i < varList.
size(); ++i) {
121 std::string_view varName = varList[i].
GetName();
124 std::string varNameStr{varName};
125 static const std::regex pureNumberNameRegex(
"^\\s*\\d+(\\.\\d+)?\\s*$");
126 if (std::regex_match(varNameStr, pureNumberNameRegex)) {
129 if (varList[i].InheritsFrom(
"RooConstVar") && isNumericNameValid(varList[i])) {
132 std::stringstream exceptionSs;
133 exceptionSs <<
"Variable '" << varName <<
"' is not a valid argument for RooFormulaVar. "
134 <<
"Variables with a name that is a number can only be of type RooConstVar "
135 <<
"and have value equal to the name";
136 throw std::invalid_argument(exceptionSs.str());
140 std::stringstream replacementStream;
141 replacementStream <<
"x[" << i <<
"]";
142 std::string replacement = replacementStream.str();
144 for (std::string::size_type pos{}; formula.npos != (pos = formula.find(varName.data(), pos, varName.length()));
145 pos += replacement.size()) {
147 std::string::size_type next = pos + varName.length();
150 if (!isWordBoundary[pos] || !isWordBoundary[next])
155 if (next < formula.size() && (formula[next] ==
'[' || formula[next] ==
']')) {
163 std::size_t nOld = varName.length();
164 std::size_t nNew = replacement.size();
165 auto wbIter = isWordBoundary.begin() + pos;
167 isWordBoundary.insert(wbIter + nOld, nNew - nOld,
false);
168 }
else if (nNew < nOld) {
169 isWordBoundary.erase(wbIter + nNew, wbIter + nOld);
173 formula.replace(pos, varName.length(), replacement);
177 <<
"Preprocessing formula: replace named references: " << varName <<
" --> " << replacement <<
"\n\t"
178 << formula << std::endl;
190RooFormulaUtils::processFormula(std::string formula,
RooArgList const &varList, std::string
const &callerName)
198 <<
"Preprocessing formula step 1: find category tags (catName::catState) in " << formula << std::endl;
201 static const std::regex categoryReg(
"(\\w+)::(\\w+)");
202 std::map<std::string, int> categoryStates;
203 for (sregex_iterator matchIt = sregex_iterator(formula.begin(), formula.end(), categoryReg);
204 matchIt != sregex_iterator(); ++matchIt) {
205 assert(matchIt->size() == 3);
206 const std::string fullMatch = (*matchIt)[0];
207 const std::string catName = (*matchIt)[1];
208 const std::string catState = (*matchIt)[2];
210 const auto catVariable =
dynamic_cast<const RooAbsCategory *
>(varList.
find(catName.c_str()));
213 <<
"Formula " << callerName <<
" uses '::' to reference a category state as '" << fullMatch
214 <<
"' but a category '" << catName <<
"' cannot be found in the input variables." << std::endl;
218 if (!catVariable->hasLabel(catState)) {
220 <<
"Formula " << callerName <<
" uses '::' to reference a category state as '" << fullMatch
221 <<
"' but the category '" << catName <<
"' does not seem to have the state '" << catState <<
"'."
223 throw std::invalid_argument(formula);
225 const int catNum = catVariable->lookupIndex(catState);
227 categoryStates[fullMatch] = catNum;
229 <<
"\n\t" << fullMatch <<
"\tname=" << catName <<
"\tstate=" << catState <<
"=" << catNum;
231 oocxcoutD(
static_cast<TObject *
>(
nullptr), InputArguments) <<
"-- End of category tags --" << std::endl;
234 for (
const auto &catState : categoryStates) {
235 replaceAll(formula, catState.first, std::to_string(catState.second));
239 <<
"Preprocessing formula step 2: replace category tags\n\t" << formula << std::endl;
242 convertArobaseReferences(formula);
245 <<
"Preprocessing formula step 3: replace '@'-references\n\t" << formula << std::endl;
248 replaceVarNamesWithIndexStyle(formula, varList);
250 oocxcoutD(
static_cast<TObject *
>(
nullptr), InputArguments) <<
"Final formula:\n\t" << formula << std::endl;
263std::string pruneAndReindexFormula(std::string
const &processedFormula,
RooArgList const &varList,
266 static const std::regex ordinalRegex(
"\\bx\\[([0-9]+)\\]");
269 std::vector<bool> varIsUsed(varList.
size());
270 for (
auto matchIt = sregex_iterator(processedFormula.begin(), processedFormula.end(), ordinalRegex);
271 matchIt != sregex_iterator(); ++matchIt) {
272 const std::size_t i = std::stoi((*matchIt)[1].str());
273 if (i < varIsUsed.size()) {
280 std::vector<int> newIndex(varList.
size(), -1);
281 for (std::size_t i = 0; i < varList.
size(); ++i) {
283 newIndex[i] = actualVars.
size();
284 actualVars.
add(varList[i]);
290 result.reserve(processedFormula.size());
291 std::size_t lastPos = 0;
292 for (
auto matchIt = sregex_iterator(processedFormula.begin(), processedFormula.end(), ordinalRegex);
293 matchIt != sregex_iterator(); ++matchIt) {
294 std::smatch match = *matchIt;
295 result.append(processedFormula, lastPos, match.position() - lastPos);
296 const std::size_t oldIdx = std::stoi(match[1].str());
297 result += oldIdx < newIndex.size() ?
"x[" + std::to_string(newIndex[oldIdx]) +
"]" : match[0].str();
298 lastPos = match.position() + match.length();
300 result.append(processedFormula, lastPos, std::string::npos);
312RooFormulaUtils::reconstructFormula(std::string internalRepr,
RooArgList const &args,
const char *fixedReplacement)
314 const auto nArgs = args.
size();
315 for (
unsigned int i = 0; i < nArgs; ++i) {
316 std::stringstream regexStr;
317 regexStr <<
"x\\[" << i <<
"\\]|@" << i;
318 std::regex regex(regexStr.str());
320 std::string replacement = fixedReplacement ? fixedReplacement : std::string(
"[") + args[i].
GetName() +
"]";
321 internalRepr = std::regex_replace(internalRepr, regex, replacement);
331std::unique_ptr<RooFormulaEvaluator>
332RooFormulaUtils::makeEvaluator(std::string
const &
name, std::string
const &processedFormula,
333 std::string
const &origFormula,
RooArgList const &varList)
336 <<
"RooFormula '" <<
name <<
"' will be compiled as "
337 <<
"\n\t" << processedFormula <<
"\n and used as"
338 <<
"\n\t" << reconstructFormula(processedFormula, varList) <<
"\n with the parameters " << varList << std::endl;
340 return std::make_unique<RooTFormulaEvaluator>(
name.c_str(), processedFormula, origFormula, varList);
348std::unique_ptr<RooFormulaEvaluator>
349RooFormulaUtils::makeFormulaEvaluator(std::string
const &
name, std::string
const &expression,
RooArgList const &varList)
351 return makeEvaluator(
name, processFormula(expression, varList,
name), expression, varList);
359void RooFormulaUtils::initFormula(std::unique_ptr<RooFormulaEvaluator> &evaluator,
TString &formExpr,
362 const std::string processed = processFormula(formExpr.
Data(), dependents,
name);
364 const std::string pruned = pruneAndReindexFormula(processed, dependents, usedVars);
365 evaluator = makeEvaluator(
name, pruned, formExpr.
Data(), usedVars);
366 actualVars.
add(usedVars);
367 formExpr = pruned.c_str();
375RooFormulaEvaluator &RooFormulaUtils::ensureEvaluator(std::unique_ptr<RooFormulaEvaluator> &evaluator,
380 std::string processed = processFormula(formExpr.
Data(), actualVars,
name);
381 evaluator = makeEvaluator(
name, processed, formExpr.
Data(), actualVars);
382 formExpr = processed.c_str();
390std::unique_ptr<RooFormulaEvaluator> RooFormulaUtils::cloneEvaluator(RooFormulaEvaluator
const &other,
393 std::unique_ptr<RooFormulaEvaluator> out = other.clone();
394 if (
TFormula *tFormula = out->getTFormula()) {
395 tFormula->SetName(newName);
407 std::vector<double> pars;
408 pars.reserve(vars.
size());
412 pars.push_back(cat.getCurrentIndex());
414 auto const &real =
static_cast<RooAbsReal const &
>(*arg);
415 pars.push_back(real.getVal(nset));
419 return evaluator.eval(pars.data());
425void RooFormulaUtils::doEvalFormula(RooFormulaEvaluator
const &evaluator,
RooArgList const &actualVars,
428 std::span<double> output = ctx.
output();
430 const std::size_t nPars = actualVars.
size();
434 std::vector<std::span<const double>> inputSpans;
435 inputSpans.reserve(nPars);
436 for (std::size_t i = 0; i < nPars; ++i) {
437 inputSpans.emplace_back(ctx.
at(
static_cast<const RooAbsReal *
>(&actualVars[i])));
440 std::vector<double> pars(nPars);
441 for (std::size_t i = 0; i < output.size(); ++i) {
442 for (std::size_t j = 0; j < nPars; ++j) {
443 pars[j] = inputSpans[j].size() > 1 ? inputSpans[j][i] : inputSpans[j][0];
445 output[i] = evaluator.eval(pars.data());
451void RooFormulaUtils::printFormula(std::ostream &os,
TString indent, std::string
const &formula,
454 os <<
indent <<
"--- RooFormula ---" << std::endl;
455 os <<
indent <<
" Formula: '" << formula <<
"'" << std::endl;
456 os <<
indent <<
" Interpretation: '" << reconstructFormula(formula, actualVars) <<
"'" << std::endl;
458 os <<
indent <<
"Servers: " << actualVars << std::endl;
463RooFormulaUtils::BinningMap RooFormulaUtils::cloneBinnings(BinningMap
const &binnings)
466 for (
auto const &item : binnings) {
467 out[item.first] = std::unique_ptr<RooAbsBinning>{item.second->clone()};
475void RooFormulaUtils::setBinning(BinningMap &binnings,
RooAbsReal const &caller,
RooArgList const &actualVars,
484 <<
") the observable " << obs.
GetName()
485 <<
" is not one of the formula variables, nothing done." << std::endl;
494 std::span<const double> boundaries{binning.
array(),
static_cast<std::size_t
>(binning.
numBoundaries())};
496 oocoutE(&caller, InputArguments)
497 << caller.
ClassName() <<
"::setBinning(" << caller.
GetName() <<
") the expression \"" << formExpr
498 <<
"\" is not flat within the given bins of " << obs.
GetName()
499 <<
". The binning is not set. Pass checkFlatness=false to override this check." << std::endl;
508 binnings[idx] = std::unique_ptr<RooAbsBinning>{binning.
clone()};
517 auto found = binnings.find(actualVars.
index(obs.
GetName()));
518 return found != binnings.end() ? found->second.get() :
nullptr;
524bool RooFormulaUtils::isBinnedDistribution(BinningMap
const &binnings,
RooArgList const &actualVars,
527 if (obs.
empty() || binnings.empty()) {
531 const int idx = actualVars.
index(o->GetName());
540 if (binnings.find(idx) == binnings.end()) {
550std::list<double> *RooFormulaUtils::binBoundaries(BinningMap
const &binnings,
RooArgList const &actualVars,
553 auto found = binnings.find(actualVars.
index(obs.
GetName()));
554 if (found == binnings.end()) {
558 auto hint =
new std::list<double>;
560 const double boundary = binning.
array()[i];
561 if (boundary >= xlo && boundary <= xhi) {
562 hint->push_back(boundary);
571std::list<double> *RooFormulaUtils::plotSamplingHint(BinningMap
const &binnings,
RooArgList const &actualVars,
574 const RooAbsBinning *binning = getBinning(binnings, actualVars, obs);
static void indent(ostringstream &buf, int indent_level)
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 r
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 result
Common abstract base class for objects that represent a value and a "shape" in RooFit.
virtual bool isCategory() const
Abstract base class for RooRealVar binning definitions.
virtual Int_t numBoundaries() const =0
virtual double * array() const =0
virtual RooAbsBinning * clone(const char *name=nullptr) const =0
A space to attach TBranches.
Abstract container object that can hold multiple RooAbsArg objects.
const char * GetName() const override
Returns name of object.
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
Int_t index(const RooAbsArg *arg) const
Returns index of given arg, or -1 if arg is not in the collection.
Storage_t::size_type size() const
RooAbsArg * find(const char *name) const
Find object with given name in list.
Abstract base class for objects that represent a real value that may appear on the left hand side of ...
Abstract base class for objects that represent a real value and implements functionality common to al...
RooArgList is a container object that can hold multiple RooAbsArg objects.
RooAbsArg * at(Int_t idx) const
Return object at given index, or nullptr if index is out of range.
RooArgSet is a container object that can hold multiple RooAbsArg objects.
static std::list< double > * plotSamplingHintForBinBoundaries(std::span< const double > boundaries, double xlo, double xhi)
Returns sampling hints for a histogram with given boundaries.
std::span< const double > at(RooAbsArg const *arg, RooAbsArg const *caller=nullptr)
std::span< double > output()
const char * GetName() const override
Returns name of object.
Mother of all ROOT objects.
virtual const char * ClassName() const
Returns name of class to which the object belongs.
const char * Data() const
void replaceAll(std::string &inOut, std::string_view what, std::string_view with)
bool isFunctionFlatInBins(const RooAbsReal &function, RooAbsRealLValue &obs, std::span< const double > boundaries, double relTol=1e-9)
Check that function is constant (flat) inside each bin defined by the sorted boundaries when scanning...