ctools  2.0.0
 All Classes Namespaces Files Functions Variables Macros Pages
cterror.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * cterror - Parameter error calculation tool *
3  * ----------------------------------------------------------------------- *
4  * copyright (C) 2015-2022 by Florent Forest *
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 cterror.cpp
23  * @brief Parameter error calculation tool interface implementation
24  * @author Florent Forest
25  */
26 
27 /* __ Includes ___________________________________________________________ */
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 #include <iostream>
32 #include "cterror.hpp"
33 #include "GTools.hpp"
34 #include "GOptimizer.hpp"
35 
36 /* __ OpenMP section _____________________________________________________ */
37 #ifdef _OPENMP
38 #include <omp.h>
39 #endif
40 
41 /* __ Method name definitions ____________________________________________ */
42 #define G_ERR_BISECTION "cterror::error_bisection(double&, double&)"
43 
44 /* __ Debug definitions __________________________________________________ */
45 
46 /* __ Coding definitions _________________________________________________ */
47 
48 
49 /*==========================================================================
50  = =
51  = Constructors/destructors =
52  = =
53  ==========================================================================*/
54 
55 /***********************************************************************//**
56  * @brief Void constructor
57  *
58  * Constructs an empty cterror tool.
59  ***************************************************************************/
61 {
62  // Initialise members
63  init_members();
64 
65  // Return
66  return;
67 }
68 
69 
70 /***********************************************************************//**
71  * @brief Observations constructor
72  *
73  * param[in] obs Observation container.
74  *
75  * Constructs cterror tool from an observations container.
76  ***************************************************************************/
77 cterror::cterror(const GObservations& obs) :
78  ctlikelihood(CTERROR_NAME, VERSION, obs)
79 {
80  // Initialise members
81  init_members();
82 
83  // Return
84  return;
85 }
86 
87 
88 /***********************************************************************//**
89  * @brief Command line constructor
90  *
91  * @param[in] argc Number of arguments in command line.
92  * @param[in] argv Array of command line arguments.
93  *
94  * Constructs an instance of the cterror tool that will parse user
95  * parameters that are provided as command line arguments.
96  ***************************************************************************/
97 cterror::cterror(int argc, char *argv[]) :
98  ctlikelihood(CTERROR_NAME, VERSION, argc, argv)
99 {
100  // Initialise members
101  init_members();
102 
103  // Return
104  return;
105 }
106 
107 
108 /***********************************************************************//**
109  * @brief Copy constructor
110  *
111  * @param[in] app Application.
112  *
113  * Constructs an instance of the cterror tool by copying information from
114  * another ctulimit tool.
115  ***************************************************************************/
117 {
118  // Initialise members
119  init_members();
120 
121  // Copy members
122  copy_members(app);
123 
124  // Return
125  return;
126 }
127 
128 
129 /***********************************************************************//**
130  * @brief Destructor
131  *
132  * Destructs the cterror tool.
133  ***************************************************************************/
135 {
136  // Free members
137  free_members();
138 
139  // Return
140  return;
141 }
142 
143 
144 /*==========================================================================
145  = =
146  = Operators =
147  = =
148  ==========================================================================*/
149 
150 /***********************************************************************//**
151  * @brief Assignment operator
152  *
153  * @param[in] app Application.
154  * @return Application.
155  *
156  * Assigns a cterror tool.
157  ***************************************************************************/
159 {
160  // Execute only if object is not identical
161  if (this != &app) {
162 
163  // Copy base class members
164  this->ctlikelihood::operator=(app);
165 
166  // Free members
167  free_members();
168 
169  // Initialise members
170  init_members();
171 
172  // Copy members
173  copy_members(app);
174 
175  } // endif: object was not identical
176 
177  // Return this object
178  return *this;
179 }
180 
181 
182 /*==========================================================================
183  = =
184  = Public methods =
185  = =
186  ==========================================================================*/
187 
188 /***********************************************************************//**
189  * @brief Clear cterror tool
190  *
191  * Clears cterror tool.
192  ***************************************************************************/
193 void cterror::clear(void)
194 {
195  // Free members
196  free_members();
199  this->ctool::free_members();
200 
201  // Clear base class (needed to conserve tool name and version)
202  this->GApplication::clear();
203 
204  // Initialise members
205  this->ctool::init_members();
208  init_members();
209 
210  // Write header into logger
211  log_header();
212 
213  // Return
214  return;
215 }
216 
217 
218 /***********************************************************************//**
219  * @brief Compute parameter errors using a likelihood profile method
220  *
221  * Computes the parameter errors using a likelihood profile method.
222  ***************************************************************************/
224 {
225  // Get task parameters
226  get_parameters();
227 
228  // Set energy dispersion flags of all CTA observations and save old
229  // values in save_edisp vector
230  std::vector<bool> save_edisp = set_edisp(m_obs, m_apply_edisp);
231 
232  // Write input observation container into logger
233  log_observations(NORMAL, m_obs, "Input observation");
234 
235  // Write header into logger
236  log_header1(TERSE, "Compute best-fit likelihood");
237 
238  // Optimize and save best log-likelihood
239  m_obs.optimize(m_opt);
240  m_obs.errors(m_opt);
241  m_best_logL = m_obs.logL();
242 
243  // Store optimizer for later recovery
244  GOptimizerLM best_opt = m_opt;
245 
246  // Write optimisation results and models into logger
247  log_string(NORMAL, m_opt.print(m_chatter));
248  log_value(NORMAL, "Maximum log likelihood", gammalib::str(m_best_logL,3));
249  log_string(NORMAL, m_obs.models().print(m_chatter));
250 
251  // Save best fitting models
252  GModels models_best = m_obs.models();
253 
254  // Get pointer on model
255  GModel* model = models_best[m_srcname];
256 
257  // Get number of parameters
258  int npars = model->size();
259 
260  // Loop over parameters of sky model
261  for (int i = 0; i < npars; ++i) {
262 
263  // Skip parameter if it is fixed
264  if (model->at(i).is_fixed()) {
265  continue;
266  }
267 
268  // Initialise with best fitting models
269  m_obs.models(models_best);
270 
271  // Get pointer on model parameter
272  GModels& current_models = const_cast<GModels&>(m_obs.models());
273  m_model_par = &(current_models[m_srcname]->at(i));
274 
275  // Extract current value
276  m_value = m_model_par->factor_value();
277 
278  // Compute parameter bracketing
279  double parmin = std::max(m_model_par->factor_min(),
280  m_value - 10.0*m_model_par->factor_error());
281  double parmax = std::min(m_model_par->factor_max(),
282  m_value + 10.0*m_model_par->factor_error());
283 
284  // Write header and initial parameters into logger
285  log_header1(TERSE, "Compute error for source \""+m_srcname+"\""
286  " parameter \""+m_model_par->name()+"\"");
287  log_value(NORMAL, "Confidence level",
288  gammalib::str(m_confidence*100.0)+" %");
289  log_value(NORMAL, "Log-likelihood difference", m_dlogL);
290  log_value(NORMAL, "Initial factor range",
291  "["+gammalib::str(parmin)+", "+gammalib::str(parmax)+"]");
292 
293  // Compute lower and upper boundaries
294  double value_lo = error_bisection(parmin, m_value);
295  double value_hi = error_bisection(m_value, parmax);
296 
297  // Compute errors
298  double error = 0.5 * (value_hi - value_lo);
299  double error_neg = m_value - value_lo;
300  double error_pos = value_hi - m_value;
301  double error_value = std::abs(error*m_model_par->scale());
302  double error_value_neg = std::abs(error_neg*m_model_par->scale());
303  double error_value_pos = std::abs(error_pos*m_model_par->scale());
304 
305  // Write results into logger
306  std::string unit = " " + m_model_par->unit();
307  log_value(NORMAL, "Lower parameter factor", value_lo);
308  log_value(NORMAL, "Upper parameter factor", value_hi);
309  log_value(NORMAL, "Error from curvature",
310  gammalib::str(m_model_par->error()) + unit);
311  log_value(NORMAL, "Error from profile",
312  gammalib::str(error_value) + unit);
313  log_value(NORMAL, "Negative profile error",
314  gammalib::str(error_value_neg) + unit);
315  log_value(NORMAL, "Positive profile error",
316  gammalib::str(error_value_pos) + unit);
317 
318  // Save error result
319  model->at(i).factor_error(error);
320 
321  } // endfor: looped over spectral parameters
322 
323  // Restore best fitting models (now with new errors computed)
324  m_obs.models(models_best);
325 
326  // Recover optimizer
327  m_opt = best_opt;
328 
329  // Restore energy dispersion flags of all CTA observations
330  restore_edisp(m_obs, save_edisp);
331 
332  // Return
333  return;
334 }
335 
336 
337 /***********************************************************************//**
338  * @brief Save model
339  *
340  * Saves the model into an XML file.
341  ***************************************************************************/
342 void cterror::save(void)
343 {
344  // Write header
345  log_header1(TERSE, "Save results");
346 
347  // Save only if filename is valid
348  if ((*this)["outmodel"].is_valid()) {
349 
350  // Get output filename
351  m_outmodel = (*this)["outmodel"].filename();
352 
353  // Log filename
354  log_value(NORMAL, "Model definition file", m_outmodel.url());
355 
356  // Write results out as XML model
357  m_obs.models().save(m_outmodel.url());
358 
359  }
360 
361  // ... otherwise signal that file was not saved
362  else {
363  log_value(NORMAL, "Model definition file", "NONE");
364  }
365 
366  // Return
367  return;
368 }
369 
370 
371 /*==========================================================================
372  = =
373  = Private methods =
374  = =
375  ==========================================================================*/
376 
377 /***********************************************************************//**
378  * @brief Initialise class members
379  ***************************************************************************/
381 {
382  // Initialise user parameters
383  m_srcname.clear();
384  m_outmodel.clear();
385  m_confidence = 0.68;
386  m_tol = 1.0e-3;
387  m_max_iter = 50;
388  m_apply_edisp = false;
389  m_chatter = static_cast<GChatter>(2);
390 
391  // Initialise protected members
392  m_value = 0.0;
393  m_dlogL = 0.0;
394  m_best_logL = 0.0;
395  m_model_par = NULL;
396 
397  // Return
398  return;
399 }
400 
401 
402 /***********************************************************************//**
403  * @brief Copy class members
404  *
405  * @param[in] app Application.
406  ***************************************************************************/
408 {
409  // Copy user parameters
410  m_srcname = app.m_srcname;
411  m_outmodel = app.m_outmodel;
413  m_tol = app.m_tol;
414  m_max_iter = app.m_max_iter;
416  m_chatter = app.m_chatter;
417 
418  // Copy protected members
419  m_value = app.m_value;
420  m_dlogL = app.m_dlogL;
421  m_best_logL = app.m_best_logL;
422  m_model_par = NULL;
423 
424  // Return
425  return;
426 }
427 
428 
429 /***********************************************************************//**
430  * @brief Delete class members
431  ***************************************************************************/
433 {
434  // Return
435  return;
436 }
437 
438 
439 /***********************************************************************//**
440  * @brief Get application parameters
441  *
442  * Get all task parameters from parameter file.
443  ***************************************************************************/
445 {
446  // Setup observations from "inobs" parameter
448 
449  // Set observation statistic
450  set_obs_statistic(gammalib::toupper((*this)["statistic"].string()));
451 
452  // Setup models from "inmodel" parameter
453  setup_models(m_obs, (*this)["srcname"].string());
454 
455  // Get name of test source
456  m_srcname = (*this)["srcname"].string();
457 
458  // Read energy dispersion flag
459  m_apply_edisp = (*this)["edisp"].boolean();
460 
461  // Get confidence level and transform into log-likelihood difference
462  m_confidence = (*this)["confidence"].real();
463  double sigma = gammalib::erfinv(m_confidence) * gammalib::sqrt_two;
464  m_dlogL = (sigma*sigma) / 2.0;
465 
466  // Set optimizer characteristics from user parameters
467  m_opt.eps((*this)["like_accuracy"].real());
468  m_opt.max_iter((*this)["max_iter"].integer());
469 
470  // Read other parameters
471  m_tol = (*this)["tol"].real();
472  m_max_iter = (*this)["max_iter"].integer();
473  m_chatter = static_cast<GChatter>((*this)["chatter"].integer());
474 
475  // If needed later, query output filename now
476  if (read_ahead()) {
477  (*this)["outmodel"].query();
478  }
479 
480  // Set number of OpenMP threads
481  #ifdef _OPENMP
482  int nthreads = (*this)["nthreads"].integer();
483  if (nthreads > 0) {
484  omp_set_num_threads(nthreads);
485  }
486  #endif
487 
488  // Write parameters into logger
489  log_parameters(TERSE);
490 
491  // Return
492  return;
493 }
494 
495 
496 /***********************************************************************//**
497  * @brief Calculate error using a bisection method
498  *
499  * @param[in] min Minimum parameter value
500  * @param[in] max Maximum parameter value
501  *
502  * Calculates the error using a bisection method.
503  ***************************************************************************/
504 double cterror::error_bisection(const double& min, const double& max)
505 {
506  // Copy values to working values
507  double wrk_min = min;
508  double wrk_max = max;
509 
510  // Initialise iteration counter
511  int iter = 1;
512 
513  // Initialize mid value
514  double mid = (wrk_min + wrk_max) / 2.0;
515 
516  // Loop until breaking condition is reached
517  while (true) {
518 
519  // Throw exception if maximum iterations are reached
520  if (iter > m_max_iter) {
521  if (wrk_min - m_model_par->factor_min() < m_tol) {
522  std::string msg = "The \""+m_model_par->name()+"\" parameter "
523  "minimum has been reached during error "
524  "calculation. To obtain accurate errors, "
525  "consider setting the minimum parameter "
526  "value to a lower value, and re-run "
527  "cterror.";
528  log_string(TERSE, msg);
529  break;
530  }
531  else if (m_model_par->factor_max() - wrk_max < m_tol) {
532  std::string msg = "The \""+m_model_par->name()+"\" parameter "
533  "maximum has been reached during error "
534  "calculation. To obtain accurate errors, "
535  "consider setting the maximum parameter "
536  "value to a higher value, and re-run "
537  "cterror.";
538  log_string(TERSE, msg);
539  break;
540  }
541  else {
542  std::string msg = "The maximum number of "+
543  gammalib::str(m_max_iter)+" iterations has "
544  "been reached. Please increase the "
545  "\"max_iter\" parameter, and re-run "
546  "cterror.";
547  throw GException::invalid_value(G_ERR_BISECTION, msg);
548  }
549  }
550 
551  // Compute center of boundary
552  mid = (wrk_min + wrk_max) / 2.0;
553 
554  // Calculate function value
555  double eval_mid = evaluate(*m_model_par, mid) - (m_best_logL + m_dlogL);
556 
557  // Write interval into logger
558  log_value(EXPLICIT, " Iteration "+gammalib::str(iter),
559  "["+gammalib::str(wrk_min)+", "+gammalib::str(wrk_max)+"]");
560 
561  // Check for convergence inside tolerance
562  if (std::abs(eval_mid) < m_tol) {
563  break;
564  }
565 
566  // Check if interval is smaller than 1.0e-6
567  if (std::abs(wrk_max-wrk_min) < 1.0e-6) {
568  break;
569  }
570 
571  // If we are on the crescent side of the parabola ...
572  if (mid > m_value) {
573 
574  // Change boundaries for further iteration
575  if (eval_mid > 0.0) {
576  wrk_max = mid;
577  }
578  else if (eval_mid < 0.0) {
579  wrk_min = mid;
580  }
581  }
582 
583  // ... otherwise we are on the decrescent side of the parabola
584  else {
585 
586  // Change boundaries for further iteration
587  if (eval_mid > 0.0) {
588  wrk_min = mid;
589  }
590  else if (eval_mid < 0.0) {
591  wrk_max = mid;
592  }
593  }
594 
595  // Increment counter
596  iter++;
597 
598  } // endwhile
599 
600  // Return mid value
601  return mid;
602 
603 }
void save(void)
Save model.
Definition: cterror.cpp:342
void setup_observations(GObservations &obs, const bool &response=true, const bool &list=true, const bool &cube=true)
Setup observation container.
Definition: ctool.cpp:431
Parameter error calculation tool.
Definition: cterror.hpp:42
void free_members(void)
Delete class members.
Definition: cterror.cpp:432
void setup_models(GObservations &obs, const std::string &name="")
Setup model container.
Definition: ctool.cpp:545
double m_tol
Tolerance for limit determination.
Definition: cterror.hpp:72
ctlikelihood & operator=(const ctlikelihood &app)
Assignment operator.
void get_parameters(void)
Get application parameters.
Definition: cterror.cpp:444
double evaluate(GModelPar &par, const double &value)
Evaluates the log-likelihood function.
GChatter m_chatter
Chattiness.
Definition: cterror.hpp:75
#define CTERROR_NAME
Definition: cterror.hpp:34
void init_members(void)
Initialise class members.
Definition: ctool.cpp:321
double m_dlogL
Likelihood difference for upper limit computation.
Definition: cterror.hpp:79
double error_bisection(const double &min, const double &max)
Calculate error using a bisection method.
Definition: cterror.cpp:504
void init_members(void)
Initialise class members.
Definition: cterror.cpp:380
virtual ~cterror(void)
Destructor.
Definition: cterror.cpp:134
void set_obs_statistic(const std::string &statistic)
Set fit statistic for CTA observations.
double m_value
Parameter value.
Definition: cterror.hpp:78
std::string m_srcname
Name of source.
Definition: cterror.hpp:69
void process(void)
Compute parameter errors using a likelihood profile method.
Definition: cterror.cpp:223
cterror(void)
Void constructor.
Definition: cterror.cpp:60
void clear(void)
Clear cterror tool.
Definition: cterror.cpp:193
void log_parameters(const GChatter &chatter)
Log application parameters.
Definition: ctool.cpp:1208
Parameter error calculation tool interface definition.
GModelPar * m_model_par
Pointer to model parameter.
Definition: cterror.hpp:80
GFilename m_outmodel
Output model XML file.
Definition: cterror.hpp:70
void free_members(void)
Delete class members.
void free_members(void)
Delete class members.
void free_members(void)
Delete class members.
Definition: ctool.cpp:357
bool m_apply_edisp
Apply energy dispersion?
Definition: cterror.hpp:74
void init_members(void)
Initialise class members.
void copy_members(const cterror &app)
Copy class members.
Definition: cterror.cpp:407
const bool & read_ahead(void) const
Signal whether parameters should be read ahead.
Definition: ctool.hpp:177
void restore_edisp(GObservations &obs, const std::vector< bool > &edisp) const
Restore energy dispersion flags of CTA observations.
Definition: ctool.cpp:1689
std::vector< bool > set_edisp(GObservations &obs, const bool &edisp) const
Set energy dispersion to CTA observations.
Definition: ctool.cpp:1649
void init_members(void)
Initialise class members.
cterror & operator=(const cterror &app)
Assignment operator.
Definition: cterror.cpp:158
void log_observations(const GChatter &chatter, const GObservations &obs, const std::string &what="Observation")
Log observation container.
Definition: ctool.cpp:1251
GObservations m_obs
Observation container.
double m_confidence
Confidence level.
Definition: cterror.hpp:71
int m_max_iter
Maximum number of iterations.
Definition: cterror.hpp:73
#define G_ERR_BISECTION
Definition: cterror.cpp:42
double m_best_logL
Best fit log likelihood of given model.
Definition: cterror.hpp:81
Base class for likelihood tools.
GOptimizerLM m_opt
Optimizer.