Belle II Software development
Manager.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <analysis/VariableManager/Manager.h>
10#include <analysis/dataobjects/Particle.h>
11#include <analysis/dataobjects/ParticleList.h>
12
13#include <framework/logging/Logger.h>
14#include <framework/utilities/Conversion.h>
15#include <framework/utilities/GeneralCut.h>
16
17#include <boost/algorithm/string.hpp>
18
19#include <string>
20#include <regex>
21#include <set>
22
23using namespace Belle2;
24
25Variable::Manager::~Manager() = default;
27{
28 static Variable::Manager v;
29 return v;
30}
31
32const Variable::Manager::Var* Variable::Manager::getVariable(const std::string& functionName,
33 const std::vector<std::string>& functionArguments)
34{
35 // Combine to full name for alias resolving
36 std::string fullname = functionName + "(" + boost::algorithm::join(functionArguments, ", ") + ")";
37
38 // resolve aliases. Aliases might point to other aliases so we need to keep a
39 // set of what we have seen so far to avoid running into infinite loops
40 std::set<std::string> aliasesSeen;
41 for (auto aliasIter = m_alias.find(fullname); aliasIter != m_alias.end(); aliasIter = m_alias.find(fullname)) {
42 const auto [it, added] = aliasesSeen.insert(fullname);
43 if (!added) {
44 B2FATAL("Encountered a loop in the alias definitions between the aliases "
45 << boost::algorithm::join(aliasesSeen, ", "));
46 }
47 fullname = aliasIter->second;
48 }
49 auto mapIter = m_variables.find(fullname);
50 if (mapIter == m_variables.end()) {
51 if (!createVariable(fullname, functionName, functionArguments)) return nullptr;
52 mapIter = m_variables.find(fullname);
53 if (mapIter == m_variables.end()) return nullptr;
54 }
55 return mapIter->second.get();
56}
57
59{
60 // resolve aliases. Aliases might point to other aliases so we need to keep a
61 // set of what we have seen so far to avoid running into infinite loops
62 std::set<std::string> aliasesSeen;
63 for (auto aliasIter = m_alias.find(name); aliasIter != m_alias.end(); aliasIter = m_alias.find(name)) {
64 const auto [it, added] = aliasesSeen.insert(name);
65 if (!added) {
66 B2FATAL("Encountered a loop in the alias definitions between the aliases "
67 << boost::algorithm::join(aliasesSeen, ", "));
68 }
69 name = aliasIter->second;
70 }
71 auto mapIter = m_variables.find(name);
72 if (mapIter == m_variables.end()) {
73 if (!createVariable(name)) return nullptr;
74 mapIter = m_variables.find(name);
75 if (mapIter == m_variables.end()) return nullptr;
76 }
77 return mapIter->second.get();
78}
79
80std::vector<const Variable::Manager::Var*> Variable::Manager::getVariables(const std::vector<std::string>& variables)
81{
82
83 std::vector<const Variable::Manager::Var*> variable_pointers;
84 for (auto& variable : variables) {
85 const Var* x = getVariable(variable);
86 if (x == nullptr) {
87 B2WARNING("Couldn't find variable " << variable << " via the Variable::Manager. Check the name!");
88 }
89 variable_pointers.push_back(x);
90 }
91 return variable_pointers;
92
93}
94
95
96bool Variable::Manager::addAlias(const std::string& alias, const std::string& variable)
97{
98
99 assertValidName(alias);
100
101 if (m_alias.find(alias) != m_alias.end()) {
102 if (variable == m_alias[alias]) { return true; }
103 B2WARNING("An alias with the name '" << alias << "' exists and is set to '" << m_alias[alias] << "', setting it to '" << variable <<
104 "'. Be aware: only the last alias defined before processing the events will be used!");
105 m_alias[alias] = variable;
106 return true;
107 }
108
109 if (m_variables.find(alias) != m_variables.end()) {
110 B2ERROR("Variable with the name '" << alias << "' exists already, cannot add it as an alias!");
111 return false;
112 }
113
114 m_alias.insert(std::make_pair(alias, variable));
115 return true;
116}
117
119{
120 m_alias.clear();
121}
122
124{
125 long unsigned int longest_alias_size = 0;
126 for (const auto& a : m_alias) {
127 if (a.first.length() > longest_alias_size) {
128 longest_alias_size = a.first.length();
129 }
130 }
131 B2INFO("=====================================");
132 B2INFO("The following aliases are registered:");
133 for (const auto& a : m_alias) {
134 B2INFO(std::string(a.first, 0, longest_alias_size) << std::string(longest_alias_size - a.first.length(),
135 ' ') << " --> " << a.second);
136 }
137 B2INFO("=====================================");
138}
139
140std::string Variable::Manager::resolveAlias(const std::string& alias)
141{
142
143 assertValidName(alias);
144
145 if (m_alias.find(alias) == m_alias.end()) {
146 return alias;
147 } else {
148 return m_alias[alias];
149 }
150}
151
152bool Variable::Manager::addCollection(const std::string& collection, const std::vector<std::string>& variables)
153{
154
155 assertValidName(collection);
156
157 if (m_collection.find(collection) != m_collection.end()) {
158 B2WARNING("Another collection with the name'" << collection << "' is already set! I overwrite it!");
159 m_collection[collection] = variables;
160 return true;
161 }
162
163 if (m_variables.find(collection) != m_variables.end()) {
164 B2ERROR("Variable with the name '" << collection << "' exists already, won't add it as an collection!");
165 return false;
166 }
167
168 m_collection.insert(std::make_pair(collection, variables));
169 return true;
170}
171
172
173std::vector<std::string> Variable::Manager::getCollection(const std::string& collection)
174{
175
176 return m_collection[collection];
177
178}
179
180std::vector<std::string> Variable::Manager::resolveCollections(const std::vector<std::string>& variables)
181{
182
183 std::vector<std::string> temp;
184
185 for (const auto& var : variables) {
186 auto it = m_collection.find(var);
187 if (it != m_collection.end()) {
188 temp.insert(temp.end(), it->second.begin(), it->second.end());
189 } else {
190 temp.push_back(var);
191 }
192 }
193 return temp;
194
195}
196
197
198void Variable::Manager::assertValidName(const std::string& name)
199{
200 const static std::regex allowedNameRegex("^[a-zA-Z0-9_]*$");
201
202 if (!std::regex_match(name, allowedNameRegex)) {
203 B2FATAL("Variable '" << name <<
204 "' contains forbidden characters! Only alphanumeric characters plus underscores (_) are allowed for variable names.");
205 }
206}
207
208
209void Variable::Manager::setVariableGroup(const std::string& groupName)
210{
211 m_currentGroup = groupName;
212}
213
214bool Variable::Manager::createVariable(const std::string& name)
215{
216 std::match_results<std::string::const_iterator> results;
217
218 // Check if name is a simple number
219 if (std::regex_match(name, results, std::regex("^([0-9]+\\.?[0-9]*)$"))) {
220 float float_number = std::stof(results[1]);
221 auto func = [float_number](const Particle*) -> double {
222 return float_number;
223 };
224 m_variables[name] = std::make_shared<Var>(name, func, std::string("Returns number ") + name);
225 return true;
226 }
227
228 // Check if name is a function call
229 if (std::regex_match(name, results, std::regex("^([a-zA-Z0-9_]*)\\((.*)\\)$"))) {
230
231 std::string functionName = results[1];
232 boost::algorithm::trim(functionName);
233 std::vector<std::string> functionArguments = splitOnDelimiterAndConserveParenthesis(results[2], ',', '(', ')');
234 for (auto& str : functionArguments) {
235 boost::algorithm::trim(str);
236 }
237
238 // Search function name in parameter variables
239 auto parameterIter = m_parameter_variables.find(functionName);
240 if (parameterIter != m_parameter_variables.end()) {
241
242 std::vector<double> arguments;
243 for (auto& arg : functionArguments) {
244 double number = 0;
245 number = Belle2::convertString<double>(arg);
246 arguments.push_back(number);
247 }
248 auto pfunc = parameterIter->second->function;
249 auto func = [pfunc, arguments](const Particle * particle) -> VarVariant { return pfunc(particle, arguments); };
250 m_variables[name] = std::make_shared<Var>(name, func, parameterIter->second->description, parameterIter->second->group,
251 parameterIter->second->variabletype);
252 return true;
253
254 }
255
256 // Search function name in meta variables
257 auto metaIter = m_meta_variables.find(functionName);
258 if (metaIter != m_meta_variables.end()) {
259 auto func = metaIter->second->function(functionArguments);
260 m_variables[name] = std::make_shared<Var>(name, func, metaIter->second->description, metaIter->second->group,
261 metaIter->second->variabletype);
262 return true;
263 }
264 }
265 // Try Formula registration with python parser if variable is not a simple identifier (else we get a infinite loop)
266 if (not std::regex_match(name, std::regex("^[a-zA-Z_][a-zA-Z_0-9]*$")) and not name.empty()) {
267 Py_Initialize();
268 try {
269 // Import parser
270 py::object b2parser_namespace = py::import("b2parser");
271 // Parse Expression
272 py::tuple expression_tuple = py::extract<boost::python::tuple>(b2parser_namespace.attr("parse_expression")(name));
273 try {
274 // Compile ExpressionNode
275 std::shared_ptr<const AbstractExpressionNode<Belle2::Variable::Manager>> expression_node =
276 NodeFactory::compile_expression_node<Belle2::Variable::Manager>(expression_tuple);
277 // Create lambda capturing the ExpressionNode
278 Variable::Manager::FunctionPtr func = [expression_node](const Particle * object) -> VarVariant {
279 return expression_node->evaluate(object);
280 };
281 // Put new variable into set of variables
282 m_variables[name] = std::make_shared<Var>(name, func, std::string("Returns expression ") + name);
283 return true;
284 } catch (std::runtime_error& exception) {
285 B2FATAL("Encountered bad variable name '" << name << "'. Maybe you misspelled it?");
286 }
287 } catch (py::error_already_set&) {
288 PyErr_Print();
289 B2FATAL("Parsing error for formula: '" << name << "'");
290 }
291 }
292
293 B2FATAL("Encountered bad variable name '" << name << "'. Maybe you misspelled it?");
294 return false;
295}
296
297bool Variable::Manager::createVariable(const std::string& fullname, const std::string& functionName,
298 const std::vector<std::string>& functionArguments)
299{
300 // Search function name in parameter variables
301 auto parameterIter = m_parameter_variables.find(functionName);
302 if (parameterIter != m_parameter_variables.end()) {
303
304 std::vector<double> arguments;
305 for (auto& arg : functionArguments) {
306 double number = 0;
307 number = Belle2::convertString<double>(arg);
308 arguments.push_back(number);
309 }
310 auto pfunc = parameterIter->second->function;
311 auto func = [pfunc, arguments](const Particle * particle) -> std::variant<double, int, bool> { return pfunc(particle, arguments); };
312 m_variables[fullname] = std::make_shared<Var>(fullname, func, parameterIter->second->description, parameterIter->second->group,
313 parameterIter->second->variabletype);
314 return true;
315
316 }
317
318 // Search function fullname in meta variables
319 auto metaIter = m_meta_variables.find(functionName);
320 if (metaIter != m_meta_variables.end()) {
321 auto func = metaIter->second->function(functionArguments);
322 m_variables[fullname] = std::make_shared<Var>(fullname, func, metaIter->second->description, metaIter->second->group,
323 metaIter->second->variabletype);
324 return true;
325 }
326
327 B2FATAL("Encountered bad variable name '" << fullname << "'. Maybe you misspelled it?");
328 return false;
329}
330
331
333 const std::string& description, const Variable::Manager::VariableDataType& variabletype,
334 const std::string& unit)
335{
336 if (!f) {
337 B2FATAL("No function provided for variable '" << name << "'.");
338 }
339
340 assertValidName(name);
341
342 auto mapIter = m_variables.find(name);
343 if (mapIter == m_variables.end()) {
344
345 auto var = std::make_shared<Var>(name, f, description, m_currentGroup, variabletype);
346 B2DEBUG(19, "Registered Variable " << name);
347 m_variables[name] = var;
348 m_variablesInRegistrationOrder.push_back(var.get());
349 if (!unit.empty()) {
350 var.get()->extendDescriptionString(":Unit: " + unit);
351 }
352 } else {
353 B2FATAL("A variable named '" << name << "' was already registered! Note that all variables need a unique name!");
354 }
355}
356
358 const std::string& description, const Variable::Manager::VariableDataType& variabletype,
359 const std::string& unit)
360{
361 if (!f) {
362 B2FATAL("No function provided for variable '" << name << "'.");
363 }
364
365 auto mapIter = m_parameter_variables.find(name);
366 if (mapIter == m_parameter_variables.end()) {
367 auto var = std::make_shared<ParameterVar>(name, f, description, m_currentGroup, variabletype);
368 std::string rawName = name.substr(0, name.find('('));
369 assertValidName(rawName);
370 B2DEBUG(19, "Registered parameter Variable " << rawName);
371 m_parameter_variables[rawName] = var;
372 m_variablesInRegistrationOrder.push_back(var.get());
373 if (!unit.empty()) {
374 var.get()->extendDescriptionString(":Unit: " + unit);
375 }
376 } else {
377 B2FATAL("A variable named '" << name << "' was already registered! Note that all variables need a unique name!");
378 }
379}
380
382 const std::string& description, const Variable::Manager::VariableDataType& variabletype)
383{
384 if (!f) {
385 B2FATAL("No function provided for variable '" << name << "'.");
386 }
387
388 auto mapIter = m_meta_variables.find(name);
389 if (mapIter == m_meta_variables.end()) {
390 auto var = std::make_shared<MetaVar>(name, f, description, m_currentGroup, variabletype);
391 std::string rawName = name.substr(0, name.find('('));
392 assertValidName(rawName);
393 B2DEBUG(19, "Registered meta Variable " << rawName);
394 m_meta_variables[rawName] = var;
395 m_variablesInRegistrationOrder.push_back(var.get());
396 } else {
397 B2FATAL("A variable named '" << name << "' was already registered! Note that all variables need a unique name!");
398 }
399}
400
401void Variable::Manager::deprecateVariable(const std::string& name, bool make_fatal, const std::string& version,
402 const std::string& description)
403{
404 auto varIter = m_deprecated.find(name);
405 if (varIter == m_deprecated.end())
406 m_deprecated.insert(std::make_pair(name, std::make_pair(make_fatal, description)));
407 else
408 B2FATAL("There seem to be two calls to deprecate the variable: Please remove one.");
409
410 auto mapIter = m_variables.find(name);
411 if (mapIter != m_variables.end()) {
412 if (make_fatal) {
413 mapIter->second.get()->extendDescriptionString("\n\n.. warning:: ");
414 } else {
415 mapIter->second.get()->extendDescriptionString("\n\n.. note:: ");
416 }
417 mapIter->second.get()->extendDescriptionString(".. deprecated:: " + version + "\n " + description);
418 } else {
419 auto parMapIter = m_parameter_variables.find(name);
420 if (parMapIter != m_parameter_variables.end()) {
421 if (make_fatal) {
422 parMapIter->second.get()->extendDescriptionString("\n\n.. warning:: ");
423 } else {
424 parMapIter->second.get()->extendDescriptionString("\n\n.. note:: ");
425 }
426 parMapIter->second.get()->extendDescriptionString(".. deprecated:: " + version + "\n " + description);
427 } else {
428 auto metaMapIter = m_meta_variables.find(name);
429 if (metaMapIter != m_meta_variables.end()) {
430 if (make_fatal) {
431 metaMapIter->second.get()->extendDescriptionString("\n\n.. warning:: ");
432 } else {
433 metaMapIter->second.get()->extendDescriptionString("\n\n.. note:: ");
434 }
435 metaMapIter->second.get()->extendDescriptionString(".. deprecated:: " + version + "\n " + description);
436 } else {
437 B2FATAL("The variable '" << name << "' is not registered so it makes no sense to try to deprecate it.");
438 }
439 }
440 }
441
442}
443
445{
446 auto varIter = m_deprecated.find(name);
447
448 if (varIter == m_deprecated.end())
449 return;
450 else {
451 bool make_fatal = varIter->second.first;
452 std::string message = varIter->second.second;
453 if (make_fatal)
454 B2FATAL("Variable " << name << " is deprecated. " << message);
455 else
456 B2WARNING("Variable " << name << " is deprecated. " << message);
457 }
458}
459
460
461std::vector<std::string> Variable::Manager::getNames() const
462{
463 std::vector<std::string> names;
464 for (const VarBase* var : m_variablesInRegistrationOrder) {
465 names.push_back(var->name);
466 }
467 return names;
468}
469
470std::vector<std::string> Variable::Manager::getAliasNames() const
471{
472 std::vector<std::string> names;
473 for (auto al : m_alias) names.push_back(al.first);
474 return names;
475}
476
477double Variable::Manager::evaluate(const std::string& varName, const Particle* p)
478{
479 const Var* var = getVariable(varName);
480 if (!var) {
481 throw std::runtime_error("Variable::Manager::evaluate(): variable '" + varName + "' not found!");
482 }
483
484 if (var->variabletype == Variable::Manager::VariableDataType::c_double)
485 return std::get<double>(var->function(p));
486 else if (var->variabletype == Variable::Manager::VariableDataType::c_int)
487 return (double)std::get<int>(var->function(p));
488 else if (var->variabletype == Variable::Manager::VariableDataType::c_bool)
489 return (double)std::get<bool>(var->function(p));
490 else return std::numeric_limits<double>::quiet_NaN();
491}
492
493std::vector<double> Variable::Manager::evaluateVariables(const std::vector<std::string>& varNames, const ParticleList* plist)
494{
495 std::vector<double> values;
496 values.reserve(varNames.size() * plist->getListSize());
497 for (size_t iPart = 0; iPart < plist->getListSize(); ++iPart) {
498 const Particle* p = plist->getParticle(iPart);
499 for (size_t iVar = 0; iVar < varNames.size(); ++iVar)
500 values.push_back(evaluate(varNames[iVar], p));
501 }
502 return values;
503}
ParticleList is a container class that stores a collection of Particle objects.
Definition: ParticleList.h:140
Class to store reconstructed particles.
Definition: Particle.h:76
Global list of available variables.
Definition: Manager.h:100
std::vector< const Belle2::Variable::Manager::VarBase * > getVariables() const
Return list of all variables (in order registered).
Definition: Manager.h:229
std::vector< std::string > getAliasNames() const
Return a list of all variable alias names (in reverse order added).
Definition: Manager.cc:470
std::function< VarVariant(const Particle *)> FunctionPtr
functions stored take a const Particle* and return VarVariant.
Definition: Manager.h:112
std::vector< std::string > resolveCollections(const std::vector< std::string > &variables)
Resolve Collection Returns variable names corresponding to the given collection or if it is not a col...
Definition: Manager.cc:180
const Var * getVariable(std::string name)
Get the variable belonging to the given key.
Definition: Manager.cc:58
std::variant< double, int, bool > VarVariant
NOTE: the python interface is documented manually in analysis/doc/Variables.rst (because we use ROOT ...
Definition: Manager.h:110
void deprecateVariable(const std::string &name, bool make_fatal, const std::string &version, const std::string &description)
Make a variable deprecated.
Definition: Manager.cc:401
static Manager & Instance()
get singleton instance.
Definition: Manager.cc:26
void printAliases()
Print existing aliases.
Definition: Manager.cc:123
bool createVariable(const std::string &name)
Creates and registers a concrete variable (Var) from a MetaVar, ParameterVar or numeric constant.
Definition: Manager.cc:214
void assertValidName(const std::string &name)
Abort with B2FATAL if name is not a valid name for a variable.
Definition: Manager.cc:198
void clearAliases()
Clear existing aliases.
Definition: Manager.cc:118
std::function< FunctionPtr(const std::vector< std::string > &)> MetaFunctionPtr
meta functions stored take a const std::vector<std::string>& and return a FunctionPtr.
Definition: Manager.h:116
std::vector< std::string > getNames() const
Return list of all variable names (in order registered).
Definition: Manager.cc:461
void setVariableGroup(const std::string &groupName)
All variables registered after VARIABLE_GROUP(groupName) will be added to this group.
Definition: Manager.cc:209
std::string resolveAlias(const std::string &alias)
Resolve alias Return original variable name.
Definition: Manager.cc:140
std::vector< std::string > getCollection(const std::string &collection)
Get Collection Returns variable names corresponding to the given collection.
Definition: Manager.cc:173
bool addAlias(const std::string &alias, const std::string &variable)
Add alias Return true if the alias was successfully added.
Definition: Manager.cc:96
bool addCollection(const std::string &collection, const std::vector< std::string > &variables)
Add collection Return true if the collection was successfully added.
Definition: Manager.cc:152
std::function< VarVariant(const Particle *, const std::vector< double > &)> ParameterFunctionPtr
parameter functions stored take a const Particle*, const std::vector<double>& and return VarVariant.
Definition: Manager.h:114
VariableDataType
data type of variables
Definition: Manager.h:121
void registerVariable(const std::string &name, const Manager::FunctionPtr &f, const std::string &description, const Manager::VariableDataType &v, const std::string &unit="")
Register a variable.
Definition: Manager.cc:332
void checkDeprecatedVariable(const std::string &name)
Check if a variable is deprecated.
Definition: Manager.cc:444
double evaluate(const std::string &varName, const Particle *p)
Evaluate variable 'varName' on given Particle.
Definition: Manager.cc:477
std::vector< double > evaluateVariables(const std::vector< std::string > &varNames, const ParticleList *plist)
Evaluate each variable in the vector 'varNames' on given ParticleList and return a flattened vector o...
Definition: Manager.cc:493
std::vector< std::string > splitOnDelimiterAndConserveParenthesis(std::string str, char delimiter, char open, char close)
Split into std::vector on delimiter ignoring delimiters between parenthesis.
Definition: CutHelpers.cc:81
Abstract base class for different kinds of events.
Base class for information common to all types of variables.
Definition: Manager.h:128
A variable returning a floating-point value for a given Particle.
Definition: Manager.h:145