GammaLib  2.1.0.dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
GOptimizerLM.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * GOptimizerLM.cpp - Levenberg Marquardt optimizer *
3  * ----------------------------------------------------------------------- *
4  * copyright (C) 2009-2023 by Juergen Knoedlseder *
5  * ----------------------------------------------------------------------- *
6  * *
7  * This program is free software: you can redistribute it and/or modify *
8  * it under the terms of the GNU General Public License as published by *
9  * the Free Software Foundation, either version 3 of the License, or *
10  * (at your option) any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License *
18  * along with this program. If not, see <http://www.gnu.org/licenses/>. *
19  * *
20  ***************************************************************************/
21 /**
22  * @file GOptimizerLM.cpp
23  * @brief Levenberg-Marquardt optimizer class implementation
24  * @author Juergen Knoedlseder
25  */
26 
27 /* __ Includes ___________________________________________________________ */
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 #include "GOptimizerLM.hpp"
32 #include "GTools.hpp"
33 #include "GException.hpp"
34 
35 /* __ Method name definitions ____________________________________________ */
36 
37 /* __ Macros _____________________________________________________________ */
38 
39 /* __ Coding definitions _________________________________________________ */
40 
41 /* __ Debug definitions __________________________________________________ */
42 //#define G_DEBUG_OPT //!< Define to debug optimize() method
43 //#define G_DEBUG_ITER //!< Define to debug iteration() method
44 //#define G_DEBUG_SHOW_GRAD_COVAR //!< Define to show grad and curvature
45 
46 
47 /*==========================================================================
48  = =
49  = Constructors/destructors =
50  = =
51  ==========================================================================*/
52 
53 /***********************************************************************//**
54  * @brief Void constructor
55  ***************************************************************************/
57 {
58  // Initialise private members for clean destruction
59  init_members();
60 
61  // Return
62  return;
63 }
64 
65 
66 /***********************************************************************//**
67  * @brief Constructor with logger
68  *
69  * @param[in] log Logger to use in optimizer.
70  ***************************************************************************/
72 {
73  // Initialise private members for clean destruction
74  init_members();
75 
76  // Set pointer to logger
77  m_logger = log;
78 
79  // Return
80  return;
81 }
82 
83 
84 /***********************************************************************//**
85  * @brief Copy constructor
86  *
87  * @param[in] opt Optimizer from which the instance should be built.
88  ***************************************************************************/
90 {
91  // Initialise private members for clean destruction
92  init_members();
93 
94  // Copy members
95  copy_members(opt);
96 
97  // Return
98  return;
99 }
100 
101 
102 /***********************************************************************//**
103  * @brief Destructor
104  ***************************************************************************/
106 {
107  // Free members
108  free_members();
109 
110  // Return
111  return;
112 }
113 
114 
115 /*==========================================================================
116  = =
117  = Operators =
118  = =
119  ==========================================================================*/
120 
121 /***********************************************************************//**
122  * @brief Assignment operator
123  *
124  * @param[in] opt Optimizer to be assigned.
125  ***************************************************************************/
127 {
128  // Execute only if object is not identical
129  if (this != &opt) {
130 
131  // Copy base class members
132  this->GOptimizer::operator=(opt);
133 
134  // Free members
135  free_members();
136 
137  // Initialise private members for clean destruction
138  init_members();
139 
140  // Copy members
141  copy_members(opt);
142 
143  } // endif: object was not identical
144 
145  // Return
146  return *this;
147 }
148 
149 
150 /*==========================================================================
151  = =
152  = Public methods =
153  = =
154  ==========================================================================*/
155 
156 /***********************************************************************//**
157  * @brief Clear object
158  *
159  * This method properly resets the object to an initial state.
160  ***************************************************************************/
162 {
163  // Free class members (base and derived classes, derived class first)
164  free_members();
165  this->GOptimizer::free_members();
166 
167  // Initialise members
168  this->GOptimizer::init_members();
169  init_members();
170 
171  // Return
172  return;
173 }
174 
175 
176 /***********************************************************************//**
177  * @brief Clone object
178 ***************************************************************************/
180 {
181  return new GOptimizerLM(*this);
182 }
183 
184 
185 /***********************************************************************//**
186  * @brief Optimize function parameters
187  *
188  * @param[in] fct Optimization function.
189  * @param[in] pars Function parameters.
190  ***************************************************************************/
192 {
193  // Initialize optimizer parameters
195  m_value = 0.0;
196  m_delta = 0.0;
198  m_iter = 0;
199  m_num_dec = 0;
200 
201  // Get number of parameters. Continue only if there are free parameters
202  m_npars = pars.size();
203  m_nfree = pars.nfree();
204  if (m_nfree > 0) {
205 
206  // Initialise bookkeeping arrays
207  m_hit_boundary.clear();
208  m_hit_minimum.clear();
209  m_hit_maximum.clear();
210  m_par_freeze.clear();
211  m_par_remove.clear();
212  m_hit_boundary.reserve(m_npars);
213  m_hit_minimum.reserve(m_npars);
214  m_hit_maximum.reserve(m_npars);
215  m_par_freeze.reserve(m_npars);
216  m_par_remove.reserve(m_npars);
217  for (int i = 0; i < m_npars; ++i) {
218  m_hit_boundary.push_back(false);
219  m_hit_minimum.push_back(0);
220  m_hit_maximum.push_back(0);
221  m_par_freeze.push_back(false);
222  m_par_remove.push_back(false);
223  }
224 
225  // Initial function evaluation
226  fct.eval(pars);
227 
228  // If a free parameter has a zero diagonal element in the curvature
229  // matrix then remove this parameter definitely from the fit as it
230  // otherwise will block the fit. The problem appears in the unbinned
231  // fitting where parameter gradients may be zero (due to the truncation
232  // of the PSF), but the Npred gradient is not zero. In principle we
233  // could use the Npred gradient for fitting (I guess), but I still
234  // have to figure out how ... (the diagonal loading was not so
235  // successful as it faked early convergence)
236  for (int ipar = 0; ipar < m_npars; ++ipar) {
237  if (pars[ipar]->is_free()) {
238  if ((*fct.curvature())(ipar,ipar) == 0.0) {
239  if (m_logger != NULL) {
240  *m_logger << " Parameter \"" << pars[ipar]->name();
241  *m_logger << "\" has zero curvature.";
242  *m_logger << " Fix parameter." << std::endl;
243  }
244  m_par_remove[ipar] = true;
245  pars[ipar]->fix();
246  }
247  }
248  }
249 
250  // Save function value
251  m_value = fct.value();
252 
253  // Save initial statistics and lambda values
254  double lambda_old = m_lambda;
255  int lambda_inc = 0;
256 
257  // Optionally write initial iteration into logger
258  if (m_logger != NULL) {
259  (*m_logger)(">Iteration %3d: -logL=%.3f, Lambda=%.1e",
260  0, m_value, m_lambda);
261  }
262  #if defined(G_DEBUG_OPT)
263  std::cout << "Initial iteration: func=" << m_value << ", Lambda="
264  << m_lambda << std::endl;
265  #endif
266  #if defined(G_DEBUG_SHOW_GRAD_COVAR)
267  if (m_logger != NULL) {
268  *m_logger << *fct.gradient() << std::endl;
269  *m_logger << *fct.curvature() << std::endl;
270  }
271  #endif
272 
273  // Initialise iteration flags
274  bool check_for_freeze = true;
275 
276  // Iterative fitting
277  for (m_iter = 1; m_iter <= m_max_iter; ++m_iter) {
278 
279  // Perform one iteration
280  double step = iteration(fct, pars);
281 
282  // Determine maximum (scaled) gradient
283  double grad_max = 0.0;
284  int grad_imax = -1;
285  for (int ipar = 0; ipar < m_npars; ++ipar) {
286  if (pars[ipar]->is_free()) {
287  double grad = pars[ipar]->factor_gradient();
288  if (std::abs(grad) > std::abs(grad_max)) {
289  grad_max = grad;
290  grad_imax = ipar;
291  }
292  if (grad == 0.0) {
293  if (m_logger != NULL) {
294  *m_logger << "Parameter " << ipar;
295  *m_logger << " (" << pars[ipar]->name() << ")";
296  *m_logger << " has a zero gradient." << std::endl;
297  }
298  }
299  }
300  }
301 
302  // Optionally write iteration results into logger
303  if (m_logger != NULL) {
304  std::string stalled = "";
305  std::string status = "";
306  if (m_lambda > lambda_old) {
307  status = " ";
308  stalled = " (stalled)";
309  }
310  else {
311  status = ">";
312  }
313  std::string parname = "";
314  if (grad_imax != -1) {
315  parname = " [" + pars[grad_imax]->name() + ":" +
316  gammalib::str(grad_imax) + "]";
317  }
318  (*m_logger)("%sIteration %3d: -logL=%.3f, Lambda=%.1e,"
319  " delta=%.3f, step=%.1e, max(|grad|)=%f%s%s",
320  status.c_str(), m_iter, m_value, lambda_old,
321  m_delta, step, grad_max,
322  parname.c_str(), stalled.c_str());
323  }
324  #if defined(G_DEBUG_OPT)
325  std::cout << "Iteration " << m_iter << ": func="
326  << m_value << ", Lambda=" << lambda_old
327  << ", delta=" << m_delta << std::endl;
328  #endif
329  #if defined(G_DEBUG_SHOW_GRAD_COVAR)
330  if (m_logger != NULL) {
331  *m_logger << *fct.gradient() << std::endl;
332  *m_logger << *fct.curvature() << std::endl;
333  }
334  #endif
335 
336  // Reset lambda increment if we had success
337  if (m_lambda < lambda_old) {
338  lambda_inc = 0;
339  }
340 
341  // Stop if convergence was reached. Before stopping, check
342  // if some parameters were frozen, and if this was the case,
343  // free them now and continue. We do this only once, i.e.
344  // the next time a parameter is frozen and convergence is
345  // reached we really stop.
346  if ((m_lambda <= lambda_old) &&
347  (m_delta >= 0.0) &&
348  (m_delta < step*m_eps)) {
349 
350  // Check for frozen parameters, and if some exist, free
351  // them and start over
352  if (check_for_freeze) {
353 
354  // Signal that we won't check frozen parameters
355  // anymore
356  check_for_freeze = false;
357 
358  // Free frozen parameters and determine how many
359  // have been frozen
360  int nfrozen = 0;
361  for (int ipar = 0; ipar < m_npars; ++ipar) {
362  if (m_par_freeze[ipar]) {
363  nfrozen++;
364  pars[ipar]->free();
365  if (m_logger != NULL) {
366  *m_logger << " Free parameter \""
367  << pars[ipar]->name()
368  << "\" after convergence was"
369  " reached with frozen"
370  " parameter." << std::endl;
371  }
372  }
373  }
374 
375  // If there were frozen parameters then start over
376  // again (initialise optimizer)
377  if (nfrozen > 0) {
379  lambda_old = m_lambda;
380  lambda_inc = 0;
381  continue;
382  }
383  }
384 
385  // ... otherwise, convergence was reached and we can stop
386  // now
387  break;
388 
389  } // endif: convergence check
390 
391  // Monitor the number of subsequent increases of lambda and
392  // stop if the number of increases exceeds threshold
393  lambda_inc = (m_lambda > lambda_old) ? lambda_inc + 1 : 0;
394  if (lambda_inc >= m_max_stall) {
396  break;
397  }
398 
399  // Bookkeeping of actual result (we always store the last
400  // lambda to detect turn arounds in the lambda tendency; however
401  // we always keep the best function value)
402  lambda_old = m_lambda;
403 
404  } // endfor: iterations
405 
406  // Free now all temporarily frozen parameters so that the resulting
407  // model has the same attributes as the initial model
408  for (int ipar = 0; ipar < m_npars; ++ipar) {
409  if (m_par_freeze[ipar] || m_par_remove[ipar]) {
410  pars[ipar]->free();
411  if (m_logger != NULL) {
412  *m_logger << " Free parameter \""
413  << pars[ipar]->name()
414  << "\" after convergence was"
415  " reached with frozen"
416  " parameter." << std::endl;
417  }
418  }
419  }
420 
421  } // endif: there were free parameters to fit
422 
423  // ... otherwise just evaluate function and store the parameters
424  else {
425 
426  // Evaluate function
427  fct.eval(pars);
428 
429  // Save function value
430  m_value = fct.value();
431 
432  // Optionally write initial iteration into logger
433  if (m_logger != NULL) {
434  (*m_logger)(">Iteration %3d: -logL=%.3f, Lambda=%.1e (no free parameters)",
435  0, m_value, m_lambda);
436  }
437 
438  }
439 
440  // Return
441  return;
442 }
443 
444 
445 /***********************************************************************//**
446  * @brief Compute parameter uncertainties
447  *
448  * @param[in] fct Optimizer function.
449  * @param[in] pars Function parameters.
450  *
451  * Compute parameter uncertainties from the diagonal elements of the
452  * curvature matrix.
453  ***************************************************************************/
455 {
456  // Get number of parameters
457  int npars = pars.size();
458 
459  // Perform final parameter evaluation
460  fct.eval(pars);
461 
462  // Fetch sparse matrix pointer. We have to do this after the eval()
463  // method since eval() will allocate new memory for the curvature
464  // matrix!
465  GMatrixSparse* curvature = fct.curvature();
466 
467  // Save best fitting value
468  m_value = fct.value();
469 
470  // Save curvature matrix
471  GMatrixSparse save_curvature = GMatrixSparse(*curvature);
472 
473  // Signal no diagonal element loading
474  bool diag_loaded = false;
475 
476  // Loop over error computation (maximum 2 turns)
477  for (int i = 0; i < 2; ++i) {
478 
479  // Solve: curvature * X = unit
480  try {
481  GMatrixSparse decomposition = curvature->cholesky_decompose(true);
482  GVector unit(npars);
483  for (int ipar = 0; ipar < npars; ++ipar) {
484  unit[ipar] = 1.0;
485  GVector x = decomposition.cholesky_solver(unit, true);
486  if (x[ipar] >= 0.0) {
487  pars[ipar]->factor_error(sqrt(x[ipar]));
488  }
489  else {
490  pars[ipar]->factor_error(0.0);
492  }
493  unit[ipar] = 0.0;
494  }
495  }
496  catch (GException::runtime_error &e) {
497 
498  // Load diagonal if this has not yet been tried
499  if (!diag_loaded) {
500 
501  // Flag errors as inaccurate
503  if (m_logger != NULL) {
504  *m_logger << "Non-Positive definite curvature matrix encountered."
505  << std::endl;
506  *m_logger << "Load diagonal elements with 1e-10."
507  << " Fit errors may be inaccurate."
508  << std::endl;
509  }
510 
511  // Try now with diagonal loaded matrix
512  *curvature = save_curvature;
513  for (int ipar = 0; ipar < npars; ++ipar) {
514  if ((*curvature)(ipar,ipar) != 0.0) {
515  (*curvature)(ipar,ipar) += 1.0e-10;
516  }
517  }
518 
519  // Signal loading
520  diag_loaded = true;
521 
522  // Try again
523  continue;
524 
525  } // endif: diagonal has not yet been loaded
526 
527  // ... otherwise signal an error
528  else {
530  if (m_logger != NULL) {
531  *m_logger << "Non-Positive definite curvature matrix encountered,"
532  << " even after diagonal loading." << std::endl;
533  }
534  break;
535  }
536  }
537  catch (std::exception &e) {
538  throw;
539  }
540 
541  // If no error occured then break now
542  break;
543 
544  } // endfor: looped over error computation
545 
546  // Return
547  return;
548 }
549 
550 
551 /***********************************************************************//**
552  * @brief Set fit status string
553  *
554  * @return Fit status string.
555  ***************************************************************************/
556 std::string GOptimizerLM::status_string(void) const
557 {
558  // Initialise status string
559  std::string status;
560 
561  // Set status string
562  switch (m_status) {
563  case G_LM_CONVERGED:
564  status = "converged";
565  break;
566  case G_LM_STALLED:
567  status = "stalled";
568  break;
569  case G_LM_SINGULAR:
570  status = "singular curvature matrix encountered";
571  break;
573  status = "curvature matrix not positive definite";
574  break;
575  case G_LM_BAD_ERRORS:
576  status = "errors are inaccurate";
577  break;
578  default:
579  status = "unknown";
580  break;
581  }
582 
583  // Return status
584  return status;
585 }
586 
587 
588 /***********************************************************************//**
589  * @brief Print optimizer information
590  *
591  * @param[in] chatter Chattiness.
592  * @return String containing optimizer information.
593  ***************************************************************************/
594 std::string GOptimizerLM::print(const GChatter& chatter) const
595 {
596  // Initialise result string
597  std::string result;
598 
599  // Continue only if chatter is not silent
600  if (chatter != SILENT) {
601 
602  // Append header
603  result.append("=== GOptimizerLM ===");
604 
605  // Append information
606  result.append("\n"+gammalib::parformat("Optimized function value"));
607  result.append(gammalib::str(m_value, 3));
608  result.append("\n"+gammalib::parformat("Absolute precision"));
609  result.append(gammalib::str(m_eps));
610  result.append("\n"+gammalib::parformat("Acceptable value decrease"));
611  result.append(gammalib::str(std::abs(m_accept_dec)));
612 
613  // Append status
614  result.append("\n"+gammalib::parformat("Optimization status"));
615  result.append(status_string());
616 
617  // Append further information
618  result.append("\n"+gammalib::parformat("Number of parameters"));
619  result.append(gammalib::str(m_npars));
620  result.append("\n"+gammalib::parformat("Number of free parameters"));
621  result.append(gammalib::str(m_nfree));
622  result.append("\n"+gammalib::parformat("Number of iterations"));
623  result.append(gammalib::str(m_iter));
624  result.append("\n"+gammalib::parformat("Lambda"));
625  result.append(gammalib::str(m_lambda));
626 
627  } // endif: chatter was not silent
628 
629  // Return result
630  return result;
631 }
632 
633 
634 /*==========================================================================
635  = =
636  = Private methods =
637  = =
638  ==========================================================================*/
639 
640 /***********************************************************************//**
641  * @brief Initialise class members
642  ***************************************************************************/
644 {
645  // Initialise optimizer parameters
646  m_npars = 0;
647  m_nfree = 0;
648  m_lambda_start = 1.0e-3;
649  m_lambda_inc = 10.0;
650  m_lambda_dec = 0.1;
651  m_eps = 5.0e-3; //!< Changed on 30/10/2014 from 1.0e-6
652  m_accept_dec = 0.0; //!< Do not allow to decrease
653  m_max_iter = 100; //!< Changed on 30/10/2014 from 1000
654  m_max_stall = 10;
655  m_max_hit = 3; //!< Maximum successive boundary hits before freeze
656  m_max_dec = 3; //!< Maximum number of function decreases
657  m_step_adjust = true;
658 
659  // Initialise bookkeeping arrays
660  m_hit_boundary.clear();
661  m_hit_minimum.clear();
662  m_hit_maximum.clear();
663  m_par_freeze.clear();
664  m_par_remove.clear();
665 
666  // Initialise optimizer values
668  m_value = 0.0;
669  m_delta = 0.0;
670  m_status = 0;
671  m_iter = 0;
672  m_num_dec = 0;
673 
674  // Initialise pointer to logger
675  m_logger = NULL;
676 
677  // Return
678  return;
679 }
680 
681 
682 /***********************************************************************//**
683  * @brief Copy class members
684  *
685  * @param[in] opt GOptimizerLM members to be copied.
686  ***************************************************************************/
688 {
689  // Copy attributes
690  m_npars = opt.m_npars;
691  m_nfree = opt.m_nfree;
695  m_eps = opt.m_eps;
697  m_max_iter = opt.m_max_iter;
698  m_max_stall = opt.m_max_stall;
699  m_max_dec = opt.m_max_dec;
706  m_lambda = opt.m_lambda;
707  m_value = opt.m_value;
708  m_delta = opt.m_delta;
709  m_status = opt.m_status;
710  m_iter = opt.m_iter;
711  m_num_dec = opt.m_num_dec;
712  m_logger = opt.m_logger;
713 
714  // Return
715  return;
716 }
717 
718 
719 /***********************************************************************//**
720  * @brief Delete class members
721  ***************************************************************************/
723 {
724  // Return
725  return;
726 }
727 
728 
729 /***********************************************************************//**
730  * @brief Perform one LM iteration
731  *
732  * @param[in] fct Optimizer function.
733  * @param[in] pars Function parameters.
734  * @return Step size for iteration.
735  *
736  * This method performs one LM iteration. Note that the method only acts on
737  * the parameter value, i.e. it does not worry about the true scale of the
738  * parameter. It calls the eval() method of the optimizer function which
739  * is assumed to return the gradient and curvature matrix with respect to the
740  * parameter values (and not the scaled true values).
741  ***************************************************************************/
743 {
744  // Debug option: dump header
745  #if defined(G_DEBUG_ITER)
746  std::cout << "GOptimizerLM::iteration: enter" << std::endl;
747  #endif
748 
749  // Initialise step size
750  double step = 0.0;
751 
752  // Single loop for common exit point
753  do {
754 
755  // Initialise iteration parameters
756  GVector* grad = fct.gradient();
757  GMatrixSparse* curvature = fct.curvature();
758 
759  // Save function value, gradient and curvature matrix
760  double save_value = m_value;
761  GVector save_grad = GVector(*grad);
762  GMatrixSparse save_curvature = GMatrixSparse(*curvature);
763 
764  // Save parameter values in vector
765  GVector save_pars(m_npars);
766  for (int ipar = 0; ipar < m_npars; ++ipar) {
767  save_pars[ipar] = pars[ipar]->factor_value();
768  }
769 
770  // Setup matrix and vector for curvature computation
771  for (int ipar = 0; ipar < m_npars; ++ipar) {
772  (*curvature)(ipar,ipar) *= (1.0 + m_lambda);
773  (*grad)[ipar] = -(*grad)[ipar];
774  }
775 
776  // Debug option: dump gradient and curvature matrix
777  #if defined(G_DEBUG_ITER)
778  std::cout << "Gradient : ";
779  for (int ipar = 0; ipar < m_npars; ++ipar) {
780  std::cout << (*grad)[ipar] << " ";
781  }
782  std::cout << std::endl;
783  std::cout << "Curvature: ";
784  for (int ipar = 0; ipar < m_npars; ++ipar) {
785  for (int jpar = 0; jpar < m_npars; ++jpar) {
786  std::cout << (*curvature)(ipar,jpar) << " ";
787  }
788  }
789  std::cout << std::endl;
790  #endif
791 
792  // Solve: curvature * X = grad. Handle matrix problems
793  try {
794  *grad = curvature->solve(*grad);
795  }
796  catch (GException::runtime_error &e) {
798  if (m_logger != NULL) {
799  *m_logger << "GOptimizerLM::iteration: "
800  << "Curvature matrix not positive definite."
801  << std::endl;
802  }
803  continue;
804  }
805  catch (std::exception &e) {
806  throw;
807  }
808 
809  // Get LM step size
810  step = step_size(*grad, pars);
811 
812  // Debug option: dump step size
813  #if defined(G_DEBUG_ITER)
814  std::cout << "Step size = " << step << std::endl;
815  #endif
816 
817  // Derive new parameter vector
818  for (int ipar = 0; ipar < m_npars; ++ipar) {
819 
820  // Consider only free parameters
821  if (pars[ipar]->is_free()) {
822 
823  // Get actual parameter value and limits
824  double p = pars[ipar]->factor_value();
825  double p_min = pars[ipar]->factor_min();
826  double p_max = pars[ipar]->factor_max();
827 
828  // Compute new parameter value
829  p += (*grad)[ipar] * step;
830 
831  // Debug option: dump new parameter value
832  #if defined(G_DEBUG_ITER)
833  std::cout << "Trial factor value of ";
834  std::cout << pars[ipar]->name() << " = ";
835  std::cout << p << std::endl;
836  #endif
837 
838  // Constrain parameter to within the valid range
839  if (pars[ipar]->has_min() && p < p_min) {
840  if (m_hit_minimum[ipar] >= m_max_hit) {
841  if (m_logger != NULL) {
842  *m_logger << " Parameter \"" << pars[ipar]->name();
843  *m_logger << "\" hits minimum " << p_min << " more than ";
844  *m_logger << m_max_hit << " times.";
845  *m_logger << " Fix parameter at minimum for now." << std::endl;
846  }
847  m_par_freeze[ipar] = true;
848  pars[ipar]->fix();
849  }
850  else {
851  if (m_logger != NULL) {
852  *m_logger << " Parameter \"" << pars[ipar]->name();
853  *m_logger << "\" hits minimum: ";
854  *m_logger << p << " < " << p_min;
855  *m_logger << " (" << m_hit_minimum[ipar]+1 << ")" << std::endl;
856  }
857  }
858  m_hit_minimum[ipar]++;
859  p = p_min;
860  }
861  else if (pars[ipar]->has_max() && p > p_max) {
862  if (m_hit_maximum[ipar] >= m_max_hit) {
863  if (m_logger != NULL) {
864  *m_logger << " Parameter \"" << pars[ipar]->name();
865  *m_logger << "\" hits maximum " << p_max << " more than ";
866  *m_logger << m_max_hit << " times.";
867  *m_logger << " Fix parameter at maximum for now." << std::endl;
868  }
869  m_par_freeze[ipar] = true;
870  pars[ipar]->fix();
871  }
872  else {
873  if (m_logger != NULL) {
874  *m_logger << " Parameter \"" << pars[ipar]->name();
875  *m_logger << "\" hits maximum: ";
876  *m_logger << p << " > " << p_max;
877  *m_logger << " (" << m_hit_maximum[ipar]+1 << ")" << std::endl;
878  }
879  }
880  m_hit_maximum[ipar]++;
881  p = p_max;
882  }
883  else {
884  m_hit_minimum[ipar] = 0;
885  m_hit_maximum[ipar] = 0;
886  }
887 
888  // Set new parameter value
889  pars[ipar]->factor_value(p);
890 
891  // Debug option: dump new parameter value
892  #if defined(G_DEBUG_ITER)
893  std::cout << "New value of ";
894  std::cout << pars[ipar]->name() << " = ";
895  std::cout << pars[ipar]->value() << std::endl;
896  #endif
897 
898  } // endif: Parameter was free
899 
900  } // endfor: computed new parameter vector
901 
902  // Evaluate function at new parameters
903  fct.eval(pars);
904 
905  // If a free parameter has a zero diagonal element in the curvature
906  // matrix then remove this parameter definitely from the fit as it
907  // otherwise will block the fit. The problem appears in the unbinned
908  // fitting where parameter gradients may be zero (due to the truncation
909  // of the PSF), but the Npred gradient is not zero. In principle we
910  // could use the Npred gradient for fitting (I guess), but I still
911  // have to figure out how ... (the diagonal loading was not so
912  // successful as it faked early convergence)
913  for (int ipar = 0; ipar < m_npars; ++ipar) {
914  if (pars[ipar]->is_free()) {
915  if ((*fct.curvature())(ipar,ipar) == 0.0) {
916  if (m_logger != NULL) {
917  *m_logger << " Parameter \"" << pars[ipar]->name();
918  *m_logger << "\" has zero curvature.";
919  *m_logger << " Fix parameter." << std::endl;
920  }
921  m_par_remove[ipar] = true;
922  pars[ipar]->fix();
923  }
924  }
925  }
926 
927  // Fetch new pointers since eval will allocate new memory
928  grad = fct.gradient();
929  curvature = fct.curvature();
930 
931  // Retrieve new function value
932  m_value = fct.value();
933 
934  // Debug option: dump new function value
935  #if defined(G_DEBUG_ITER)
936  std::cout << "Function value " << m_value;
937  std::cout << " (was " << save_value << ")" << std::endl;
938  #endif
939 
940  // Determine how many parameters have changed
941  int par_change = 0;
942  for (int ipar = 0; ipar < m_npars; ++ipar) {
943  if (pars[ipar]->factor_value() != save_pars[ipar]) {
944  par_change++;
945  }
946  }
947 
948  // If the function has decreased then accept the new solution
949  // and decrease lambda ...
950  m_delta = save_value - m_value;
951  if (m_delta > 0.0) {
953  }
954 
955  // ... if function is identical then accept new solution. If the
956  // parameters have changed then increase lambda, otherwise decrease
957  // lambda
958  else if (m_delta == 0.0) {
959  if (par_change) {
961  }
962  else {
964  }
965  }
966 
967  // ... if function worsened by an amount smaller than m_accept_dec
968  // then accept new solution and increase lambda for the next
969  // iteration. This kluge which may help to overcome some local
970  // minimum will only for m_max_dec times at maximum to avoid
971  // keeping oscillating in a loop.
972  else if ((m_num_dec < m_max_dec) && (m_delta >= -std::abs(m_accept_dec))) {
974  m_num_dec++;
975  }
976 
977  // ... otherwise, if the statistics did not improve then use old
978  // parameters and increase lamdba. Restore also the best statistics
979  // value that was reached so far, the gradient vector and the curve
980  // matrix.
981  else {
983  m_value = save_value;
984  *grad = save_grad;
985  *curvature = save_curvature;
986  for (int ipar = 0; ipar < m_npars; ++ipar) {
987  pars[ipar]->factor_value(save_pars[ipar]);
988  }
989  }
990 
991  } while (0); // endwhile: main loop
992 
993  // Debug option: dump trailer
994  #if defined(G_DEBUG_ITER)
995  std::cout << "GOptimizerLM::iteration: exit" << std::endl;
996  #endif
997 
998  // Return step size
999  return step;
1000 
1001 }
1002 
1003 
1004 /***********************************************************************//**
1005  * @brief Return LM step size
1006  *
1007  * @param[in] grad Function gradient.
1008  * @param[in] pars Function parameters.
1009  *
1010  * Determine the size of the LM step. By default a step size of 1 is taken.
1011  * If m_step_adjust=true then the step size will be estimated so that the
1012  * next parameter vector should stay within the parameter boundaries
1013  * (provided that boundaries exist).
1014  ***************************************************************************/
1015 double GOptimizerLM::step_size(const GVector& grad, const GOptimizerPars& pars)
1016 {
1017  // Initialise step size
1018  double step = 1.0;
1019 
1020  // Check if we should reduce the step size
1021  if (m_step_adjust) {
1022 
1023  // Initialise the parameter index that constrains most the fit
1024  int ipar_bnd = -1;
1025 
1026  // Loop over all parameters
1027  for (int ipar = 0; ipar < pars.size(); ++ipar) {
1028 
1029  // Get parameter attributes
1030  double p = pars[ipar]->factor_value();
1031  double p_min = pars[ipar]->factor_min();
1032  double p_max = pars[ipar]->factor_max();
1033  double delta = grad[ipar];
1034 
1035  // Check if a parameter minimum requires a reduced step size
1036  if (pars[ipar]->has_min()) {
1037  double step_min = (delta < 0.0) ? (p_min - p)/delta : 1.0;
1038  if (step_min > 0.0) {
1039  if (step_min < step) {
1040  if (!m_hit_boundary[ipar]) {
1041  ipar_bnd = ipar;
1042  step = step_min;
1043  }
1044  }
1045  else if (m_hit_boundary[ipar]) {
1046  m_hit_boundary[ipar] = false;
1047  if (m_logger != NULL) {
1048  *m_logger << " Parameter \"";
1049  *m_logger << pars[ipar]->name();
1050  *m_logger << "\" does not drive optimization step anymore.";
1051  *m_logger << std::endl;
1052  }
1053  }
1054  }
1055  }
1056 
1057  // Check if a parameter maximum requires a reduced step size
1058  if (pars[ipar]->has_max()) {
1059  double step_max = (delta > 0.0) ? (p_max - p)/delta : 1.0;
1060  if (step_max > 0.0) {
1061  if (step_max < step) {
1062  if (!m_hit_boundary[ipar]) {
1063  ipar_bnd = ipar;
1064  step = step_max;
1065  }
1066  }
1067  else if (m_hit_boundary[ipar]) {
1068  m_hit_boundary[ipar] = false;
1069  if (m_logger != NULL) {
1070  *m_logger << " Parameter \"";
1071  *m_logger << pars[ipar]->name();
1072  *m_logger << "\" does not drive optimization step anymore.";
1073  *m_logger << std::endl;
1074  }
1075  }
1076  }
1077  }
1078 
1079  } // endfor: looped over all parameters
1080 
1081  // Signal if a parameter is driving the optimization step
1082  if (ipar_bnd != -1) {
1083  m_hit_boundary[ipar_bnd] = true;
1084  if (m_logger != NULL) {
1085  *m_logger << " Parameter \"";
1086  *m_logger << pars[ipar_bnd]->name();
1087  *m_logger << "\" drives optimization step (step=";
1088  *m_logger << step << ")" << std::endl;
1089  }
1090  }
1091 
1092  } // endif: automatic step size adjustment requested
1093 
1094  // Return step size
1095  return step;
1096 }
#define G_LM_SINGULAR
GOptimizerLM & operator=(const GOptimizerLM &opt)
Assignment operator.
const double & lambda_inc(void) const
Return lambda increment value.
double step_size(const GVector &grad, const GOptimizerPars &pars)
Return LM step size.
Optimizer function abstract base class.
void init_members(void)
Initialise class members.
Sparse matrix class interface definition.
#define G_LM_STALLED
GVector abs(const GVector &vector)
Computes absolute of vector elements.
Definition: GVector.cpp:1253
virtual GMatrixSparse * curvature(void)=0
Optimizer parameter container class.
virtual void optimize(GOptimizerFunction &fct, GOptimizerPars &pars)
Optimize function parameters.
double m_lambda_start
Initial start value.
double m_lambda
Actual lambda.
void copy_members(const GOptimizerLM &opt)
Copy class members.
int m_iter
Iteration.
double m_eps
Absolute precision.
std::vector< bool > m_hit_boundary
Bookkeeping array for boundary hits.
virtual void eval(const GOptimizerPars &pars)=0
int m_npars
Number of parameters.
Gammalib tools definition.
#define G_LM_CONVERGED
GOptimizerLM(void)
Void constructor.
virtual GOptimizer & operator=(const GOptimizer &opt)
Assignment operator.
Definition: GOptimizer.cpp:101
std::vector< bool > m_par_remove
Bookkeeping of parameter removal.
int m_max_dec
Maximum number of function decrease.
#define G_LM_BAD_ERRORS
double m_value
Actual function value.
GLog * m_logger
Pointer to optional logger.
Information logger interface definition.
Definition: GLog.hpp:62
virtual GOptimizerLM * clone(void) const
Clone object.
int m_nfree
Number of free parameters.
GVector sqrt(const GVector &vector)
Computes square root of vector elements.
Definition: GVector.cpp:1358
GVector solve(const GVector &vector) const
Solves linear matrix equation.
bool m_step_adjust
Adjust step size to boundaries.
int m_status
Fit status.
virtual void clear(void)
Clear object.
double m_delta
Function improvement.
double m_accept_dec
Acceptable function decrease.
GVector log(const GVector &vector)
Computes natural logarithm of vector elements.
Definition: GVector.cpp:1274
#define G_LM_NOT_POSTIVE_DEFINITE
virtual ~GOptimizerLM(void)
Destructor.
double m_lambda_dec
Lambda decrease.
Abstract optimizer abstract base class.
Definition: GOptimizer.hpp:54
std::vector< bool > m_par_freeze
Bookkeeping of parameter freeze.
GChatter
Definition: GTypemaps.hpp:33
double m_lambda_inc
Lambda increase.
Levenberg Marquardt optimizer class interface definition.
int m_num_dec
Number of function decreases.
void free_members(void)
Delete class members.
const int & npars(void) const
Return number of model parameters.
int m_max_iter
Maximum number of iterations.
int m_max_hit
Maximum number of successive hits.
std::vector< int > m_hit_maximum
Bookkeeping of successive maximum hits.
void init_members(void)
Initialise class members.
Definition: GOptimizer.cpp:137
virtual GVector * gradient(void)=0
GVector cholesky_solver(const GVector &vector, const bool &compress=true) const
Cholesky solver.
int nfree(void) const
Return number of free parameters.
virtual double value(void) const =0
virtual void errors(GOptimizerFunction &fct, GOptimizerPars &pars)
Compute parameter uncertainties.
virtual std::string print(const GChatter &chatter=NORMAL) const
Print optimizer information.
Exception handler interface definition.
Vector class.
Definition: GVector.hpp:46
int size(void) const
Return number of parameters in container.
std::string parformat(const std::string &s, const int &indent=0)
Convert string in parameter format.
Definition: GTools.cpp:1143
std::vector< int > m_hit_minimum
Bookkeeping of successive minimum hits.
GMatrixSparse cholesky_decompose(const bool &compress=true) const
Return Cholesky decomposition.
void free_members(void)
Delete class members.
Definition: GOptimizer.cpp:159
Levenberg Marquardt optimizer class.
int m_max_stall
Maximum number of stalls.
virtual int status(void) const
Return optimizer status.
std::string status_string(void) const
Set fit status string.
double iteration(GOptimizerFunction &fct, GOptimizerPars &pars)
Perform one LM iteration.
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition: GTools.cpp:489