Skip to content

Commit 5a0ca98

Browse files
will-cernguitargeek
authored andcommitted
[Math][Minuit2] New Minuit2 strategy for improved Hessian calculation
New Minuit2 strategy for improved Hessian calculation and return without making positive definite. This proposed new Strategy in minuit2 is the same migrad behaviour as Strategy=2 but with the following changes to the Hesse calculation: * The step and g2 tolerances have been zeroed so that the 7 cycles are forced in the diagonal-term calculation. This was found to be necessary in cases where asimov datasets were used for the minimization and there were very few iterations for the approximate covariance to be determined from. * Central finite difference is used for mixed partial derivatives. This requires 3 extra function evaluations per derivative, but is necessary in the case of minima where there is high curvature (in the case of high stats) and the forward finite difference (default) behaviour leads incorrectly to a non-positive-definite covariance matrix * Return the uncorrected covariance matrix, even if it is not positive definite. This useful for checking just how far from positive-definiteness the matrix is by being able to examine the eigenvalues. Additionally, a lower bound on the precision allowed for the spread of eigenvalues of the "hessian" correlation matrix (computing a correlation matrix with the hessian as if it was a covariance matrix) was reduced from 1e-6 to 1e-12 (see MnHesse.cxx) ... it is not clear why 1e-6 was the lower bound previously, but current machine precision can beat that (I get locally 1e-8). I left a comment about whether this lower bound should be made configurable or not... This new strategy was tested with a model with high statistics (almost 50 million events) where the migrad minimization was successful but the hessian was being forced positive definite. With this new Strategy 3 the hessian is accurate and positive definite in all cases tested.
1 parent 6fd4b5d commit 5a0ca98

7 files changed

Lines changed: 116 additions & 22 deletions

File tree

