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