MoMEMta.cc
1 
2 /*
3  * MoMEMta: a modular implementation of the Matrix Element Method
4  * Copyright (C) 2016 Universite catholique de Louvain (UCL), Belgium
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <momemta/MoMEMta.h>
21 
22 #include <cstring>
23 #include <cmath>
24 #include <cstdint>
25 
26 #include <cuba.h>
27 
28 #include <momemta/Configuration.h>
29 #include <momemta/Logging.h>
30 #include <momemta/ParameterSet.h>
31 #include <momemta/Utils.h>
32 #include <momemta/Unused.h>
33 
34 #include <Graph.h>
35 #include <ModuleUtils.h>
36 #include <lua/utils.h>
37 #include <Path.h>
38 
39 #define CUBA_ABORT -999
40 #define CUBA_OK 0
41 
47 void validateModules(const std::vector<Configuration::ModuleDecl>& module_decls,
48  const momemta::ModuleList& available_modules) {
49 
50  bool all_parameters_valid = true;
51 
52  for (const auto& decl: module_decls) {
53  // Find module inside the registry
54  auto it = std::find_if(available_modules.begin(), available_modules.end(),
55  [&decl](const momemta::ModuleList::value_type& available_module) {
56  // The *name* of the module inside the registry is what we call the
57  // *type* in userland.
58  return available_module.name == decl.type;
59  });
60 
61  if (it == available_modules.end())
62  throw std::runtime_error("A module was declared with a type unknown to the registry. This is not supposed to "
63  "be possible");
64 
65  const auto& def = *it;
66 
67  // Ignore internal modules
68  if (def.internal)
69  continue;
70 
71  all_parameters_valid &= momemta::validateModuleParameters(def, *decl.parameters);
72  }
73 
74  if (! all_parameters_valid) {
75  // At least one set of parameters is invalid. Stop here
76  auto exception = lua::invalid_configuration_file("Validation of modules' parameters failed. "
77  "Check the log output for more details on how to fix your configuration file.");
78 
79  LOG(fatal) << exception.what();
80 
81  throw exception;
82  }
83 }
84 
85 MoMEMta::MoMEMta(const Configuration& configuration_) {
86 
87  Configuration configuration = configuration_;
88 
89  // List of all available type of modules with their definition
90  momemta::ModuleList available_modules;
91  momemta::ModuleRegistry::get().exportList(false, available_modules);
92 
93  // List of module instances defined by the user, with their parameters
94  std::vector<Configuration::ModuleDecl> module_instances_def = configuration.getModules();
95 
96  validateModules(
97  module_instances_def,
98  available_modules
99  );
100 
101  // Check if the user defined which integrand to use
102  std::vector<InputTag> integrands = configuration.getIntegrands();
103  if (!integrands.size()) {
104  LOG(fatal)
105  << "No integrand found. Define which module's output you want to use as the integrand using the lua `integrand` function.";
106  throw integrands_output_error("No integrand found");
107  }
108 
109  std::string export_graph_as;
110  if (configuration.getGlobalParameters().existsAs<std::string>("export_graph_as"))
111  export_graph_as = configuration.getGlobalParameters().get<std::string>("export_graph_as");
112 
113  // All modules are correctly declared. Create the computation graph.
114  momemta::ComputationGraphBuilder builder(available_modules, configuration);
115  m_computation_graph = builder.build();
116 
117  if (! export_graph_as.empty())
118  builder.exportGraph(export_graph_as);
119 
120  // Initialize shared memory pool for modules
121  initPool(configuration);
122 
123  // And initialize the computation graph
124  m_computation_graph->initialize(m_pool);
125 
126  for (const auto& component: integrands) {
127  m_integrands.push_back(m_pool->get<double>(component));
128  LOG(debug) << "Configuration declared integrand component using: " << component.toString();
129  }
130  m_n_components = m_integrands.size();
131 
132  m_n_dimensions = m_computation_graph->getNDimensions();
133  LOG(info) << "Number of expected inputs: " << m_inputs_p4.size();
134  LOG(info) << "Number of dimensions for integration: " << m_n_dimensions;
135 
136  // Resize pool ps-points vector
137  m_ps_points->resize(m_n_dimensions);
138 
139  m_cuba_configuration = configuration.getCubaConfiguration();
140 
141  // Freeze the pool after removing unneeded modules
142  m_pool->freeze();
143 
144  m_computation_graph->configure();
145 
146  // Register logging function
147  cubalogging(MoMEMta::cuba_logging);
148 }
149 
151  m_computation_graph->finish();
152 }
153 
154 const Pool& MoMEMta::getPool() const {
155  return *m_pool;
156 }
157 
158 
159 void MoMEMta::setEvent(const std::vector<momemta::Particle>& particles, const LorentzVector& met) {
160  if (particles.size() != m_inputs_p4.size()) {
161  auto exception = invalid_inputs("Some inputs are missing. " + std::to_string(m_inputs_p4.size()) + " expected, "
162  + std::to_string(particles.size()) + " provided.");
163  LOG(fatal) << exception.what();
164  throw exception;
165  }
166 
167  std::vector<std::string> consumed_inputs;
168  for (const auto& p: particles) {
169  checkIfPhysical(p.p4);
170 
171  if (m_inputs_p4.count(p.name) == 0) {
172  auto exception = invalid_inputs(p.name + " is not a declared input");
173  LOG(fatal) << exception.what();
174  throw exception;
175  }
176 
177  if (std::find(consumed_inputs.begin(), consumed_inputs.end(), p.name) != consumed_inputs.end()) {
178  auto exception = invalid_inputs("Duplicated input " + p.name);
179  LOG(fatal) << exception.what();
180  throw exception;
181  }
182 
183  *m_inputs_p4[p.name] = p.p4;
184  *m_inputs_type[p.name] = p.type;
185 
186  consumed_inputs.push_back(p.name);
187  }
188 
189  *m_met = met;
190 }
191 
192 std::vector<std::pair<double, double>> MoMEMta::computeWeights(const std::vector<momemta::Particle>& particles, const LorentzVector& met) {
193  setEvent(particles, met);
194 
195  m_computation_graph->beginIntegration();
196 
197  std::unique_ptr<double[]> mcResult(new double[m_n_components]);
198  std::unique_ptr<double[]> error(new double[m_n_components]);
199 
200  for (size_t i = 0; i < m_n_components; i++) {
201  mcResult[i] = 0;
202  error[i] = 0;
203  }
204 
205  if (m_n_dimensions > 0) {
206 
207  // Read cuba configuration
208 
209  std::string algorithm = m_cuba_configuration.get<std::string>("algorithm", "vegas");
210 
211  // Common arguments
212  double relative_accuracy = m_cuba_configuration.get<double>("relative_accuracy", 0.005);
213  double absolute_accuracy = m_cuba_configuration.get<double>("absolute_accuracy", 0.);
214  int64_t seed = m_cuba_configuration.get<int64_t>("seed", 0);
215  int64_t min_eval = m_cuba_configuration.get<int64_t>("min_eval", 0);
216  int64_t max_eval = m_cuba_configuration.get<int64_t>("max_eval", 500000);
217  std::string grid_file = m_cuba_configuration.get<std::string>("grid_file", "");
218 
219  // Common arguments entering the flags bitset
220  uint8_t verbosity = m_cuba_configuration.get<int64_t>("verbosity", 0);
221  bool subregion = m_cuba_configuration.get<bool>("subregion", false);
222  bool retainStateFile = m_cuba_configuration.get<bool>("retainStateFile", false);
223  uint64_t level = m_cuba_configuration.get<int64_t>("level", 0);
224  // Only used by vegas!
225  bool takeOnlyGridFromFile = m_cuba_configuration.get<bool>("takeOnlyGridFromFile", true);
226  // Only used by vegas and suave!
227  bool smoothing = m_cuba_configuration.get<bool>("smoothing", true);
228 
229  unsigned int flags = cuba::createFlagsBitset(verbosity, subregion, retainStateFile, level, smoothing, takeOnlyGridFromFile);
230 
231  int64_t ncores = m_cuba_configuration.get<int64_t>("ncores", 0);
232  int64_t pcores = m_cuba_configuration.get<int64_t>("pcores", 1000000);
233  cubacores(ncores, pcores);
234 
235  // Output from cuba
236  long long int neval = 0;
237  int nfail = 0;
238  std::unique_ptr<double[]> prob(new double[m_n_components]);
239 
240  for (size_t i = 0; i < m_n_components; i++) {
241  prob[i] = 0;
242  }
243 
244  if (algorithm == "vegas") {
245  int64_t n_start = m_cuba_configuration.get<int64_t>("n_start", 25000);
246  int64_t n_increase = m_cuba_configuration.get<int64_t>("n_increase", 0);
247  int64_t batch_size = m_cuba_configuration.get<int64_t>("batch_size", std::min(n_start, INT64_C(50000)));
248  int64_t grid_number = m_cuba_configuration.get<int64_t>("grid_number", 0);
249 
250  llVegas(
251  m_n_dimensions, // (int) dimensions of the integrated volume
252  m_n_components, // (int) dimensions of the integrand
253  reinterpret_cast<integrand_t>(CUBAIntegrandWeighted), // (integrand_t) integrand (cast to integrand_t)
254  (void *) this, // (void*) pointer to additional arguments passed to integrand
255  1, // (int) maximum number of points given the integrand in each invocation (=> SIMD) ==> PS points = vector of sets of points (x[ndim][nvec]), integrand returns vector of vector values (f[ncomp][nvec])
256  relative_accuracy, // (double) requested relative accuracy /
257  absolute_accuracy, // (double) requested absolute accuracy /-> error < max(rel*value,abs)
258  flags, // (int) various control flags in binary format, see setFlags function
259  seed, // (int) seed (seed==0 => SOBOL; seed!=0 && control flag "level"==0 => Mersenne Twister)
260  min_eval, // (int) minimum number of integrand evaluations
261  max_eval, // (int) maximum number of integrand evaluations (approx.!)
262  n_start, // (int) number of integrand evaluations per interations (to start)
263  n_increase, // (int) increase in number of integrand evaluations per interations
264  batch_size, // (int) batch size for sampling
265  grid_number, // (int) grid number, 1-10 => up to 10 grids can be stored, and re-used for other integrands (provided they are not too different)
266  grid_file.c_str(), // (char*) name of state file => state can be stored and retrieved for further refinement
267  nullptr, // (void*) "spinning cores": -1 || null <=> integrator takes care of starting & stopping child processes (other value => keep or retrieve child processes, memory NOT FREED!!)
268  &neval, // (int*) actual number of evaluations done
269  &nfail, // 0=desired accuracy was reached; -1=dimensions out of range; >0=accuracy was not reached
270  mcResult.get(), // (double*) integration result ([ncomp])
271  error.get(), // (double*) integration error ([ncomp])
272  prob.get() // (double*) Chi-square p-value that error is not reliable (ie should be <0.95) ([ncomp])
273  );
274  } else if (algorithm == "suave") {
275  int64_t n_new = m_cuba_configuration.get<int64_t>("n_new", 1000);
276  int64_t n_min = m_cuba_configuration.get<int64_t>("n_min", 2);
277  double flatness = m_cuba_configuration.get<double>("flatness", 0.25);
278 
279  int nregions = 0;
280 
281  llSuave(
282  m_n_dimensions,
283  m_n_components,
284  reinterpret_cast<integrand_t>(CUBAIntegrandWeighted),
285  (void *) this,
286  1,
287  relative_accuracy,
288  absolute_accuracy,
289  flags,
290  seed,
291  min_eval,
292  max_eval,
293  n_new,
294  n_min,
295  flatness,
296  grid_file.c_str(),
297  nullptr,
298  &nregions,
299  &neval,
300  &nfail,
301  mcResult.get(),
302  error.get(),
303  prob.get()
304  );
305  } else if (algorithm == "divonne") {
306  int64_t key1 = m_cuba_configuration.get<int64_t>("key1", 47);
307  int64_t key2 = m_cuba_configuration.get<int64_t>("key2", 1);
308  int64_t key3 = m_cuba_configuration.get<int64_t>("key3", 1);
309  int64_t maxpass = m_cuba_configuration.get<int64_t>("maxpass", 5);
310  double border = m_cuba_configuration.get<double>("border", 0);
311  double maxchisq = m_cuba_configuration.get<double>("maxchisq", 10.0);
312  double mindeviation = m_cuba_configuration.get<double>("mindeviation", 0.25);
313 
314  int nregions = 0;
315 
316  llDivonne(
317  m_n_dimensions,
318  m_n_components,
319  reinterpret_cast<integrand_t>(CUBAIntegrand),
320  (void *) this,
321  1,
322  relative_accuracy,
323  absolute_accuracy,
324  flags,
325  seed,
326  min_eval,
327  max_eval,
328  key1, key2, key3,
329  maxpass,
330  border,
331  maxchisq,
332  mindeviation,
333  0,
334  0,
335  nullptr,
336  0,
337  nullptr,
338  grid_file.c_str(),
339  nullptr,
340  &nregions,
341  &neval,
342  &nfail,
343  mcResult.get(),
344  error.get(),
345  prob.get()
346  );
347  } else if (algorithm == "cuhre") {
348  int64_t key = m_cuba_configuration.get<int64_t>("key", 0);
349 
350  int nregions = 0;
351 
352  llCuhre(
353  m_n_dimensions,
354  m_n_components,
355  reinterpret_cast<integrand_t>(CUBAIntegrand),
356  (void *) this,
357  1,
358  relative_accuracy,
359  absolute_accuracy,
360  flags,
361  min_eval,
362  max_eval,
363  key,
364  grid_file.c_str(),
365  nullptr,
366  &nregions,
367  &neval,
368  &nfail,
369  mcResult.get(),
370  error.get(),
371  prob.get()
372  );
373  } else {
374  throw cuba_configuration_error("Integration algorithm " + algorithm + " is not supported");
375  }
376 
377  if (nfail == 0) {
378  integration_status = IntegrationStatus::SUCCESS;
379  } else if (nfail == -1) {
380  integration_status = IntegrationStatus::DIM_OUT_OF_RANGE;
381  } else if (nfail > 0) {
382  integration_status = IntegrationStatus::ACCURACY_NOT_REACHED;
383  } else if (nfail == -99) {
384  integration_status = IntegrationStatus::ABORTED;
385  }
386  } else {
387 
388  LOG(debug) << "No integration dimension requested, bypassing integration.";
389 
390  // Directly call integrand
391  int status = integrand(nullptr, mcResult.get(), nullptr);
392 
393  if (status == CUBA_OK) {
394  integration_status = IntegrationStatus::SUCCESS;
395  } else {
396  integration_status = IntegrationStatus::ABORTED;
397  }
398  }
399 
400 #ifdef DEBUG_TIMING
401  m_computation_graph->logTimings();
402 #endif
403 
404  m_computation_graph->endIntegration();
405 
406  std::vector<std::pair<double, double>> result;
407  for (size_t i = 0; i < m_n_components; i++) {
408  result.push_back( std::make_pair(mcResult[i], error[i]) );
409  }
410 
411  return result;
412 }
413 
414 std::vector<double> MoMEMta::evaluateIntegrand(const std::vector<double>& psPoints) {
415 
416  if (psPoints.size() != m_n_dimensions) {
417  throw invalid_inputs("Dimensionality of the phase-space point is incorrect.");
418  }
419 
420  std::vector<double> results(m_n_components);
421 
422  integrand(static_cast<const double*>(&psPoints[0]), &results[0]);
423 
424  return results;
425 }
426 
427 int MoMEMta::integrand(const double* psPoints, double* results, const double* weights) {
428 
429  // Store phase-space points into the pool
430  std::memcpy(m_ps_points->data(), psPoints, sizeof(double) * m_n_dimensions);
431 
432  if (weights != nullptr) {
433  // Store phase-space weight into the pool
434  *m_ps_weight = *weights;
435  }
436 
437  auto status = m_computation_graph->execute();
438 
439  int return_value = CUBA_OK;
440  if (status != Module::Status::OK) {
441  for (size_t i = 0; i < m_n_components; i++)
442  results[i] = 0;
443 
444  if (status == Module::Status::ABORT)
445  return_value = CUBA_ABORT;
446  } else {
447  for (size_t i = 0; i < m_n_components; i++) {
448  results[i] = *(m_integrands[i]);
449  if (!std::isfinite(results[i]))
450  throw integrands_nonfinite_error("Integrand component " + std::to_string(i) + " is infinite or NaN!");
451  }
452  }
453 
454  return return_value;
455 }
456 
457 int MoMEMta::CUBAIntegrand(const int *nDim, const double* psPoint, const int *nComp, double *value, void *inputs, const int *nVec, const int *core) {
458  UNUSED(nDim);
459  UNUSED(nComp);
460  UNUSED(nVec);
461  UNUSED(core);
462 
463  return static_cast<MoMEMta*>(inputs)->integrand(psPoint, value);
464 }
465 
466 int MoMEMta::CUBAIntegrandWeighted(const int *nDim, const double* psPoint, const int *nComp, double *value, void *inputs, const int *nVec, const int *core, const double *weight) {
467  UNUSED(nDim);
468  UNUSED(nComp);
469  UNUSED(nVec);
470  UNUSED(core);
471 
472  return static_cast<MoMEMta*>(inputs)->integrand(psPoint, value, weight);
473 }
474 
475 void MoMEMta::cuba_logging(const char* s) {
476  std::stringstream ss(s);
477  std::string line;
478 
479  while(std::getline(ss, line, '\n')) {
480  if (line.length() > 0)
481  LOG(debug) << line;
482  }
483 }
484 
486  return integration_status;
487 }
488 
489 void MoMEMta::checkIfPhysical(const LorentzVector& p4) {
490  // Use M2() to prevent computation of the square root
491  if ((p4.M2() < 0) || (p4.E() < 0)) {
492  auto exception = unphysical_lorentzvector_error(p4);
493  LOG(fatal) << exception.what();
494  throw exception;
495  }
496 }
497 
498 void MoMEMta::initPool(const Configuration& configuration) {
499 
500  m_pool.reset(new Pool());
501 
502  // Create phase-space points vector, input for many modules
503  m_ps_points = m_pool->put<std::vector<double>>({"cuba", "ps_points"});
504  m_ps_weight = m_pool->put<double>({"cuba", "ps_weight"});
505 
506  // For each input declared in the configuration, create pool entries for p4 and type
507  auto inputs = configuration.getInputs();
508  for (const auto& input: inputs) {
509  LOG(debug) << "Input declared: " << input;
510  m_inputs_p4.emplace(input, m_pool->put<LorentzVector>({input, "p4"}));
511  m_inputs_type.emplace(input, m_pool->put<int64_t>({input, "type"}));
512  }
513 
514  // Create input for met
515  m_met = m_pool->put<LorentzVector>({"met", "p4"});
516 
517 }
518 
519 MoMEMta::unphysical_lorentzvector_error::unphysical_lorentzvector_error(const LorentzVector& p4) {
520  std::stringstream msg;
521 
522  msg << "Unphysical lorentz vector: " << p4;
523  msg << ". Please ensure that the energy and the mass are positive or null.";
524 
525  _what = msg.str();
526 }
527 
528 const char* MoMEMta::unphysical_lorentzvector_error::what() const noexcept {
529  return _what.c_str();
530 }
const ParameterSet & getGlobalParameters() const
A MoMEMta instance.
Definition: MoMEMta.h:44
const Pool & getPool() const
Read-only access to the global memory pool.
Definition: MoMEMta.cc:154
IntegrationStatus getIntegrationStatus() const
Return the status of the integration.
Definition: MoMEMta.cc:485
std::vector< double > evaluateIntegrand(const std::vector< double > &psPoints)
Evaluate the integrand on a single phase-space point.
Definition: MoMEMta.cc:414
static ModuleRegistry & get()
A singleton available at startup.
virtual ~MoMEMta()
Destructor.
Definition: MoMEMta.cc:150
MoMEMta(const Configuration &configuration)
Create a new MoMEMta instance.
Definition: MoMEMta.cc:85
const ParameterSet & getCubaConfiguration() const
std::vector< InputTag > getIntegrands() const
std::vector< std::string > getInputs() const
Definition: Pool.h:40
Integration was successful.
< Thrown if the configuration file is not valid
Definition: utils.h:24
A frozen snapshot of the configuration file.
Definition: Configuration.h:36
void setEvent(const std::vector< momemta::Particle > &particles, const LorentzVector &met=LorentzVector())
Set the event particles&#39; momenta.
Definition: MoMEMta.cc:159
void exportList(bool ignore_internal, ModuleList &list) const
Integration was stopped before desired accuracy was reached.
std::vector< std::pair< double, double > > computeWeights(const std::vector< momemta::Particle > &particles, const LorentzVector &met=LorentzVector())
Compute the weights in the current configuration.
Definition: MoMEMta.cc:192
const std::vector< ModuleDecl > & getModules() const
IntegrationStatus
Status of the integration.
Definition: MoMEMta.h:47