math/minuit2/inc/Minuit2/MinimumError.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ class MinimumError {
7676
double Dcovar() const { return fPtr->fDCovar; }
7777
Status GetStatus() const { return fPtr->fStatus; }
7878

79-
bool IsValid() const { return IsAvailable() && (IsPosDef() || IsMadePosDef()); }
79+
bool IsValid() const { return IsAvailable() && (IsPosDef() || IsMadePosDef() || IsNotPosDef()); }
8080
bool IsAccurate() const { return IsPosDef() && Dcovar() < 0.1; }
8181

8282
bool IsPosDef() const { return GetStatus() == MnPosDef; }
8383
bool IsMadePosDef() const { return GetStatus() == MnMadePosDef; }
84+
bool IsNotPosDef() const { return GetStatus() == MnNotPosDef; }
8485
bool HesseFailed() const { return GetStatus() == MnHesseFailed; }
8586
bool InvertFailed() const { return GetStatus() == MnInvertFailed; }
8687
bool HasReachedCallLimit() const { return GetStatus() == MnReachedCallLimit; }

math/minuit2/inc/Minuit2/MnStrategy.h

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ namespace Minuit2 {
1616

1717
//_________________________________________________________________________
1818
/**
19-
API class for defining three levels of strategies: low (0), medium (1),
20-
high (>=2);
19+
API class for defining four levels of strategies: low (0), medium (1),
20+
high (2), very high (>=3);
2121
acts on: Migrad (behavioural),
2222
Minos (lowers strategy by 1 for Minos-own minimization),
2323
Hesse (iterations),
@@ -30,7 +30,7 @@ class MnStrategy {
3030
// default strategy
3131
MnStrategy();
3232

33-
// user defined strategy (0, 1, >=2)
33+
// user defined strategy (0, 1, 2, >=3)
3434
explicit MnStrategy(unsigned int);
3535

3636
~MnStrategy() {}
@@ -45,16 +45,20 @@ class MnStrategy {
4545
double HessianStepTolerance() const { return fHessTlrStp; }
4646
double HessianG2Tolerance() const { return fHessTlrG2; }
4747
unsigned int HessianGradientNCycles() const { return fHessGradNCyc; }
48+
unsigned int HessianCentralFDMixedDerivatives() const { return fHessCFDG2; }
49+
unsigned int HessianForcePosDef() const { return fHessForcePosDef; }
4850

4951
int StorageLevel() const { return fStoreLevel; }
5052

5153
bool IsLow() const { return fStrategy == 0; }
5254
bool IsMedium() const { return fStrategy == 1; }
53-
bool IsHigh() const { return fStrategy >= 2; }
55+
bool IsHigh() const { return fStrategy == 2; }
56+
bool IsVeryHigh() const { return fStrategy >= 3; }
5457

5558
void SetLowStrategy();
5659
void SetMediumStrategy();
5760
void SetHighStrategy();
61+
void SetVeryHighStrategy();
5862

5963
void SetGradientNCycles(unsigned int n) { fGradNCyc = n; }
6064
void SetGradientStepTolerance(double stp) { fGradTlrStp = stp; }
@@ -65,6 +69,14 @@ class MnStrategy {
6569
void SetHessianG2Tolerance(double toler) { fHessTlrG2 = toler; }
6670
void SetHessianGradientNCycles(unsigned int n) { fHessGradNCyc = n; }
6771

72+
// 1 = calculate central finite difference mixed derivatives (involves 3 extra evaluations per derivative)
73+
// 0 = use forward finite difference (default)
74+
void SetHessianCentralFDMixedDerivatives(unsigned int flag) { fHessCFDG2 = flag; }
75+
76+
// 1 = returned matrix from Hesse should be forced positive definite (default)
77+
// 0 = do not force matrix positive definite
78+
void SetHessianForcePosDef(unsigned int flag) { fHessForcePosDef = flag; }
79+
6880
// set storage level of iteration quantities
6981
// 0 = store only last iterations 1 = full storage (default)
7082
void SetStorageLevel(unsigned int level) { fStoreLevel = level; }
@@ -79,6 +91,8 @@ class MnStrategy {
7991
double fHessTlrStp;
8092
double fHessTlrG2;
8193
unsigned int fHessGradNCyc;
94+
int fHessCFDG2;
95+
int fHessForcePosDef;
8296
int fStoreLevel;
8397
};
8498

math/minuit2/src/Minuit2Minimizer.cxx

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,10 @@ bool Minuit2Minimizer::Minimize()
467467

468468
// set strategy and add extra options if needed
469469
ROOT::Minuit2::MnStrategy strategy(strategyLevel);
470-
ROOT::Math::IOptions *minuit2Opt = ROOT::Math::MinimizerOptions::FindDefault("Minuit2");
470+
const ROOT::Math::IOptions *minuit2Opt = fOptions.ExtraOptions();
471+
if (!minuit2Opt) {
472+
minuit2Opt = ROOT::Math::MinimizerOptions::FindDefault("Minuit2");
473+
}
471474
if (minuit2Opt) {
472475
// set extra options
473476
int nGradCycles = strategy.GradientNCycles();
@@ -495,7 +498,7 @@ bool Minuit2Minimizer::Minimize()
495498
strategy.SetGradientTolerance(gradTol);
496499
strategy.SetGradientStepTolerance(gradStepTol);
497500
strategy.SetHessianStepTolerance(hessStepTol);
498-
strategy.SetHessianG2Tolerance(hessStepTol);
501+
strategy.SetHessianG2Tolerance(hessG2Tol);
499502

500503
int storageLevel = 1;
501504
bool ret = minuit2Opt->GetValue("StorageLevel", storageLevel);
@@ -551,7 +554,7 @@ bool Minuit2Minimizer::Minimize()
551554
fMinimum = new ROOT::Minuit2::FunctionMinimum(min);
552555
}
553556

554-
// check if Hesse needs to be run. We do it when is requested (IsValidError() == true)
557+
// check if Hesse needs to be run. We do it when is requested (IsValidError() == true , set by SetParabError(true) in fitConfig)
555558
// (IsValidError() means the flag to get correct error from the Minimizer is set (Minimizer::SetValidError())
556559
// AND when we have a valid minimum,
557560
// AND when the the current covariance matrix is estimated using the iterative approximation (Dcovar != 0 , i.e. Hesse has not computed before)
@@ -1202,7 +1205,43 @@ bool Minuit2Minimizer::Hesse()
12021205
return false;
12031206
}
12041207

1205-
const int strategy = Strategy();
1208+
1209+
// set strategy and add extra options if needed
1210+
ROOT::Minuit2::MnStrategy strategy(Strategy());
1211+
const ROOT::Math::IOptions *minuit2Opt = fOptions.ExtraOptions();
1212+
if (!minuit2Opt) {
1213+
minuit2Opt = ROOT::Math::MinimizerOptions::FindDefault("Minuit2");
1214+
}
1215+
if (minuit2Opt) {
1216+
// set extra options
1217+
int nGradCycles = strategy.GradientNCycles();
1218+
int nHessCycles = strategy.HessianNCycles();
1219+
int nHessGradCycles = strategy.HessianGradientNCycles();
1220+
1221+
double gradTol = strategy.GradientTolerance();
1222+
double gradStepTol = strategy.GradientStepTolerance();
1223+
double hessStepTol = strategy.HessianStepTolerance();
1224+
double hessG2Tol = strategy.HessianG2Tolerance();
1225+
1226+
minuit2Opt->GetValue("GradientNCycles", nGradCycles);
1227+
minuit2Opt->GetValue("HessianNCycles", nHessCycles);
1228+
minuit2Opt->GetValue("HessianGradientNCycles", nHessGradCycles);
1229+
1230+
minuit2Opt->GetValue("GradientTolerance", gradTol);
1231+
minuit2Opt->GetValue("GradientStepTolerance", gradStepTol);
1232+
minuit2Opt->GetValue("HessianStepTolerance", hessStepTol);
1233+
minuit2Opt->GetValue("HessianG2Tolerance", hessG2Tol);
1234+
1235+
strategy.SetGradientNCycles(nGradCycles);
1236+
strategy.SetHessianNCycles(nHessCycles);
1237+
strategy.SetHessianGradientNCycles(nHessGradCycles);
1238+
1239+
strategy.SetGradientTolerance(gradTol);
1240+
strategy.SetGradientStepTolerance(gradStepTol);
1241+
strategy.SetHessianStepTolerance(hessStepTol);
1242+
strategy.SetHessianG2Tolerance(hessG2Tol);
1243+
}
1244+
12061245
const int maxfcn = MaxFunctionCalls();
12071246
print.Info("Using max-calls", maxfcn);
12081247

@@ -1254,6 +1293,8 @@ bool Minuit2Minimizer::Hesse()
12541293
covStatusType = "full but made positive defined";
12551294
if (covStatus == 3)
12561295
covStatusType = "accurate";
1296+
if (covStatus == 0)
1297+
covStatusType = "full but not positive defined";
12571298

12581299
if (!fState.HasCovariance()) {
12591300
// if false means error is not valid and this is due to a failure in Hesse

math/minuit2/src/MnHesse.cxx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ MinimumState MnHesse::ComputeNumerical(const MnFcn &mfcn, const MinimumState &st
334334

335335
// off-diagonal Elements
336336
// initial starting values
337+
bool doCentralFD = fStrategy.HessianCentralFDMixedDerivatives();
337338
if (n > 0) {
338339
MPIProcess mpiprocOffDiagonal(n * (n - 1) / 2, 0);
339340
unsigned int startParIndexOffDiagonal = mpiprocOffDiagonal.StartElementIndex();
@@ -357,10 +358,19 @@ MinimumState MnHesse::ComputeNumerical(const MnFcn &mfcn, const MinimumState &st
357358
x(j) += dirin(j);
358359

359360
double fs1 = mfcn(x);
360-
double elem = (fs1 + amin - yy(i) - yy(j)) / (dirin(i) * dirin(j));
361-
vhmat(i, j) = elem;
362-
363-
x(j) -= dirin(j);
361+
if(!doCentralFD) {
362+
double elem = (fs1 + amin - yy(i) - yy(j)) / (dirin(i) * dirin(j));
363+
vhmat(i, j) = elem;
364+
x(j) -= dirin(j);
365+
} else {
366+
// three more function evaluations required for central fd
367+
x(i) -= dirin(i); x(i) -= dirin(i);double fs3 = mfcn(x);
368+
x(j) -= dirin(j); x(j) -= dirin(j);double fs4 = mfcn(x);
369+
x(i) += dirin(i); x(i) += dirin(i);double fs2 = mfcn(x);
370+
x(j) += dirin(j);
371+
double elem = (fs1 - fs2 - fs3 + fs4)/(4.*dirin(i)*dirin(j));
372+
vhmat(i, j) = elem;
373+
}
364374

365375
if (j % (n - 1) == 0 || in == endParIndexOffDiagonal - 1)
366376
x(i) -= dirin(i);
@@ -370,11 +380,17 @@ MinimumState MnHesse::ComputeNumerical(const MnFcn &mfcn, const MinimumState &st
370380
}
371381

372382
// verify if matrix pos-def (still 2nd derivative)
383+
// Note that for cases of extreme spread of eigenvalues, numerical precision
384+
// can mean the hessian is computed as being not pos-def
385+
// but the inverse of it is.
373386

374387
print.Debug("Original error matrix", vhmat);
375388

376-
MinimumError tmpErr = MnPosDef()(MinimumError(vhmat, 1.), prec);
377-
vhmat = tmpErr.InvHessian();
389+
MinimumError tmpErr = MnPosDef()(MinimumError(vhmat, 1.), prec); // pos-def version of hessian
390+
391+
if(fStrategy.HessianForcePosDef()) {
392+
vhmat = tmpErr.InvHessian();
393+
}
378394

379395
print.Debug("PosDef error matrix", vhmat);
380396

@@ -398,7 +414,7 @@ MinimumState MnHesse::ComputeNumerical(const MnFcn &mfcn, const MinimumState &st
398414

399415
// if matrix is made pos def returns anyway edm
400416
if (tmpErr.IsMadePosDef()) {
401-
MinimumError err(vhmat, MinimumError::MnMadePosDef);
417+
MinimumError err(vhmat, fStrategy.HessianForcePosDef() ? MinimumError::MnMadePosDef : MinimumError::MnNotPosDef);
402418
double edm = estim.Estimate(gr, err);
403419
return MinimumState(st.Parameters(), err, gr, edm, mfcn.NumOfCalls());
404420
}

math/minuit2/src/MnPosDef.cxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ MinimumError MnPosDef::operator()(const MinimumError &e, const MnMachinePrecisio
4343
}
4444
// std::cout<<"MnPosDef init matrix= "<<err<<std::endl;
4545

46-
double epspdf = std::max(1.e-6, prec.Eps2());
46+
double epspdf = std::max(1.e-12, prec.Eps2()); // should this lower bound be configurable?
4747
double dgmin = err(0, 0);
4848

4949
for (unsigned int i = 0; i < err.Nrow(); i++) {

math/minuit2/src/MnStrategy.cxx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,23 @@ namespace ROOT {
1313

1414
namespace Minuit2 {
1515

16-
MnStrategy::MnStrategy() : fStoreLevel(1)
16+
MnStrategy::MnStrategy() : fHessCFDG2(0), fHessForcePosDef(1), fStoreLevel(1)
1717
{
1818
// default strategy
1919
SetMediumStrategy();
2020
}
2121

22-
MnStrategy::MnStrategy(unsigned int stra) : fStoreLevel(1)
22+
MnStrategy::MnStrategy(unsigned int stra) : fHessCFDG2(0), fHessForcePosDef(1), fStoreLevel(1)
2323
{
24-
// user defined strategy (0, 1, >=2)
24+
// user defined strategy (0, 1, 2, >=3)
2525
if (stra == 0)
2626
SetLowStrategy();
2727
else if (stra == 1)
2828
SetMediumStrategy();
29-
else
29+
else if (stra == 2)
3030
SetHighStrategy();
31+
else
32+
SetVeryHighStrategy();
3133
}
3234

3335
void MnStrategy::SetLowStrategy()
@@ -41,6 +43,7 @@ void MnStrategy::SetLowStrategy()
4143
SetHessianStepTolerance(0.5);
4244
SetHessianG2Tolerance(0.1);
4345
SetHessianGradientNCycles(1);
46+
SetHessianCentralFDMixedDerivatives(0);
4447
}
4548

4649
void MnStrategy::SetMediumStrategy()
@@ -54,6 +57,7 @@ void MnStrategy::SetMediumStrategy()
5457
SetHessianStepTolerance(0.3);
5558
SetHessianG2Tolerance(0.05);
5659
SetHessianGradientNCycles(2);
60+
SetHessianCentralFDMixedDerivatives(0);
5761
}
5862

5963
void MnStrategy::SetHighStrategy()
@@ -67,6 +71,22 @@ void MnStrategy::SetHighStrategy()
6771
SetHessianStepTolerance(0.1);
6872
SetHessianG2Tolerance(0.02);
6973
SetHessianGradientNCycles(6);
74+
SetHessianCentralFDMixedDerivatives(0);
75+
}
76+
77+
void MnStrategy::SetVeryHighStrategy()
78+
{
79+
// set very high strategy (3)
80+
fStrategy = 3;
81+
SetGradientNCycles(5);
82+
SetGradientStepTolerance(0.1);
83+
SetGradientTolerance(0.02);
84+
SetHessianNCycles(7);
85+
SetHessianStepTolerance(0.);
86+
SetHessianG2Tolerance(0.);
87+
SetHessianGradientNCycles(6);
88+
SetHessianCentralFDMixedDerivatives(1);
89+
SetHessianForcePosDef(0);
7090
}
7191

7292
} // namespace Minuit2

math/minuit2/src/MnUserParameterState.cxx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,14 @@ MnUserParameterState::MnUserParameterState(const MinimumState &st, double up, co
176176

177177
assert(fCovariance.Nrow() == VariableParameters());
178178

179-
fCovStatus = 1; // when is valid
179+
if (!st.Error().IsNotPosDef())
180+
fCovStatus = 1; // when is valid and not NotPosDef
180181
}
181182
if (st.Error().IsMadePosDef())
182183
fCovStatus = 2;
183184
if (st.Error().IsAccurate())
184185
fCovStatus = 3;
186+
185187
}
186188

187189
MnUserCovariance MnUserParameterState::Hessian() const

0 commit comments

Comments
 (0)