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