gambit is hosted by Hepforge, IPPP Durham
GAMBIT  v1.5.0-2191-ga4742ac
a Global And Modular Bsm Inference Tool
minuit2.cpp
Go to the documentation of this file.
1 // GAMBIT: Global and Modular BSM Inference Tool
2 // *********************************************
18 
19 #ifdef WITH_MPI
21 #include "mpi.h"
23 #endif
24 
25 #include <iostream>
26 #include <map>
27 #include <string>
28 #include <vector>
29 
35 
36 #include "Minuit2/Minuit2Minimizer.h"
37 #include "Math/Functor.h"
38 
39 
41 void check_node_keys(YAML::Node node, std::vector<std::string> keys)
42 {
43  if (node)
44  {
45  for (const auto &s : node)
46  {
47  const auto key = s.first.as<std::string>();
48  if (std::find(keys.begin(), keys.end(), key) == keys.end())
49  {
50  Gambit::Scanner::scan_error().raise(LOCAL_INFO, "Minuit2: unexpected key = " + key);
51  }
52  }
53  }
54 }
55 
57 double get_node_value(YAML::Node node, std::string key, double default_)
58 {
59  if (node && node[key])
60  {
61  return node[key].as<double>();
62  }
63  return default_;
64 }
65 
66 scanner_plugin(minuit2, version(6, 23, 01))
67 {
68  reqd_libraries("Minuit2", "Minuit2Math");
69  reqd_headers("Minuit2/Minuit2Minimizer.h", "Math/Functor.h");
70 
71  int plugin_main(void)
72  {
73  // retrieve the dimensionality of the scan
74  const int dim = get_dimension();
75 
76 #ifdef WITH_MPI
77  int size = 0;
78  MPI_Comm_size(MPI_COMM_WORLD, &size);
79  const int elements = dim * (dim - 1) / 2;
80  if (size > elements)
81  {
82  scan_error().raise(LOCAL_INFO, "Minuit2: require no. processes <= 1/2 dim (dim - 1)");
83  }
84 #endif
85 
86  // retrieve the model - contains loglike etc
87  Gambit::Scanner::like_ptr model = get_purpose(get_inifile_value<std::string>("like"));
88  const auto offset = get_inifile_value<double>("likelihood: lnlike_offset", 0.);
89  model->setPurposeOffset(offset);
90  const auto names = model.get_names();
91 
92  // minuit2 algorithm options
93  const auto algorithm{get_inifile_value<std::string>("algorithm", "combined")};
94  const auto max_loglike_calls{get_inifile_value<int>("max_loglike_calls", 100000)};
95  const auto max_iterations{get_inifile_value<int>("max_iterations", 100000)};
96  const auto tolerance{get_inifile_value<double>("tolerace", 0.0001)};
97  const auto precision{get_inifile_value<double>("precision", 0.0001)};
98  const auto print_level{get_inifile_value<int>("print_level", 1)};
99  const auto strategy{get_inifile_value<int>("strategy", 2)};
100 
101  // get starting point (optional). It can be written in hypercube or physical
102  // parameters. Default is center of hypercube for each parameter
103 
104  const auto hypercube_start_node = get_inifile_node("unit_hypercube_start");
105  const auto physical_start_node = get_inifile_node("start");
106 
107  if (hypercube_start_node && physical_start_node)
108  {
109  std::string msg = "Minuit2: start specified by unit hypercube or physical parameters";
110  scan_error().raise(LOCAL_INFO, msg);
111  }
112 
113  check_node_keys(physical_start_node ? physical_start_node : hypercube_start_node, names);
114 
115  const double default_hypercube_start = 0.5;
116  std::vector<double> hypercube_start(dim, default_hypercube_start);
117  std::unordered_map<std::string, double> physical_start_map;
118 
119  if (physical_start_node)
120  {
121  physical_start_map = model.transform(hypercube_start);
122  for (auto &s : physical_start_map)
123  {
124  s.second = get_node_value(physical_start_node, s.first, s.second);
125  }
126  hypercube_start = model.inverse_transform(physical_start_map);
127  }
128  else
129  {
130  for (int i = 0; i < dim; i++)
131  {
132  hypercube_start[i] = get_node_value(hypercube_start_node, names[i], hypercube_start[i]);
133  }
134  physical_start_map = model.transform(hypercube_start);
135  }
136 
137  // get hypercube step (optional). It can be written in hypercube or physical
138  // parameters. Default is same for each parameter
139 
140  const double default_hypercube_step = 0.01;
141  const auto hypercube_step_node = get_inifile_node("unit_hypercube_step");
142  const auto physical_step_node = get_inifile_node("step");
143 
144  if (hypercube_step_node && physical_step_node)
145  {
146  std::string msg = "Minuit2: step specified by unit hypercube or physical parameters";
147  scan_error().raise(LOCAL_INFO, msg);
148  }
149 
150  check_node_keys(physical_step_node ? physical_step_node : hypercube_step_node, names);
151 
152  std::vector<double> hypercube_step;
153 
154  if (physical_step_node)
155  {
156  const auto center = model.transform(hypercube_start);
157 
158  for (int i = 0; i < dim; i++)
159  {
160  if (!physical_step_node[names[i]])
161  {
162  hypercube_step.push_back(default_hypercube_step);
163  }
164  else
165  {
166  double physical_step = physical_step_node[names[i]].as<double>();
167  auto forward = center;
168  forward.at(names[i]) += physical_step;
169  auto backward = center;
170  backward.at(names[i]) -= physical_step;
171 
172  const auto hypercube_forward = model.inverse_transform(forward);
173  const auto hypercube_backward = model.inverse_transform(backward);
174  const double mean_step = 0.5 * (hypercube_forward[i] - hypercube_backward[i]);
175  hypercube_step.push_back(mean_step);
176  }
177  }
178  }
179  else
180  {
181  for (const auto& n : names)
182  {
183  hypercube_step.push_back(get_node_value(hypercube_step_node, n, default_hypercube_step));
184  }
185  }
186 
187  // select algorithm
188 
189  ROOT::Minuit2::EMinimizerType kalgorithm;
190  if (algorithm == "simplex")
191  {
192  kalgorithm = ROOT::Minuit2::kSimplex;
193  }
194  else if (algorithm == "combined")
195  {
196  kalgorithm = ROOT::Minuit2::kCombined;
197  }
198  else if (algorithm == "scan")
199  {
200  kalgorithm = ROOT::Minuit2::kScan;
201  }
202  else if (algorithm == "fumili")
203  {
204  kalgorithm = ROOT::Minuit2::kFumili;
205  }
206  else if (algorithm == "bfgs")
207  {
208  kalgorithm = ROOT::Minuit2::kMigradBFGS;
209  }
210  else if (algorithm == "migrad")
211  {
212  kalgorithm = ROOT::Minuit2::kMigrad;
213  }
214  else
215  {
216  std::string msg = "Minuit2: Unknown algorithm: " + algorithm;
217  scan_error().raise(LOCAL_INFO, msg);
218  }
219 
220  ROOT::Math::Minimizer* min = new ROOT::Minuit2::Minuit2Minimizer(kalgorithm);
221  min->SetStrategy(strategy);
222  min->SetMaxFunctionCalls(max_loglike_calls);
223  min->SetMaxIterations(max_iterations);
224  min->SetTolerance(tolerance);
225  min->SetPrintLevel(print_level);
226  min->SetPrecision(precision);
227 
228  auto chi_squared = [&model, dim] (const double* x)
229  {
230  std::vector<double> v;
231  for (int i = 0; i < dim; i++)
232  {
233  v.push_back(x[i]);
234  }
235  return -2. * model(v);
236  };
237 
238  ROOT::Math::Functor f(chi_squared, dim);
239  min->SetFunction(f);
240 
241  // set the free variables to be minimized
242 
243  for (int i = 0; i < dim; i++)
244  {
245  const bool added = min->SetLimitedVariable(i, names[i], hypercube_start[i], hypercube_step[i], 0., 1.);
246  if (added)
247  {
248  std::cout << names[i] << ". hypercube = " << hypercube_start[i]
249  << " +/- " << hypercube_step[i]
250  << ". physical = " << physical_start_map.at(names[i]) << std::endl;
251  }
252  else
253  {
254  scan_error().raise(LOCAL_INFO, "could not add parameter");
255  }
256  }
257 
258  // do the minimization
259  min->Minimize();
260 
261  std::cout << "minimum chi-squared = " << min->MinValue() << std::endl;
262 
263  const double *best_fit_hypercube = min->X();
264  for (int i = 0; i < dim; i++)
265  {
266  std::cout << "best-fit hypercube " << i << " = "
267  << best_fit_hypercube[i] << std::endl;
268  }
269 
270  // convert result to physical parameters
271  std::vector<double> v;
272  for (int i = 0; i < dim; i++)
273  {
274  v.push_back(best_fit_hypercube[i]);
275  }
276  auto best_fit_physical = model.transform(v);
277  std::cout << "best-fit physical = " << best_fit_physical << std::endl;
278 
279  // whether successful
280  const int status = min->Status();
281  switch (status) {
282  case 0:
283  break;
284  case 1:
285  scan_error().raise(LOCAL_INFO, "Minuit2: Covar was made pos def");
286  break;
287  case 2:
288  scan_error().raise(LOCAL_INFO, "Minuit2: Hesse is not valid");
289  break;
290  case 3:
291  scan_error().raise(LOCAL_INFO, "Minuit2: Edm is above max");
292  break;
293  case 4:
294  scan_error().raise(LOCAL_INFO, "Minuit2: Reached call limit");
295  break;
296  case 5:
297  scan_error().raise(LOCAL_INFO, "Minuit2: Covar is not pos def");
298  break;
299  default:
300  scan_error().raise(LOCAL_INFO, "Minuit2: Unknown error: " + std::to_string(status));
301  }
302 
303  // manually delete the pointer
304  delete min;
305 
306  // success if reached end
307  return 0;
308  }
309 }
#define LOCAL_INFO
Definition: local_info.hpp:34
std::vector< std::string > get_names() const
Declarations for the YAML options class.
General small utility functions.
ScannerBit interface to Minuit2.
std::unordered_map< std::string, double > transform(const std::vector< double > &vec)
#define plugin_main(...)
Declaration of the main function which will be ran by the interface.
scanner_plugin(minuit2, version(6, 23, 01))
Definition: minuit2.cpp:66
Utility Functions for the Gambit Scanner.
Simple header file for turning compiler warnings back on after having included one of the begin_ignor...
Pragma directives to suppress compiler warnings coming from including MPI library headers...
void check_node_keys(YAML::Node node, std::vector< std::string > keys)
Check that a yaml node does not contain unexpected keys.
Definition: minuit2.cpp:41
EXPORT_SYMBOLS error & scan_error()
Scanner errors.
#define reqd_libraries(...)
Tells ScannerBit that these libraries are requested.
double get_node_value(YAML::Node node, std::string key, double default_)
Get a particular key from a node.
Definition: minuit2.cpp:57
std::vector< double > inverse_transform(const std::unordered_map< std::string, double > &physical)
#define reqd_headers(...)
Tells ScannerBit that these include files must exist.
declaration for scanner module
likelihood container for scanner plugins.