Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
MnFunctionCross.cxx
Go to the documentation of this file.
1// @(#)root/minuit2:$Id$
2// Authors: M. Winkler, F. James, L. Moneta, A. Zsenei 2003-2005
3
4/**********************************************************************
5 * *
6 * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT *
7 * *
8 **********************************************************************/
9
12#include "Minuit2/MnMigrad.h"
13#include "Minuit2/FCNBase.h"
14#include "Minuit2/MnParabola.h"
17#include "Minuit2/MnCross.h"
19#include "Minuit2/MnPrint.h"
20
21#include <array>
22
23namespace ROOT {
24
25namespace Minuit2 {
26
27MnCross MnFunctionCross::operator()(std::span<const unsigned int> par, std::span<const double> pmid,
28 std::span<const double> pdir, double tlr, unsigned int maxcalls) const
29{
30 // evaluate crossing point where function is equal to MIN + UP,
31 // with direction pdir from values pmid
32 // tlr indicate tolerance and maxcalls maximum number of calls
33
34 // double edmmax = 0.5*0.001*toler*fFCN.Up();
35
36 unsigned int npar = par.size();
37 unsigned int nfcn = 0;
38 const MnMachinePrecision &prec = fState.Precision();
39 // tolerance used when calling Migrad
40 double mgr_tlr = 0.5 * tlr; // to be consistent with F77 version (for default values of tlr which is 0.1)
41 // other olerance values are fixed at 0.01
42 tlr = 0.01;
43 // convergence when F is within tlf of aim and next prediction
44 // of aopt is within tla of previous value of aopt
45 double up = fFCN.Up();
46 // for finding the point :
47 double tlf = tlr * up;
48 double tla = tlr;
49 unsigned int maxitr = 30;
50 unsigned int ipt = 0;
51 double aminsv = fFval;
52 double aim = aminsv + up;
53 // std::cout<<"aim= "<<aim<<std::endl;
54 double aopt = 0.;
55 bool limset = false;
56 std::array<double, 3> alsb{0., 0., 0.};
57 std::array<double, 3> flsb{0., 0., 0.};
58
59 MnPrint print("MnFunctionCross");
60
61 print.Debug([&](std::ostream &os) {
62 for (unsigned int i = 0; i < par.size(); ++i)
63 os << "Parameter " << par[i] << " value " << pmid[i] << " dir " << pdir[i] << " function min = " << aminsv
64 << " contour value aim = (fmin + up) = " << aim;
65 });
66
67 // find the largest allowed aulim
68
69 double aulim = 100.;
70 for (unsigned int i = 0; i < par.size(); i++) {
71 unsigned int kex = par[i];
72 if (fState.Parameter(kex).HasLimits()) {
73 double zmid = pmid[i];
74 double zdir = pdir[i];
75 // double zlim = 0.;
76 if (zdir > 0. && fState.Parameter(kex).HasUpperLimit()) {
77 double zlim = fState.Parameter(kex).UpperLimit();
78 if (std::fabs(zdir) < fState.Precision().Eps()) {
79 // we have a limit
80 if (std::fabs(zlim - zmid) < fState.Precision().Eps())
81 limset = true;
82 continue;
83 }
84 aulim = std::min(aulim, (zlim - zmid) / zdir);
85 } else if (zdir < 0. && fState.Parameter(kex).HasLowerLimit()) {
86 double zlim = fState.Parameter(kex).LowerLimit();
87 if (std::fabs(zdir) < fState.Precision().Eps()) {
88 // we have a limit
89 if (std::fabs(zlim - zmid) < fState.Precision().Eps())
90 limset = true;
91 continue;
92 }
93 aulim = std::min(aulim, (zlim - zmid) / zdir);
94 }
95 }
96 }
97
98 print.Debug("Largest allowed aulim", aulim);
99
100 // case of a single parameter and we are at limit
101 if (limset && npar == 1) {
102 print.Warn("Parameter is at limit", pmid[0], "delta", pdir[0]);
104 }
105
106 if (aulim < aopt + tla)
107 limset = true;
108
109 MnMigrad migrad(fFCN, fState, MnStrategy(std::max(0, int(fStrategy.Strategy() - 1))));
110
111 print.Info([&](std::ostream &os) {
112 os << "Run Migrad with fixed parameters:";
113 for (unsigned i = 0; i < npar; ++i)
114 os << "\n Pos " << par[i] << ": " << fState.Name(par[i]) << " = " << pmid[i];
115 });
116
117 for (unsigned int i = 0; i < npar; i++)
118 migrad.SetValue(par[i], pmid[i]);
119
120 // find minimum with respect all the other parameters (n- npar) (npar are the fixed ones)
121
123 nfcn += min0.NFcn();
124
125 print.Info("Result after Migrad", MnPrint::Oneline(min0), min0.UserState().Parameters());
126
127 // case a new minimum is found
128 if (min0.Fval() < fFval - tlf) {
129 // case of new minimum is found
130 print.Warn("New minimum found while scanning parameter", par.front(), "new value =", min0.Fval(),
131 "old value =", fFval);
132 return MnCross(min0.UserState(), nfcn, MnCross::CrossNewMin());
133 }
134 if (min0.HasReachedCallLimit())
135 return MnCross(min0.UserState(), nfcn, MnCross::CrossFcnLimit());
136 if (!min0.IsValid())
137 return MnCross(fState, nfcn);
138 if (limset == true && min0.Fval() < aim)
139 return MnCross(min0.UserState(), nfcn, MnCross::CrossParLimit());
140
141 ipt++;
142 alsb[0] = 0.;
143 flsb[0] = min0.Fval();
144 flsb[0] = std::max(flsb[0], aminsv + 0.1 * up);
145 aopt = std::sqrt(up / (flsb[0] - aminsv)) - 1.;
146 if (std::fabs(flsb[0] - aim) < tlf)
147 return MnCross(aopt, min0.UserState(), nfcn);
148
149 if (aopt > 1.)
150 aopt = 1.;
151 if (aopt < -0.5)
152 aopt = -0.5;
153 limset = false;
154 if (aopt > aulim) {
155 aopt = aulim;
156 limset = true;
157 }
158
159 print.Debug("flsb[0]", flsb[0], "aopt", aopt);
160
161 print.Info([&](std::ostream &os) {
162 os << "Run Migrad again (2nd) with fixed parameters:";
163 for (unsigned i = 0; i < npar; ++i)
164 os << "\n Pos " << par[i] << ": " << fState.Name(par[i]) << " = " << pmid[i] + (aopt)*pdir[i];
165 });
166
167 for (unsigned int i = 0; i < npar; i++)
168 migrad.SetValue(par[i], pmid[i] + (aopt)*pdir[i]);
169
171 nfcn += min1.NFcn();
172
173 print.Info("Result after 2nd Migrad", MnPrint::Oneline(min1), min1.UserState().Parameters());
174
175 if (min1.Fval() < fFval - tlf) {
176 // case of new minimum found
177 print.Debug("A new minimum is found: return");
178 return MnCross(min1.UserState(), nfcn, MnCross::CrossNewMin());
179 }
180 if (min1.HasReachedCallLimit()) {
181 print.Debug("FCN call limit is reached: return");
182 return MnCross(min1.UserState(), nfcn, MnCross::CrossFcnLimit());
183 }
184 if (!min1.IsValid()) {
185 print.Debug("Migrad failed: return ");
186 return MnCross(fState, nfcn);
187 }
188 if (limset == true && min1.Fval() < aim) {
189 print.Debug("Parameter(s) at limit: return ");
190 return MnCross(min1.UserState(), nfcn, MnCross::CrossParLimit());
191 }
192
193 ipt++;
194 alsb[1] = aopt;
195 flsb[1] = min1.Fval();
196 double dfda = (flsb[1] - flsb[0]) / (alsb[1] - alsb[0]);
197
198 print.Debug("aopt", aopt, "min1Val", flsb[1], "dfda", dfda);
199
200L300:
201 if (dfda < 0.) {
202 // looking for slope of the right sign
203 print.Debug("dfda < 0 - iterate from", ipt, "to max of", maxitr);
204 // iterate (max times is maxitr) incrementing aopt
205
206 unsigned int maxlk = maxitr - ipt;
207 for (unsigned int it = 0; it < maxlk; it++) {
208 alsb[0] = alsb[1];
209 flsb[0] = flsb[1];
210 // LM: Add + 1, looking at Fortran code it starts from 1 ( see bug #8396)
211 aopt = alsb[0] + 0.2 * (it + 1);
212 limset = false;
213 if (aopt > aulim) {
214 aopt = aulim;
215 limset = true;
216 }
217
218 print.Info([&](std::ostream &os) {
219 os << "Run Migrad again (iteration " << it << " ) :";
220 for (unsigned i = 0; i < npar; ++i)
221 os << "\n parameter " << par[i] << " (" << fState.Name(par[i]) << ") fixed to "
222 << pmid[i] + (aopt)*pdir[i];
223 });
224
225 for (unsigned int i = 0; i < npar; i++)
226 migrad.SetValue(par[i], pmid[i] + (aopt)*pdir[i]);
227
228 min1 = migrad(maxcalls, mgr_tlr);
229 nfcn += min1.NFcn();
230
231 print.Info("Result after Migrad", MnPrint::Oneline(min1), '\n', min1.UserState().Parameters());
232
233 if (min1.Fval() < fFval - tlf) { // case of new minimum found
234 print.Debug("A new minimum is found: return");
235 return MnCross(min1.UserState(), nfcn, MnCross::CrossNewMin());
236 }
237 if (min1.HasReachedCallLimit()) {
238 print.Debug("FCN call limit is reached: return");
239 return MnCross(min1.UserState(), nfcn, MnCross::CrossFcnLimit());
240 }
241 if (!min1.IsValid()){
242 print.Debug("Migrad failed: return ");
243 return MnCross(fState, nfcn);
244 }
245 if (limset == true && min1.Fval() < aim) {
246 print.Debug("Parameter(s) at limit: return ");
247 return MnCross(min1.UserState(), nfcn, MnCross::CrossParLimit());
248 }
249 ipt++;
250 alsb[1] = aopt;
251 flsb[1] = min1.Fval();
252 dfda = (flsb[1] - flsb[0]) / (alsb[1] - alsb[0]);
253 // if(dfda > 0.) goto L460;
254
255 print.Debug("aopt", aopt, "min1Val", flsb[1], "dfda", dfda);
256
257 if (dfda > 0.)
258 break;
259 }
260 if (ipt > maxitr)
261 return MnCross(fState, nfcn);
262 } // if(dfda < 0.)
263
264L460:
265
266 // dfda > 0: we have two points with the right slope
267
268 aopt = alsb[1] + (aim - flsb[1]) / dfda;
269
270 print.Debug("dfda > 0 : aopt", aopt);
271
272 double fdist = std::min(std::fabs(aim - flsb[0]), std::fabs(aim - flsb[1]));
273 double adist = std::min(std::fabs(aopt - alsb[0]), std::fabs(aopt - alsb[1]));
274 tla = tlr;
275 if (std::fabs(aopt) > 1.)
276 tla = tlr * std::fabs(aopt);
277 if (adist < tla && fdist < tlf) {
278 print.Info("Return: Found good value for aopt = ",aopt);
279 return MnCross(aopt, min1.UserState(), nfcn);
280 }
281 if (ipt > maxitr) {
282 print.Info("Number of iterations",ipt,"larger than max",maxitr,": return");
283 return MnCross(fState, nfcn);
284 }
285 double bmin = std::min(alsb[0], alsb[1]) - 1.;
286 if (aopt < bmin)
287 aopt = bmin;
288 double bmax = std::max(alsb[0], alsb[1]) + 1.;
289 if (aopt > bmax)
290 aopt = bmax;
291
292 limset = false;
293 if (aopt > aulim) {
294 aopt = aulim;
295 limset = true;
296 }
297
298 print.Info([&](std::ostream &os) {
299 os << "Run Migrad again (3rd) with fixed parameters:";
300 for (unsigned i = 0; i < npar; ++i)
301 os << "\n Pos " << par[i] << ": " << fState.Name(par[i]) << " = " << pmid[i] + (aopt)*pdir[i];
302 });
303
304 for (unsigned int i = 0; i < npar; i++)
305 migrad.SetValue(par[i], pmid[i] + (aopt)*pdir[i]);
306
308 nfcn += min2.NFcn();
309
310 print.Info("Result after Migrad (3rd):", MnPrint::Oneline(min2), min2.UserState().Parameters());
311
312 if (min2.Fval() < fFval - tlf) {// case of new minimum found
313 print.Debug("A new minimum is found: return");
314 return MnCross(min2.UserState(), nfcn, MnCross::CrossNewMin());
315 }
316 if (min2.HasReachedCallLimit()) {
317 print.Debug("FCN call limit is reached: return");
318 return MnCross(min2.UserState(), nfcn, MnCross::CrossFcnLimit());
319 }
320 if (!min2.IsValid()) {
321 print.Debug("Migrad failed: return ");
322 return MnCross(fState, nfcn);
323 }
324 if (limset == true && min2.Fval() < aim) {
325 print.Debug("Parameter(s) at limit: return ");
326 return MnCross(min2.UserState(), nfcn, MnCross::CrossParLimit());
327 }
328
329 ipt++;
330 alsb[2] = aopt;
331 flsb[2] = min2.Fval();
332
333 // now we have three points, ask how many < AIM
334
335 double ecarmn = std::fabs(flsb[2] - aim);
336 double ecarmx = 0.;
337 unsigned int ibest = 2;
338 unsigned int iworst = 0;
339 unsigned int noless = 0;
340
341 for (unsigned int i = 0; i < 3; i++) {
342 double ecart = std::fabs(flsb[i] - aim);
343 if (ecart > ecarmx) {
344 ecarmx = ecart;
345 iworst = i;
346 }
347 if (ecart < ecarmn) {
348 ecarmn = ecart;
349 ibest = i;
350 }
351 if (flsb[i] < aim)
352 noless++;
353 }
354
355 print.Debug("have three points : noless < aim; noless", noless, "ibest", ibest, "iworst", iworst);
356
357 // std::cout<<"480"<<std::endl;
358
359 // at least one on each side of AIM (contour), fit a parabola
360 if (noless == 1 || noless == 2)
361 goto L500;
362 // if all three are above AIM, third point must be the closest to AIM, return it
363 if (noless == 0 && ibest != 2) {
364 print.Debug("all 3 points are above - invalid result- return");
365 return MnCross(fState, nfcn);
366 }
367 // if all three below and third is not best then the slope has again gone negative,
368 // re-iterate and look for positive slope
369 if (noless == 3 && ibest != 2) {
370 alsb[1] = alsb[2];
371 flsb[1] = flsb[2];
372
373 print.Debug("All three points below - look again for positive slope");
374 goto L300;
375 }
376
377 // in other case new straight line thru first two points
378
379 flsb[iworst] = flsb[2];
380 alsb[iworst] = alsb[2];
381 dfda = (flsb[1] - flsb[0]) / (alsb[1] - alsb[0]);
382
383 print.Debug("New straight line using point 1-2; dfda", dfda);
384
385 goto L460;
386
387L500:
388
389 do {
390 // do parabola fit
392 MnParabolaPoint(alsb[2], flsb[2]));
393 // aopt = parbol.X_pos(aim);
394 // std::cout<<"alsb1,2,3= "<<alsb[0]<<", "<<alsb[1]<<", "<<alsb[2]<<std::endl;
395 // std::cout<<"flsb1,2,3= "<<flsb[0]<<", "<<flsb[1]<<", "<<flsb[2]<<std::endl;
396
397 print.Debug("Parabola fit: iteration", ipt);
398
399 double coeff1 = parbol.C();
400 double coeff2 = parbol.B();
401 double coeff3 = parbol.A();
402 double determ = coeff2 * coeff2 - 4. * coeff3 * (coeff1 - aim);
403
404 print.Debug("Parabola fit: a =", coeff3, "b =", coeff2, "c =", coeff1, "determ =", determ);
405
406 // curvature is negative
407 if (determ < prec.Eps())
408 return MnCross(fState, nfcn);
409 double rt = std::sqrt(determ);
410 double x1 = (-coeff2 + rt) / (2. * coeff3);
411 double x2 = (-coeff2 - rt) / (2. * coeff3);
412 double s1 = coeff2 + 2. * x1 * coeff3;
413 double s2 = coeff2 + 2. * x2 * coeff3;
414
415 print.Debug("Parabola fit: x1", x1, "x2", x2, "s1", s1, "s2", s2);
416
417 if (s1 * s2 > 0.)
418 print.Warn("Problem 1");
419
420 // find with root is the right one
421 aopt = x1;
422 double slope = s1;
423 if (s2 > 0.) {
424 aopt = x2;
425 slope = s2;
426 }
427
428 print.Debug("Parabola fit: aopt", aopt, "slope", slope);
429
430 // ask if converged
431 tla = tlr;
432 if (std::fabs(aopt) > 1.)
433 tla = tlr * std::fabs(aopt);
434
435 print.Debug("Delta(aopt)", std::fabs(aopt - alsb[ibest]), "tla", tla, "Delta(F)", std::fabs(flsb[ibest] - aim),
436 "tlf", tlf);
437
438 if (std::fabs(aopt - alsb[ibest]) < tla && std::fabs(flsb[ibest] - aim) < tlf) {
439 print.Debug("Return: Found best value is within tolerance, aopt",aopt,"F=",flsb[ibest]);
440 return MnCross(aopt, min2.UserState(), nfcn);
441 }
442
443 // if(ipt > maxitr) return MnCross();
444
445 // see if proposed point is in acceptable zone between L and R
446 // first find ileft, iright, iout and ibest
447
448 unsigned int ileft = 3;
449 unsigned int iright = 3;
450 unsigned int iout = 3;
451 ibest = 0;
452 ecarmx = 0.;
453 ecarmn = std::fabs(aim - flsb[0]);
454 for (unsigned int i = 0; i < 3; i++) {
455 double ecart = std::fabs(flsb[i] - aim);
456 if (ecart < ecarmn) {
457 ecarmn = ecart;
458 ibest = i;
459 }
460 if (ecart > ecarmx)
461 ecarmx = ecart;
462 if (flsb[i] > aim) {
463 if (iright == 3)
464 iright = i;
465 else if (flsb[i] > flsb[iright])
466 iout = i;
467 else {
468 iout = iright;
469 iright = i;
470 }
471 } else if (ileft == 3)
472 ileft = i;
473 else if (flsb[i] < flsb[ileft])
474 iout = i;
475 else {
476 iout = ileft;
477 ileft = i;
478 }
479 }
480
481 print.Debug("ileft", ileft, "iright", iright, "iout", iout, "ibest", ibest);
482
483 // avoid keeping a bad point nest time around
484
485 if (ecarmx > 10. * std::fabs(flsb[iout] - aim))
486 aopt = 0.5 * (aopt + 0.5 * (alsb[iright] + alsb[ileft]));
487
488 // knowing ileft and iright, get acceptable window
489 double smalla = 0.1 * tla;
490 if (slope * smalla > tlf)
491 smalla = tlf / slope;
492 double aleft = alsb[ileft] + smalla;
493 double aright = alsb[iright] - smalla;
494
495 // move proposed point AOPT into window if necessary
496 if (aopt < aleft)
497 aopt = aleft;
498 if (aopt > aright)
499 aopt = aright;
500 if (aleft > aright)
501 aopt = 0.5 * (aleft + aright);
502
503 // see if proposed point outside limits (should be impossible)
504 limset = false;
505 if (aopt > aulim) {
506 aopt = aulim;
507 limset = true;
508 }
509
510 // evaluate at new point aopt
511 print.Info([&](std::ostream &os) {
512 os << "Run Migrad again at new point (#iter = " << ipt+1 << " ):";
513 for (unsigned i = 0; i < npar; ++i)
514 os << "\n\t - parameter " << par[i] << " fixed to " << pmid[i] + (aopt)*pdir[i];
515 });
516
517 for (unsigned int i = 0; i < npar; i++)
518 migrad.SetValue(par[i], pmid[i] + (aopt)*pdir[i]);
519
520 min2 = migrad(maxcalls, mgr_tlr);
521 nfcn += min2.NFcn();
522
523 print.Info("Result after new Migrad:", MnPrint::Oneline(min2), min2.UserState().Parameters());
524
525 if (min2.Fval() < fFval - tlf) { // case of new minimum found
526 print.Debug("A new minimum is found: return");
527 return MnCross(min2.UserState(), nfcn, MnCross::CrossNewMin());
528 }
529 if (min2.HasReachedCallLimit()) {
530 print.Debug("FCN call limit is reached: return");
531 return MnCross(min2.UserState(), nfcn, MnCross::CrossFcnLimit());
532 }
533 if (!min2.IsValid()) {
534 print.Debug("Migrad failed: return ");
535 return MnCross(fState, nfcn);
536 }
537 if (limset == true && min2.Fval() < aim) {
538 print.Debug("Parameter(s) at limit: return ");
539 return MnCross(min2.UserState(), nfcn, MnCross::CrossParLimit());
540 }
541
542 ipt++;
543 // replace odd point with new one (which is the best of three)
544 alsb[iout] = aopt;
545 flsb[iout] = min2.Fval();
546 ibest = iout;
547 } while (ipt < maxitr);
548
549 // goto L500;
550
551 print.Debug("Best point is not found: return invalid result after many trial",ipt);
552 return MnCross(fState, nfcn);
553}
554
555} // namespace Minuit2
556
557} // namespace ROOT
#define s1(x)
Definition RSha256.hxx:91
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
Option_t Option_t TPoint TPoint const char x2
Option_t Option_t TPoint TPoint const char x1
class holding the full result of the minimization; both internal and external (MnUserParameterState) ...
void SetValue(unsigned int, double)
MnCross operator()(std::span< const unsigned int >, std::span< const double >, std::span< const double >, double, unsigned int) const
const MnUserParameterState & fState
Sets the relative floating point (double) arithmetic precision.
API class for minimization using Variable Metric technology ("MIGRAD"); allows for user interaction: ...
Definition MnMigrad.h:32
This class defines a parabola of the form a*x*x + b*x + c.
Definition MnParabola.h:30
void Debug(const Ts &... args)
Definition MnPrint.h:135
void Info(const Ts &... args)
Definition MnPrint.h:129
void Warn(const Ts &... args)
Definition MnPrint.h:123
API class for defining four levels of strategies: low (0), medium (1), high (2), very high (>=3); act...
Definition MnStrategy.h:27
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...