ComPWA
Common Partial-Wave-Analysis Framework
MinuitIF.cpp
Go to the documentation of this file.
1 // Copyright (c) 2013, 2017 The ComPWA Team.
2 // This file is part of the ComPWA framework, check
3 // https://github.com/ComPWA/ComPWA/license.txt for details.
4 
5 #include "MinuitIF.hpp"
6 #include "MinuitFcn.hpp"
7 
8 #include "Core/Exceptions.hpp"
9 #include "Core/FitParameter.hpp"
10 #include "Core/Logging.hpp"
11 #include "Core/Utils.hpp"
12 
13 #include "Minuit2/FunctionMinimum.h"
14 #include "Minuit2/MinosError.h"
15 #include "Minuit2/MnHesse.h"
16 #include "Minuit2/MnMigrad.h"
17 #include "Minuit2/MnMinos.h"
18 #include "Minuit2/MnPrint.h"
19 #include "Minuit2/MnStrategy.h"
20 #include "Minuit2/MnUserParameters.h"
21 
22 #include <chrono>
23 #include <iomanip>
24 #include <string>
25 #include <vector>
26 
27 namespace ComPWA {
28 namespace Optimizer {
29 namespace Minuit2 {
30 
31 using namespace ROOT::Minuit2;
32 
34 getFinalParameters(const ROOT::Minuit2::MnUserParameterState &minState,
35  FitParameterList InitialParameters) {
36  FitParameterList FinalParameters(InitialParameters);
37 
38  for (auto &FinalPar : FinalParameters) {
39  if (FinalPar.IsFixed)
40  continue;
41 
42  // central value
43  double val = minState.Value(FinalPar.Name);
44  double err = minState.Error(FinalPar.Name);
45 
46  // shift to [-pi;pi] if parameter is a phase
47  if (FinalPar.Name.find("phase") != FinalPar.Name.npos)
48  val = ComPWA::Utils::shiftAngle(val);
49  FinalPar.Value = val;
50  FinalPar.Error = std::make_pair(err, err);
51  }
52  return FinalParameters;
53 }
54 
55 std::vector<std::vector<double>>
56 getCovarianceMatrix(const ROOT::Minuit2::MnUserParameterState &minState) {
57  std::vector<std::vector<double>> CovarianceMatrix;
58 
59  if (minState.HasCovariance()) {
60  auto NumFreeParameter = minState.Parameters().Trafo().VariableParameters();
61  ROOT::Minuit2::MnUserCovariance minuitCovMatrix = minState.Covariance();
62  // Size of Minuit covariance vector is given by dim*(dim+1)/2.
63  // dim is the dimension of the covariance matrix.
64  // The dimension can therefore be calculated as
65  // dim = -0.5+-0.5 sqrt(8*size+1)
66  assert(minuitCovMatrix.Nrow() == NumFreeParameter);
67  CovarianceMatrix = std::vector<std::vector<double>>(
68  NumFreeParameter, std::vector<double>(NumFreeParameter));
69  for (unsigned i = 0; i < NumFreeParameter; ++i)
70  for (unsigned j = i; j < NumFreeParameter; ++j) {
71  CovarianceMatrix.at(i).at(j) = minuitCovMatrix(j, i);
72  CovarianceMatrix.at(j).at(i) = minuitCovMatrix(j, i); // fill lower half
73  }
74 
75  } else {
76  LOG(ERROR)
77  << "MinuitIF::createResult(): no valid covariance matrix available!";
78  }
79  return CovarianceMatrix;
80 }
81 
82 void MinuitIF::setStrategy(std::string strategy) {
83  MnStrategy strat; // using default strategy = 1 (medium)
84  if (strategy == "low")
85  strat.SetLowStrategy();
86  else if (strategy == "medium")
87  strat.SetMediumStrategy();
88  else if (strategy == "high")
89  strat.SetHighStrategy();
90  else
91  LOG(INFO) << "MinuitIF::setStrategy() | Minuit strategy must be "
92  "set to 'low', 'medium' or 'high'";
93 
94  GradientNCycles = strat.GradientNCycles();
95  GradientStepTolerance = strat.GradientStepTolerance();
96  GradientTolerance = strat.GradientTolerance();
97  HessianNCycles = strat.HessianNCycles();
98  HessianGradientNCycles = strat.HessianGradientNCycles();
99  HessianStepTolerance = strat.HessianStepTolerance();
100  HessianG2Tolerance = strat.HessianG2Tolerance();
101 }
102 
103 std::string MinuitIF::checkStrategy() {
104  MnStrategy strat;
105 
106  strat.SetLowStrategy();
107  if (strat.GradientNCycles() == GradientNCycles &&
108  strat.GradientStepTolerance() == GradientStepTolerance &&
109  strat.GradientTolerance() == GradientTolerance &&
110  strat.HessianNCycles() == HessianNCycles &&
111  strat.HessianGradientNCycles() == HessianGradientNCycles &&
112  strat.HessianStepTolerance() == HessianStepTolerance &&
113  strat.HessianG2Tolerance() == HessianG2Tolerance)
114  return "low";
115 
116  strat.SetMediumStrategy();
117  if (strat.GradientNCycles() == GradientNCycles &&
118  strat.GradientStepTolerance() == GradientStepTolerance &&
119  strat.GradientTolerance() == GradientTolerance &&
120  strat.HessianNCycles() == HessianNCycles &&
121  strat.HessianGradientNCycles() == HessianGradientNCycles &&
122  strat.HessianStepTolerance() == HessianStepTolerance &&
123  strat.HessianG2Tolerance() == HessianG2Tolerance)
124  return "medium";
125 
126  strat.SetHighStrategy();
127  if (strat.GradientNCycles() == GradientNCycles &&
128  strat.GradientStepTolerance() == GradientStepTolerance &&
129  strat.GradientTolerance() == GradientTolerance &&
130  strat.HessianNCycles() == HessianNCycles &&
131  strat.HessianGradientNCycles() == HessianGradientNCycles &&
132  strat.HessianStepTolerance() == HessianStepTolerance &&
133  strat.HessianG2Tolerance() == HessianG2Tolerance)
134  return "high";
135 
136  return "custom";
137 }
138 
140  ComPWA::FitParameterList InitialParameters) {
141  LOG(DEBUG) << "MinuitIF::optimize() | Start";
142  std::chrono::steady_clock::time_point StartTime =
143  std::chrono::steady_clock::now();
144 
145  double InitialEstimatorValue(Estimator.evaluate());
146 
147  MnUserParameters upar;
148  unsigned int FreeParameters = 0;
149  for (auto Param : InitialParameters) {
150  if (Param.Name == "")
151  throw BadParameter("MinuitIF::optimize() | FitParameter without name in "
152  "list. Since FitParameter names are unique we stop "
153  "here.");
154  // If no error is set or error set to 0 we use a default error,
155  // otherwise minuit treats this parameter as fixed
156  double Error(0.5 * (Param.Error.first + Param.Error.second));
157  if (Error == 0.0) {
158  if (Param.Value != 0.0)
159  Error = Param.Value * 0.1;
160  else
161  Error = 0.01;
162  }
163 
164  if (Param.HasBounds) {
165  bool rt = upar.Add(Param.Name, Param.Value, Error, Param.Bounds.first,
166  Param.Bounds.second);
167  if (!rt)
168  throw BadParameter(
169  "MinuitIF::optimize() | FitParameter " + Param.Name +
170  " can not be added to Minuit2 (internal) parameters.");
171  } else {
172  bool rt = upar.Add(Param.Name, Param.Value, Error);
173  if (!rt)
174  throw BadParameter(
175  "MinuitIF::optimize() | FitParameter " + Param.Name +
176  " can not be added to Minuit2 (internal) parameters.");
177  }
178  if (!Param.IsFixed)
179  FreeParameters++;
180  if (Param.IsFixed)
181  upar.Fix(Param.Name);
182  }
183 
184  ROOT::Minuit2::MinuitFcn Function(Estimator);
185 
186  LOG(INFO) << "MinuitIF::optimize() | Number of parameters (free): "
187  << InitialParameters.size() << " (" << FreeParameters << ")";
188 
189  // Configure minimization strategy of Minuit2
190  MnStrategy strat; // default strategy = 1 (medium)
191 
192  strat.SetGradientNCycles(GradientNCycles);
193  strat.SetGradientStepTolerance(GradientStepTolerance);
194  strat.SetGradientTolerance(GradientTolerance);
195  strat.SetHessianNCycles(HessianNCycles);
196  strat.SetHessianGradientNCycles(HessianGradientNCycles);
197  strat.SetHessianStepTolerance(HessianStepTolerance);
198  strat.SetHessianG2Tolerance(HessianG2Tolerance);
199 
200  LOG(INFO) << "Minuit2 strategy: " << checkStrategy()
201  << "\n GradientNCycles: " << GradientNCycles
202  << "\n GradientStepTolerance: " << GradientStepTolerance
203  << "\n GradientTolerance: " << GradientTolerance
204  << "\n HessianNCycles: " << HessianNCycles
205  << "\n HessianGradientNCycles: " << HessianGradientNCycles
206  << "\n HessianStepTolerance: " << HessianStepTolerance
207  << "\n HessianG2Tolerance: " << HessianG2Tolerance;
208 
209  // MIGRAD
210  MnMigrad migrad(Function, upar, strat);
211  double maxfcn = 0.0;
212  double tolerance = 0.1;
213 
214  // From the MINUIT2 Documentation:
215  // Minimize the function MnMigrad()(maxfcn, tolerance)
216  // \param maxfcn : max number of function calls (if = 0) default is
217  // used which is set to 200 + 100 * npar + 5 * npar**2
218  // \param tolerance : value used for terminating iteration procedure.
219  // For example, MIGRAD will stop iterating when edm (expected
220  // distance from minimum) will be: edm < tolerance * 10**-3
221  // Default value of tolerance used is 0.1
222  LOG(INFO) << "MinuitIF::optimize() | Starting migrad: "
223  "maxCalls="
224  << maxfcn << " tolerance=" << tolerance;
225 
226  FunctionMinimum minMin = migrad(maxfcn, tolerance); //(maxfcn,tolerance)
227 
228  LOG(INFO) << "MinuitIF::optimize() | Migrad finished! "
229  "Minimum is valid = "
230  << minMin.IsValid();
231 
232  // HESSE
233  MnHesse hesse(strat);
234  if (minMin.IsValid() && UseHesse) {
235  LOG(INFO) << "MinuitIF::optimize() | Starting hesse";
236  hesse(Function, minMin); // function minimum minMin is updated by hesse
237  LOG(INFO) << "MinuitIF::optimize() | Hesse finished";
238  } else
239  LOG(INFO) << "MinuitIF::optimize() | Migrad failed to "
240  "find minimum! Skip hesse and minos!";
241 
242  LOG(INFO) << "MinuitIF::optimize() | Minimization finished! "
243  "LH = "
244  << std::setprecision(10) << minMin.Fval();
245 
246  std::chrono::steady_clock::time_point EndTime =
247  std::chrono::steady_clock::now();
248 
249  // Create fit result, before changes to parameters in
250  // FunctionMinimum might occur
251  auto MinState = minMin.UserState();
252  FitResult BaseResult{
253  InitialParameters,
254  getFinalParameters(MinState, InitialParameters),
255  FreeParameters,
256  minMin.IsValid(),
257  InitialEstimatorValue,
258  MinState.Fval(),
259  std::chrono::duration_cast<std::chrono::seconds>(EndTime - StartTime),
260  getCovarianceMatrix(MinState)};
261  MinuitResult Result(BaseResult, minMin);
262 
263  // In case Minos should be used, recalculate the errors
264  if (minMin.IsValid() && UseMinos) {
265  // MINOS
266  MnMinos minos(Function, minMin, strat);
267  size_t id = 0;
268  for (auto &FinalPar : Result.FinalParameters) {
269  if (FinalPar.IsFixed)
270  continue;
271 
272  // asymmetric errors -> run minos
273  LOG(INFO) << "MinuitIF::optimize() | Run minos "
274  "for parameter ["
275  << id << "] " << FinalPar.Name << "...";
276  MinosError err = minos.Minos(id);
277  FinalPar.Error = err();
278  ++id;
279  }
280  }
281 
282  return Result;
283 }
284 
285 } // namespace Minuit2
286 } // namespace Optimizer
287 } // namespace ComPWA
Parameter not existing.
Definition: Exceptions.hpp:62
MinuitResult optimize(ComPWA::Estimator::Estimator< double > &Estimator, ComPWA::FitParameterList InitialParameters)
Finds the optimal value of the Estimator, by varying its parameters.
Definition: MinuitIF.cpp:139
ComPWA exceptions.
double shiftAngle(double value)
Definition: Utils.hpp:27
std::vector< FitParameter< double > > FitParameterList
std::string checkStrategy()
Check if a pre-defined strategy is set or if custom settings are used.
Definition: MinuitIF.cpp:103
virtual OutputType evaluate(const InputTypes &... args) noexcept=0
FitParameterList getFinalParameters(const ROOT::Minuit2::MnUserParameterState &minState, FitParameterList InitialParameters)
Definition: MinuitIF.cpp:34
Data structure which resembles a general fit result.
Definition: FitResult.hpp:19
FitParameterList FinalParameters
Definition: FitResult.hpp:21
std::vector< std::vector< double > > getCovarianceMatrix(const ROOT::Minuit2::MnUserParameterState &minState)
Definition: MinuitIF.cpp:56
void setStrategy(std::string strategy)
Minuit strategy (low, medium(default), high) See https://root.cern.ch/root/htmldoc/guides/minuit2/Min...
Definition: MinuitIF.cpp:82
Interface template for a general Function of the form OutputType Function(InputTypes) The concept clo...
Definition: Function.hpp:24