GammaLib 2.1.0.dev
Loading...
Searching...
No Matches
GObservation.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * GObservation.cpp - Abstract observation base class *
3 * ----------------------------------------------------------------------- *
4 * copyright (C) 2008-2024 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 GObservation.cpp
23 * @brief Abstract observation base class implementation
24 * @author Juergen Knoedlseder
25 */
26
27/* __ Includes ___________________________________________________________ */
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31#include "GTools.hpp"
32#include "GException.hpp"
33#include "GIntegral.hpp"
34#include "GDerivative.hpp"
35#include "GVector.hpp"
36#include "GVectorSparse.hpp"
37#include "GMatrixSparse.hpp"
38#include "GObservation.hpp"
39#include "GResponse.hpp"
40#include "GEventCube.hpp"
41#include "GEventList.hpp"
42#include "GEventBin.hpp"
43#include "GModel.hpp"
44#include "GModels.hpp"
45#include "GModelPar.hpp"
46#include "GModelSky.hpp"
47#include "GModelData.hpp"
48
49/* __ Method name definitions ____________________________________________ */
50#define G_LIKELIHOOD "GObservation::likelihood(GModels&, GVector*,"\
51 " GMatrixSparse*, double*)"
52#define G_MODEL1 "GObservation::model(GModels&, GEvent&, GVector*)"
53#define G_MODEL2 "GObservation::model(GModels&, GMatrixSparse*)"
54#define G_EVENTS "GObservation::events()"
55#define G_NPRED "GObservation::npred(GModel&)"
56#define G_NPRED_SPEC "GObservation::npred_spec(GModel&, GTime&)"
57
58/* __ Constants __________________________________________________________ */
59const double minmod = 1.0e-100; //!< Minimum model value
60const double minerr = 1.0e-100; //!< Minimum statistical error
61
62/* __ Macros _____________________________________________________________ */
63
64/* __ Coding definitions _________________________________________________ */
65#define G_LN_ENERGY_INT //!< ln(E) variable substitution for integration
66#define G_MODEL_COLUMN_FILL //!< Use column fill for sparse matrix
67#define G_MODEL_SPARSE_VECTOR //!< Use sparse vector for sparse matrix
68#define G_LIKELIHOOD_SPARSE_VECTOR //!< Use sparse vector for sparse matrix
69
70/* __ Debug definitions __________________________________________________ */
71//#define G_OPT_DEBUG //!< Debug likelihood computation
72//#define G_DEBUG_VECTOR_MODEL_GRADIENTS //!< Debug vector model gradients
73
74
75/*==========================================================================
76 = =
77 = Constructors/destructors =
78 = =
79 ==========================================================================*/
80
81/***********************************************************************//**
82 * @brief Void constructor
83 ***************************************************************************/
85{
86 // Initialise members
88
89 // Return
90 return;
91}
92
93
94/***********************************************************************//**
95 * @brief Copy constructor
96 *
97 * @param[in] obs Observation.
98 *
99 * Instantiate the class by copying from an existing observation.
100 ***************************************************************************/
102{
103 // Initialise members
104 init_members();
105
106 // Copy members
107 copy_members(obs);
108
109 // Return
110 return;
111}
112
113
114/***********************************************************************//**
115 * @brief Destructor
116 ***************************************************************************/
118{
119 // Free members
120 free_members();
121
122 // Return
123 return;
124}
125
126
127/*==========================================================================
128 = =
129 = Operators =
130 = =
131 ==========================================================================*/
132
133/***********************************************************************//**
134 * @brief Assignment operator
135 *
136 * @param[in] obs Observation.
137 * @return Observation.
138 *
139 * Assign observation.
140 ***************************************************************************/
142{
143 // Execute only if object is not identical
144 if (this != &obs) {
145
146 // Free members
147 free_members();
148
149 // Initialise members
150 init_members();
151
152 // Copy members
153 copy_members(obs);
154
155 } // endif: object was not identical
156
157 // Return this object
158 return *this;
159}
160
161
162/*==========================================================================
163 = =
164 = Public methods =
165 = =
166 ==========================================================================*/
167
168/***********************************************************************//**
169 * @brief Return events
170 *
171 * @exception GException::no_events
172 * No events allocated for observation.
173 *
174 * Returns pointer to events.
175 ***************************************************************************/
177{
178 // Throw an exception if the event container is not valid
179 if (m_events == NULL) {
180 std::string msg = "No events allocated for observation.";
182 }
183
184 // Return pointer to events
185 return (m_events);
186}
187
188
189/***********************************************************************//**
190 * @brief Return events (const version)
191 *
192 * @exception GException::no_events
193 * No events allocated for observation.
194 *
195 * Returns const pointer to events.
196 ***************************************************************************/
198{
199 // Throw an exception if the event container is not valid
200 if (m_events == NULL) {
201 std::string msg = "No events allocated for observation.";
203 }
204
205 // Return pointer to events
206 return (m_events);
207}
208
209
210/***********************************************************************//**
211 * @brief Set event container
212 *
213 * @param[in] events Event container.
214 *
215 * Set the event container for this observation by cloning the container
216 * specified in the argument.
217 ***************************************************************************/
218void GObservation::events(const GEvents& events)
219{
220 // Free existing event container only if it differs from current event
221 // container. This prevents unintential deallocation of the argument
222 if ((m_events != NULL) && (m_events != &events)) {
223 delete m_events;
224 }
225
226 // Clone events
228
229 // Return
230 return;
231}
232
233
234/***********************************************************************//**
235 * @brief Compute likelihood function
236 *
237 * @param[in] models Models.
238 * @param[in,out] gradients Pointer to gradients.
239 * @param[in,out] curvature Pointer to curvature matrix.
240 * @param[in,out] npred Pointer to Npred value.
241 * @return Likelihood.
242 *
243 * Computes the likelihood for a specified set of models. The method also
244 * returns the gradients, the curvature matrix, and the number of events
245 * that are predicted by all models.
246 ***************************************************************************/
248 GVector* gradients,
249 GMatrixSparse* curvature,
250 double* npred) const
251{
252 // Initialise likelihood value
253 double value = 0.0;
254
255 // Extract statistic for this observation
256 std::string statistic = gammalib::toupper(this->statistic());
257
258 // Unbinned analysis
259 if (dynamic_cast<const GEventList*>(events()) != NULL) {
260
261 // Poisson statistic
262 if ((statistic == "POISSON") || (statistic == "CSTAT")) {
263
264 // Update the log-likelihood
265 value = likelihood_poisson_unbinned(models,
266 gradients,
267 curvature,
268 npred);
269
270 } // endif: Poisson statistic
271
272 // ... otherwise throw an exception
273 else {
274 std::string msg = "Invalid statistic \""+statistic+"\". Unbinned "
275 "optimization requires \"POISSON\" or \"CSTAT\" "
276 "statistic.";
278 }
279
280 } // endif: unbinned analysis
281
282 // ... or binned analysis
283 else {
284
285 // Poisson statistic
286 if ((statistic == "POISSON") || (statistic == "CSTAT")) {
287 value = likelihood_poisson_binned(models,
288 gradients,
289 curvature,
290 npred);
291 }
292
293 // ... or Gaussian statistic
294 else if ((statistic == "GAUSSIAN") || (statistic == "CHI2")) {
295 value = likelihood_gaussian_binned(models,
296 gradients,
297 curvature,
298 npred);
299 }
300
301 // ... or unsupported
302 else {
303 std::string msg = "Invalid statistic \""+statistic+"\". Binned "
304 "optimization requires \"POISSON\", \"CSTAT\", "
305 "\"GAUSSIAN\" or \"CHI2\" statistic.";
307 }
308
309 } // endelse: binned analysis
310
311 // Return likelihood
312 return value;
313}
314
315
316/***********************************************************************//**
317 * @brief Return model value and (optionally) gradients
318 *
319 * @param[in] models Model container.
320 * @param[in] event Observed event.
321 * @param[out] gradients Pointer to gradient vector (optional).
322 * @return Model value.
323 *
324 * @exception GException::invalid_value
325 * Dimension of gradient vector mismatches number of parameters.
326 *
327 * Implements generic model and gradient evaluation for an observation. The
328 * model gives the probability for an event to occur with a given instrument
329 * direction, at a given energy and at a given time. The gradient is the
330 * parameter derivative of this probability. If NULL is passed for the
331 * gradient vector, then gradients will not be computed.
332 *
333 * The method will only operate on models for which the list of instruments
334 * and observation identifiers matches those of the observation. Models that
335 * do not match will be skipped.
336 ***************************************************************************/
337double GObservation::model(const GModels& models,
338 const GEvent& event,
339 GVector* gradients) const
340{
341 // Initialise method variables
342 double model = 0.0; // Reset model value
343 int igrad = 0; // Reset gradient counter
344 int grad_size = 0; // Reset gradient size
345
346 // Set gradient usage flag
347 bool use_grad = (gradients != NULL);
348
349 // If gradient is available then reset gradient vector elements to 0
350 // and determine vector size
351 if (use_grad) {
352
353 // Reset gradient vector
354 (*gradients) = 0.0;
355 grad_size = gradients->size();
356
357 // Initialise stack of parameters with gradients
358 m_pars_with_gradients.clear();
359 m_pars_with_gradients.reserve(models.size());
360
361 } // endif: gradient vector available
362
363 // Loop over models
364 for (int i = 0; i < models.size(); ++i) {
365
366 // Get model pointer. Continue only if pointer is valid
367 const GModel* mptr = models[i];
368 if (mptr != NULL) {
369
370 // Continue only if model applies to specific instrument and
371 // observation identifier
372 if (mptr->is_valid(instrument(), id())) {
373
374 // Compute value and add to model
375 model += mptr->eval(event, *this, use_grad);
376
377 // Optionally determine model gradients
378 if (use_grad) {
379
380 // Make sure that we have a slot for the gradient
381 #if defined(G_RANGE_CHECK)
382 if (igrad+mptr->size() > grad_size) {
383 std::string msg = "Vector has not enough elements to "
384 "store the model parameter gradients. "+
385 gammalib::str(models.npars())+" elements "
386 "requested while vector only contains "+
387 gammalib::str(gradients->size())+" elements.";
389 }
390 #endif
391
392 // If model provides the parameter indices that were
393 // updated by the eval() method then use them as this is
394 // less time consuming than looping over all indices
395 if (mptr->has_eval_indices()) {
396
397 // Get number of relevant parameters
398 int npars = mptr->eval_indices().size();
399
400 // Loop over all relevant parameters
401 for (int index = 0; index < npars; ++index) {
402
403 // Retrieve parameter index
404 int ipar = mptr->eval_indices()[index];
405
406 // Get reference to model parameter
407 const GModelPar& par = (*mptr)[ipar];
408
409 // Set gradient
410 if (par.is_free()) {
411 if (has_gradient(*mptr, par)) {
412 (*gradients)[igrad+ipar] =
413 par.factor_gradient();
414 }
415 else {
416 (*gradients)[igrad+ipar] =
417 model_grad(*mptr, par, event);
418 }
419 }
420 else {
421 (*gradients)[igrad+ipar] = 0.0;
422 }
423
424 } // endfor: looped over all parameter indices
425
426 } // endif: parameter indices were available
427
428 // ... otherwise loop over all parameter indices
429 else {
430
431 // Loop over all parameters
432 for (int ipar = 0; ipar < mptr->size(); ++ipar) {
433
434 // Get reference to model parameter
435 const GModelPar& par = (*mptr)[ipar];
436
437 // Set gradient
438 if (par.is_free()) {
439 if (has_gradient(*mptr, par)) {
440 (*gradients)[igrad+ipar] =
441 par.factor_gradient();
442 }
443 else {
444 (*gradients)[igrad+ipar] =
445 model_grad(*mptr, par, event);
446 }
447 }
448 else {
449 (*gradients)[igrad+ipar] = 0.0;
450 }
451
452 } // endfor: looped over all parameter indices
453
454 } // endelse: no parameter indices were available
455
456 } // endif: use model gradients
457
458 } // endif: model component was valid for instrument
459
460 // Increment parameter counter for gradients
461 igrad += mptr->size();
462
463 } // endif: model was valid
464
465 } // endfor: Looped over models
466
467 // Return
468 return model;
469}
470
471
472/***********************************************************************//**
473 * @brief Return vector of model values and (optionally) gradients
474 *
475 * @param[in] models Model container.
476 * @param[out] gradients Pointer to sparse gradient matrix.
477 * @return Vector of model values.
478 *
479 * @exception GException::invalid_argument
480 * Gradient matrix mismatches number of events or model parameters.
481 *
482 * Returns the model values for each event in the observation.
483 *
484 * If @p gradients is not NULL, the matrix contains on output the model
485 * factor gradients for all events. Each row of the @p gradients matrix
486 * corresponds to one event, the columns correspond to the parameters
487 * of the @p models container.
488 *
489 * The method is optimised for speed, using sparse vectors and matrices.
490 ***************************************************************************/
492 GMatrixSparse* gradients) const
493{
494 // Initialise variables
495 int nevents = events()->size();
496 bool use_grad = (gradients != NULL);
497 int igrad = 0;
498
499 // Initialise model values
500 GVector values(nevents);
501
502 // Initialise vector for gradient manipulation
503 #if defined(G_MODEL_SPARSE_VECTOR)
504 GVectorSparse grad(nevents, nevents);
505 #else
506 GVector grad(nevents);
507 #endif
508
509 // If gradient is available then check gradient size
510 if (use_grad) {
511
512 // Initialise stack of parameters with gradients
513 m_pars_with_gradients.clear();
514 m_pars_with_gradients.reserve(models.size());
515
516 // Check number of columns
517 int ncolumns = (nevents > 0) ? models.npars() : 0;
518 if (gradients->columns() != ncolumns) {
519 std::string msg = "Number of "+gammalib::str(gradients->columns())+" "
520 "columns in gradient matrix differs from expected "
521 "number of "+gammalib::str(ncolumns)+". Please "
522 "specify compatible arguments.";
524 }
525
526 // Check number of rows
527 int nrows = (ncolumns > 0) ? nevents : 0;
528 if (gradients->rows() != nrows) {
529 std::string msg = "Number of "+gammalib::str(gradients->rows())+" "
530 "rows in gradient matrix differs from expected "
531 "number of "+gammalib::str(nrows)+". Please "
532 "specify compatible arguments.";
534 }
535
536 // Initialise sparse matrix column fill buffers
537 #if defined(G_MODEL_COLUMN_FILL)
538 gradients->colfill_init();
539 #endif
540
541 } // endif: gradient was requested
542
543 // Loop over models
544 for (int i = 0; i < models.size(); ++i) {
545
546 // Get model pointer. Continue only if pointer is valid
547 const GModel* mptr = models[i];
548 if (mptr != NULL) {
549
550 // Continue only if model applies to specific instrument and
551 // observation identifier
552 if (mptr->is_valid(instrument(), id())) {
553
554 // If no gradients should be used then evaluate model and
555 // add it to values
556 if (!use_grad) {
557
558 // Evaluate model and add to values
559 values += mptr->eval(*this, NULL);
560
561 }
562
563 // ... otherwise if there are events then evaluate model and
564 // gradients and add to model vector and gradient matrix
565 else if (nevents > 0) {
566
567 // Allocate gradient matrix
568 GMatrixSparse grads(nevents, mptr->size());
569
570 // Initialise sparse matrix stack
571 grads.stack_init(nevents*mptr->size(), mptr->size());
572
573 // Evaluate model and add to values
574 values += mptr->eval(*this, &grads);
575
576 // Destroy sparse matrix stack (fills stack in matrix)
577 grads.stack_destroy();
578
579 // If model provides the parameter indices that were
580 // updated by the eval() method then use them as this
581 // is less time consuming than looping over all indices
582 if (mptr->has_eval_indices()) {
583
584 // Get number of relevant parameters
585 int npars = mptr->eval_indices().size();
586
587 // Loop over all relevant parameters
588 for (int index = 0; index < npars; ++index) {
589
590 // Retrieve parameter index
591 int ipar = mptr->eval_indices()[index];
592
593 // Get reference to model parameter
594 const GModelPar& par = (*mptr)[ipar];
595
596 // If parameters is free then set gradient
597 if (par.is_free()) {
598
599 // Determine gradient
600 //GVector grad(nevents);
601 if (has_gradient(*mptr, par)) {
602 //grad = grads.column(ipar);
603 grads.column_to_vector(ipar, &grad);
604 }
605 else {
606 grad = model_grad(*mptr, par);
607 }
608
609 // Set gradient
610 #if defined(G_MODEL_COLUMN_FILL)
611 gradients->colfill_set_column(igrad+ipar, grad);
612 #else
613 gradients->column(igrad+ipar, grad);
614 #endif
615
616 } // endif: parameter was free
617
618 } // endfor: looped over all parameter indices
619
620 } // endif: parameter indices were available
621
622 // ... otherwise loop over all parameter indices
623 else {
624
625 // Loop over all parameters
626 for (int ipar = 0; ipar < mptr->size(); ++ipar) {
627
628 // Get reference to model parameter
629 const GModelPar& par = (*mptr)[ipar];
630
631 // If parameters is free then set gradient
632 if (par.is_free()) {
633
634 // Determine gradient
635 //GVector grad(nevents);
636 if (has_gradient(*mptr, par)) {
637 //grad = grads.column(ipar);
638 grads.column_to_vector(ipar, &grad);
639 #if defined(G_DEBUG_VECTOR_MODEL_GRADIENTS)
640 if (ipar < 3) {
641 GVector num_grad = model_grad(*mptr, par);
642 for (int k = 0; k < nevents; ++k) {
643 if (grad[k] != 0.0 || num_grad[k] != 0.0) {
644 std::cout << "ipar=" << ipar;
645 std::cout << " par=" << par.name();
646 std::cout << " k=" << k;
647 std::cout << " ana=" << grad[k];
648 std::cout << " num=" << num_grad[k];
649 std::cout << std::endl;
650 }
651 }
652 }
653 #endif
654 }
655 else {
656 grad = model_grad(*mptr, par);
657 }
658
659 // Set gradient
660 #if defined(G_MODEL_COLUMN_FILL)
661 gradients->colfill_set_column(igrad+ipar, grad);
662 #else
663 gradients->column(igrad+ipar, grad);
664 #endif
665
666 } // endif: parameter was free
667
668 } // endfor: looped over all parameter indices
669
670 } // endelse: no parameter indices were available
671
672 } // endif: use model gradients
673
674 } // endif: model component was valid for instrument
675
676 // Increment parameter counter for gradients
677 igrad += mptr->size();
678
679 } // endif: model was valid
680
681 } // endfor: Looped over models
682
683 // If gradient is available then flush columns into sparse matrix
684 #if defined(G_MODEL_COLUMN_FILL)
685 if (use_grad) {
686 gradients->colfill_flush();
687 }
688 #endif
689
690 // Return values
691 return values;
692}
693
694
695/***********************************************************************//**
696 * @brief Return total number of observed events
697 *
698 * @returns Total number of observed events.
699 *
700 * Returns the total number of observed events. If the observation does not
701 * contain any events the method returns zero.
702 ***************************************************************************/
703double GObservation::nobserved(void) const
704{
705 // Initialise number of observed events
706 double nobserved = 0;
707
708 // Extract number of observed events
709 if (m_events != NULL) {
711 }
712
713 // Return number of observed events
714 return nobserved;
715}
716
717
718/***********************************************************************//**
719 * @brief Return total number (and optionally gradients) of predicted counts
720 * for all models
721 *
722 * @param[in] models Models.
723 * @param[out] gradients Model parameter gradients (optional).
724 *
725 * @exception GException::invalid_argument
726 * Dimension of gradient vector mismatches number of parameters.
727 *
728 * Returns the total number of predicted counts within the analysis region.
729 * If NULL is passed for the gradient vector then gradients will not be
730 * computed.
731 *
732 * The method will only operate on models for which the list of instruments
733 * and observation identifiers matches those of the observation. Models that
734 * do not match will be skipped.
735 ***************************************************************************/
736double GObservation::npred(const GModels& models, GVector* gradients) const
737{
738 // Verify that gradient vector and models have the same dimension
739 #if defined(G_RANGE_CHECK)
740 if (gradients != NULL) {
741 if (models.npars() != gradients->size()) {
742 std::string msg = "Model parameter gradients vector size "+
743 gammalib::str(gradients->size())+" differs from "
744 "number of "+gammalib::str(models.npars())+
745 " model parameters. Please specify a gradients "
746 "vector with the appropriate size.";
748 }
749 }
750 #endif
751
752 // Initialise
753 double npred = 0.0; // Reset predicted number of counts
754 int igrad = 0; // Reset gradient counter
755
756 // If gradient is available then reset gradient vector elements to 0
757 if (gradients != NULL) {
758 (*gradients) = 0.0;
759 }
760
761 // Loop over models
762 for (int i = 0; i < models.size(); ++i) {
763
764 // Get model pointer. Continue only if pointer is valid
765 const GModel* mptr = models[i];
766 if (mptr != NULL) {
767
768 // Continue only if model applies to specific instrument and
769 // observation identifier
770 if (mptr->is_valid(instrument(), id())) {
771
772 // Determine Npred for model
773 npred += this->npred(*mptr);
774
775 // Optionally determine Npred gradients
776 if (gradients != NULL) {
777 for (int k = 0; k < mptr->size(); ++k) {
778 const GModelPar& par = (*mptr)[k];
779 (*gradients)[igrad+k] = npred_grad(*mptr, par);
780 }
781 }
782
783 } // endif: model component was valid for instrument
784
785 // Increment parameter counter for gradient
786 igrad += mptr->size();
787
788 } // endif: model was valid
789
790 } // endfor: Looped over models
791
792 // Return prediction
793 return npred;
794}
795
796
797/***********************************************************************//**
798 * @brief Return total number of predicted counts for one model
799 *
800 * @param[in] model Gamma-ray source model.
801 *
802 * @exception GException::gti_invalid
803 * Good Time Interval is invalid.
804 *
805 * Computes
806 *
807 * \f[
808 * N_{\rm pred} = \int_{\rm GTI} \int_{E_{\rm bounds}} \int_{\rm ROI}
809 * P(p',E',t') \, dp' \, dE' \, dt'
810 * \f]
811 *
812 * of the event probability
813 *
814 * \f[
815 * P(p',E',t') = \int \int \int
816 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp \, dE \, dt
817 * \f]
818 *
819 * where
820 * \f$S(p,E,t)\f$ is the source model,
821 * \f$R(p',E',t'|p,E,t)\f$ is the instrument response function,
822 * \f$p'\f$ is the measured photon direction,
823 * \f$E'\f$ is the measured photon energy,
824 * \f$t'\f$ is the measured photon arrival time,
825 * \f$p\f$ is the true photon arrival direction,
826 * \f$E\f$ is the true photon energy, and
827 * \f$t\f$ is the true photon arrival time.
828 *
829 * This method performs the time integration over the Good Time Intervals
830 * \f${\rm GTI}\f$. Note that MET is used for the time integration interval.
831 * This, however, is no specialisation since npred_grad_kern_spec::eval()
832 * converts the argument back in a GTime object by assuming that the argument
833 * is in MET, hence the correct time system will be used at the end by the
834 * method.
835 ***************************************************************************/
836double GObservation::npred(const GModel& model) const
837{
838 // Initialise result
839 double npred = 0.0;
840
841 // Continue only if model applies to specific instrument and
842 // observation identifier
843 if (model.is_valid(instrument(), id())) {
844
845 // Case A: If the model is constant then integrate analytically
846 if (model.is_constant()) {
847
848 // Evaluate model at first start time and multiply by ontime
849 double ontime = events()->gti().ontime();
850
851 // Integrate only if ontime is positive
852 if (ontime > 0.0) {
853
854 // Integration is a simple multiplication by the time
855 npred = npred_spec(model, events()->gti().tstart()) * ontime;
856
857 }
858
859 } // endif: model was constant
860
861 // ... otherwise integrate temporally
862 else {
863
864 // Loop over GTIs
865 for (int i = 0; i < events()->gti().size(); ++i) {
866
867 // Set integration interval in seconds
868 double tstart = events()->gti().tstart(i).secs();
869 double tstop = events()->gti().tstop(i).secs();
870
871 // Throw exception if time interval is not valid
872 if (tstop <= tstart) {
873 std::string msg = "Start time "+
874 events()->gti().tstart(i).print()+
875 " is past the stop time "+
876 events()->gti().tstop(i).print()+
877 " for Good Time Interval "+
878 gammalib::str(i)+". Please make sure "
879 "that all Good Time Intervals have start "
880 "times that are earlier than the stop "
881 "time.";
883 }
884
885 // Setup integration function
886 GObservation::npred_kern integrand(this, &model);
887 GIntegral integral(&integrand);
888
889 // Do Romberg integration
890 npred += integral.romberg(tstart, tstop);
891
892 } // endfor: looped over GTIs
893
894 } // endelse: integrated temporally
895
896 } // endif: model was valid
897
898 // Return Npred
899 return npred;
900}
901
902
903/***********************************************************************//**
904 * @brief Returns parameter gradient of model for a given event
905 *
906 * @param[in] model Model.
907 * @param[in] par Model parameter.
908 * @param[in] event Event.
909 *
910 * This method uses a robust but simple difference method to estimate
911 * parameter gradients that have not been provided by the model. We use here
912 * a simple method as this method is likely used for spatial model parameters,
913 * and the spatial model may eventually be noisy due to numerical integration
914 * limits.
915 ***************************************************************************/
917 const GModelPar& par,
918 const GEvent& event) const
919{
920 // Initialise gradient
921 double grad = 0.0;
922
923 // Compute gradient only if parameter is free
924 if (par.is_free()) {
925
926 // Get non-const model pointer
927 GModelPar* ptr = const_cast<GModelPar*>(&par);
928
929 // Save current model parameter
930 GModelPar current = par;
931
932 // Get actual parameter value
933 double x = par.factor_value();
934
935 // Set step size for computation of derivative.
936 double h = grad_step_size(par);
937
938 // Re-adjust the step-size h in case that the initial step size is
939 // larger than the allowed parameter range
940 if (par.has_min() && par.has_max()) {
941 double par_h = par.factor_max() - par.factor_min();
942 if (par_h < h) {
943 h = par_h;
944 }
945 }
946
947 // Continue only if step size is positive
948 if (h > 0.0) {
949
950 // Remove any boundaries to avoid limitations
951 //ptr->remove_range(); // Not needed in principle
952
953 // Setup derivative function
954 GObservation::model_func function(this, &model, ptr, &event);
955 GDerivative derivative(&function);
956
957 // If we are too close to the minimum boundary use a right sided
958 // difference ...
959 if (par.has_min() && ((x-par.factor_min()) < h)) {
960 grad = derivative.right_difference(x, h);
961 }
962
963 // ... otherwise if we are too close to the maximum boundary use
964 // a left sided difference ...
965 else if (par.has_max() && ((par.factor_max()-x) < h)) {
966 grad = derivative.left_difference(x, h);
967 }
968
969 // ... otherwise use a symmetric difference
970 else {
971 grad = derivative.difference(x, h);
972 }
973
974 } // endif: step size was positive
975
976 // Restore current model parameter
977 *ptr = current;
978
979 } // endif: model parameter was free
980
981 // Return gradient
982 return grad;
983}
984
985
986/***********************************************************************//**
987 * @brief Returns parameter gradients of model for all events
988 *
989 * @param[in] model Model.
990 * @param[in] par Model parameter.
991 *
992 * This method uses a robust but simple difference method to estimate
993 * parameter gradients that have not been provided by the model. We use here
994 * a simple method as this method is likely used for spatial model parameters,
995 * and the spatial model may eventually be noisy due to numerical integration
996 * limits.
997 ***************************************************************************/
999 const GModelPar& par) const
1000{
1001 // Initialise gradients
1002 GVector gradients(events()->size());
1003
1004 // Compute gradient only if parameter is free
1005 if (par.is_free()) {
1006
1007 // Get non-const model pointer
1008 GModelPar* ptr = const_cast<GModelPar*>(&par);
1009
1010 // Save current model parameter
1011 GModelPar current = par;
1012
1013 // Get actual parameter value
1014 double x = par.factor_value();
1015
1016 // Set step size for computation of derivative.
1017 double h = grad_step_size(par);
1018
1019 // Re-adjust the step-size h in case that the initial step size is
1020 // larger than the allowed parameter range
1021 if (par.has_min() && par.has_max()) {
1022 double par_h = par.factor_max() - par.factor_min();
1023 if (par_h < h) {
1024 h = par_h;
1025 }
1026 }
1027
1028 // Continue only if step size is positive
1029 if (h > 0.0) {
1030
1031 // Setup derivative function
1032 //GObservation::model_func function(this, &model, ptr, &event);
1033 //GDerivative derivative(&function);
1034
1035 // If we are too close to the minimum boundary use a right sided
1036 // difference ...
1037 if (par.has_min() && ((x-par.factor_min()) < h)) {
1038 //grad = derivative.right_difference(x, h);
1039 // Compute fs1
1040 ptr->factor_value(x);
1041 GVector fs1 = model.eval(*this, NULL);
1042
1043 // Compute fs2
1044 ptr->factor_value(x+h);
1045 GVector fs2 = model.eval(*this, NULL);
1046
1047 // Compute derivative
1048 gradients = (fs1 - fs2) / h;
1049
1050 }
1051
1052 // ... otherwise if we are too close to the maximum boundary use
1053 // a left sided difference ...
1054 else if (par.has_max() && ((par.factor_max()-x) < h)) {
1055 //grad = derivative.left_difference(x, h);
1056 // Compute fs1
1057 ptr->factor_value(x);
1058 GVector fs1 = model.eval(*this, NULL);
1059
1060 // Compute fs2
1061 ptr->factor_value(x-h);
1062 GVector fs2 = model.eval(*this, NULL);
1063
1064 // Compute derivative
1065 gradients = (fs1 - fs2) / h;
1066 }
1067
1068 // ... otherwise use a symmetric difference
1069 else {
1070 //grad = derivative.difference(x, h);
1071 // Compute fs1
1072 ptr->factor_value(x+h);
1073 GVector fs1 = model.eval(*this, NULL);
1074
1075 // Compute fs2
1076 ptr->factor_value(x-h);
1077 GVector fs2 = model.eval(*this, NULL);
1078
1079 // Compute derivative
1080 gradients = 0.5 * (fs1 - fs2) / h;
1081 }
1082
1083 } // endif: step size was positive
1084
1085 // Restore current model parameter
1086 *ptr = current;
1087
1088 } // endif: model parameter was free
1089
1090 // Return gradients
1091 return gradients;
1092}
1093
1094
1095/***********************************************************************//**
1096 * @brief Returns parameter gradient of Npred
1097 *
1098 * @param[in] model Gamma-ray source model.
1099 * @param[in] par Model parameter.
1100 *
1101 * Computes
1102 *
1103 * \f[
1104 * \frac{{\rm d} N_{\rm pred}}{{\rm d} a_i}
1105 * \f]
1106 *
1107 * where
1108 *
1109 * \f[
1110 * N_{\rm pred} = \int_{\rm GTI} \int_{E_{\rm bounds}} \int_{\rm ROI}
1111 * P(p',E',t') \, dp' \, dE' \, dt'
1112 * \f]
1113 *
1114 * is the integral of the event probability
1115 *
1116 * \f[
1117 * P(p',E',t') = \int \int \int
1118 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp \, dE \, dt
1119 * \f]
1120 *
1121 * and
1122 * \f$a_i\f$ is the model parameter \f$i\f$.
1123 * Furthermore
1124 * \f$S(p,E,t)\f$ is the source model,
1125 * \f$R(p',E',t'|p,E,t)\f$ is the instrument response function,
1126 * \f$p'\f$ is the measured photon direction,
1127 * \f$E'\f$ is the measured photon energy,
1128 * \f$t'\f$ is the measured photon arrival time,
1129 * \f$p\f$ is the true photon arrival direction,
1130 * \f$E\f$ is the true photon energy, and
1131 * \f$t\f$ is the true photon arrival time.
1132 *
1133 * This method uses a robust but simple difference method to estimate
1134 * parameter gradients that have not been provided by the model. We use here
1135 * a simple method as this method is likely used for spatial model parameters,
1136 * and the spatial model may eventually be noisy due to numerical integration
1137 * limits.
1138 ***************************************************************************/
1139double GObservation::npred_grad(const GModel& model, const GModelPar& par) const
1140{
1141 // Initialise result
1142 double grad = 0.0;
1143
1144 // Compute gradient only if parameter is free
1145 if (par.is_free()) {
1146
1147 // Get non-const model pointer
1148 GModelPar* ptr = const_cast<GModelPar*>(&par);
1149
1150 // Save current model parameter
1151 GModelPar current = par;
1152
1153 // Get actual parameter value
1154 double x = par.factor_value();
1155
1156 // Set step size for computation of derivative.
1157 double h = grad_step_size(par);
1158
1159 // Re-adjust the step-size h in case that the initial step size is
1160 // larger than the allowed parameter range
1161 if (par.has_min() && par.has_max()) {
1162 double par_h = par.factor_max() - par.factor_min();
1163 if (par_h < h) {
1164 h = par_h;
1165 }
1166 }
1167
1168 // Continue only if step size is positive
1169 if (h > 0.0) {
1170
1171 // Remove any boundaries to avoid limitations
1172 //ptr->remove_range(); // Not needed in principle
1173
1174 // Setup derivative function
1175 GObservation::npred_func function(this, &model, ptr);
1176 GDerivative derivative(&function);
1177
1178 // If we are too close to the minimum boundary use a right sided
1179 // difference ...
1180 if (par.has_min() && ((x-par.factor_min()) < h)) {
1181 grad = derivative.right_difference(x, h);
1182 }
1183
1184 // ... otherwise if we are too close to the maximum boundary use
1185 // a left sided difference ...
1186 else if (par.has_max() && ((par.factor_max()-x) < h)) {
1187 grad = derivative.left_difference(x, h);
1188 }
1189
1190 // ... otherwise use a symmetric difference
1191 else {
1192 grad = derivative.difference(x, h);
1193 }
1194
1195 } // endif: step size was positive
1196
1197 // Restore current model parameter
1198 *ptr = current;
1199
1200 } // endif: model parameter was free
1201
1202 // Return result
1203 return grad;
1204}
1205
1206
1207/***********************************************************************//**
1208 * @brief Remove response cache for specific model
1209 *
1210 * @param[in] name Model name.
1211 *
1212 * Removes response cache for model @p name from current observation.
1213 ***************************************************************************/
1214void GObservation::remove_response_cache(const std::string& name)
1215{
1216 // Build cache identifier
1217 std::string cache_id = id() + ":" + name;
1218
1219 // Remove response cache
1220 const_cast<GResponse*>(this->response())->remove_response_cache(cache_id);
1221
1222 // Return
1223 return;
1224}
1225
1226
1227/***********************************************************************//**
1228 * @brief Check whether a model parameter has an analytical gradient
1229 *
1230 * @param[in] model Model.
1231 * @param[in] par Model parameter.
1232 * @returns True if model parameter is free and has an analytical gradient.
1233 *
1234 * Checks whether a model parameter is free and has an analytical gradient.
1235 ***************************************************************************/
1236bool GObservation::has_gradient(const GModel& model, const GModelPar& par) const
1237{
1238 // Initialise flag
1239 bool has_gradient = false;
1240
1241 // Only consider free parameters
1242 if (par.is_free()) {
1243
1244 // Build identifier
1245 std::string id = model.name() + ":" + par.name();
1246
1247 // Search for parameter address in stack and set flag to true if
1248 // parameter address was found
1249 for (int i = 0; i < m_pars_with_gradients.size(); ++i) {
1250 if (m_pars_with_gradients[i] == id) {
1251 has_gradient = true;
1252 break;
1253 }
1254 }
1255
1256 } // endif: parameter was free
1257
1258 // Return flag
1259 return has_gradient;
1260}
1261
1262
1263/***********************************************************************//**
1264 * @brief Signals that an analytical gradient was computed for a model
1265 * parameter
1266 *
1267 * @param[in] model Model.
1268 * @param[in] par Model parameter.
1269 *
1270 * Signals that an analytical gradient was computed for a model parameter.
1271 ***************************************************************************/
1272void GObservation::computed_gradient(const GModel& model, const GModelPar& par) const
1273{
1274 // Initialise flag
1275 bool in_stack = false;
1276
1277 // Build identifier
1278 std::string id = model.name() + ":" + par.name();
1279
1280 // Check if parameter is already in stack
1281 for (int i = 0; i < m_pars_with_gradients.size(); ++i) {
1282 if (m_pars_with_gradients[i] == id) {
1283 in_stack = true;
1284 break;
1285 }
1286 }
1287
1288 // If parameter is not yet in stack the push it on the stack
1289 if (!in_stack) {
1290 m_pars_with_gradients.push_back(id);
1291 }
1292
1293 // Return
1294 return;
1295}
1296
1297
1298/*==========================================================================
1299 = =
1300 = Private methods =
1301 = =
1302 ==========================================================================*/
1303
1304/***********************************************************************//**
1305 * @brief Initialise class members
1306 *
1307 * The step size for gradient computation is fixed by default to 0.0002
1308 * degrees, which corresponds to about 1 arcsec for parameters that are
1309 * given in degrees. The reasoning behind this value is that parameters that
1310 * use numerical gradients are typically angles, such as for example the
1311 * position, and we want to achieve arcsec precision with this method.
1312 ***************************************************************************/
1314{
1315 // Initialise members
1316 m_name.clear();
1317 m_id.clear();
1318 m_statistic = "cstat";
1319 m_events = NULL;
1320
1321 // Initialise stack of identifiers of parameters with gradients
1322 m_pars_with_gradients.clear();
1323
1324 // Return
1325 return;
1326}
1327
1328
1329/***********************************************************************//**
1330 * @brief Copy class members
1331 *
1332 * @param[in] obs Observation.
1333 *
1334 * Copy members from an observation.
1335 ***************************************************************************/
1337{
1338 // Copy members
1339 m_name = obs.m_name;
1340 m_id = obs.m_id;
1342
1343 // Copy stack of identifiers of parameters with gradients
1345
1346 // Clone members
1347 m_events = (obs.m_events != NULL) ? obs.m_events->clone() : NULL;
1348
1349 // Return
1350 return;
1351}
1352
1353
1354/***********************************************************************//**
1355 * @brief Delete class members
1356 ***************************************************************************/
1358{
1359 // Free members
1360 if (m_events != NULL) delete m_events;
1361
1362 // Signal free pointers
1363 m_events = NULL;
1364
1365 // Return
1366 return;
1367}
1368
1369
1370/*==========================================================================
1371 = =
1372 = Likelihood methods =
1373 = =
1374 ==========================================================================*/
1375
1376/***********************************************************************//**
1377 * @brief Evaluate log-likelihood function for Poisson statistic and
1378 * unbinned analysis (version with working arrays)
1379 *
1380 * @param[in] models Models.
1381 * @param[in,out] gradients Gradient.
1382 * @param[in,out] curvature Curvature matrix.
1383 * @param[in,out] npred Number of predicted events.
1384 * @return Likelihood value.
1385 *
1386 * This method evaluates the -(log-likelihood) function for parameter
1387 * optimisation using unbinned analysis and Poisson statistic.
1388 * The -(log-likelihood) function is given by
1389 *
1390 * \f[
1391 * L = N_{\rm pred} - \sum_i \log e_i
1392 * \f]
1393 *
1394 * where
1395 * \f$N_{\rm pred}\f$ is the number of events predicted by the model, and
1396 * the sum is taken over all events. This method also computes the
1397 * parameter gradients
1398 * \f$\delta L/dp\f$
1399 * and the curvature matrix
1400 * \f$\delta^2 L/dp_1 dp_2\f$.
1401 ***************************************************************************/
1403 GVector* gradients,
1404 GMatrixSparse* curvature,
1405 double* npred) const
1406{
1407 // Initialise likelihood value
1408 double value = 0.0;
1409
1410 // Get number of events and parameters
1411 int nevents = events()->size();
1412 int npars = gradients->size();
1413
1414 // Allocate some working arrays
1415 int* inx = new int[npars];
1416 double* values = new double[npars];
1417 GMatrixSparse wrk_matrix(nevents, npars);
1418 GVector wrk_grad(npars);
1419
1420 // Determine Npred value and gradient for this observation
1421 double npred_value = this->npred(models, &wrk_grad);
1422
1423 // Update likelihood, Npred and gradients
1424 value += npred_value;
1425 *npred += npred_value;
1426 *gradients += wrk_grad;
1427
1428 // Compute model and derivative
1429 GVector model_vector = this->model(models, &wrk_matrix);
1430
1431 // Transpose matrix
1432 wrk_matrix = wrk_matrix.transpose();
1433
1434 // Iterate over all events
1435 for (int i = 0; i < nevents; ++i) {
1436
1437 // Skip events that should not be used
1438 if (!use_event_for_likelihood(i)) {
1439 continue;
1440 }
1441
1442 // Get model value
1443 double model = model_vector[i];
1444
1445 // Skip bin if model is too small (avoids -Inf or NaN gradients)
1446 if (model <= minmod) {
1447 continue;
1448 }
1449
1450 // Extract working vector of parameter gradients
1451 wrk_matrix.column_to_vector(i, &wrk_grad);
1452
1453 // Create index array of non-zero derivatives and initialise working
1454 // array
1455 int ndev = 0;
1456 for (int ipar = 0; ipar < npars; ++ipar) {
1457 values[ipar] = 0.0;
1458 if (wrk_grad[ipar] != 0.0 && !gammalib::is_infinite(wrk_grad[ipar])) {
1459 inx[ndev] = ipar;
1460 ndev++;
1461 }
1462 }
1463
1464 // Update Poissonian statistic (excluding factorial term for faster
1465 // computation)
1466 value -= std::log(model);
1467
1468 // Skip bin now if there are no non-zero derivatives
1469 if (ndev < 1) {
1470 continue;
1471 }
1472
1473 // Update gradient vector and curvature matrix.
1474 double fb = 1.0 / model;
1475 double fa = fb / model;
1476 for (int jdev = 0; jdev < ndev; ++jdev) {
1477
1478 // Initialise computation
1479 register int jpar = inx[jdev];
1480 double g = wrk_grad[jpar];
1481 double fa_i = fa * g;
1482
1483 // Update gradient.
1484 (*gradients)[jpar] -= fb * g;
1485
1486 // Loop over rows
1487 register int* ipar = inx;
1488
1489 for (register int idev = 0; idev < ndev; ++idev, ++ipar) {
1490 values[idev] = fa_i * wrk_grad[*ipar];
1491 }
1492
1493 // Add column to matrix
1494 curvature->add_to_column(jpar, values, inx, ndev);
1495
1496 } // endfor: looped over columns
1497
1498 } // endfor: iterated over all events
1499
1500 // Free temporary memory
1501 if (values != NULL) delete [] values;
1502 if (inx != NULL) delete [] inx;
1503
1504 // Return
1505 return value;
1506}
1507
1508
1509/***********************************************************************//**
1510 * @brief Evaluate log-likelihood function for Poisson statistic and
1511 * binned analysis (version with working arrays)
1512 *
1513 * @param[in] models Models.
1514 * @param[in,out] gradients Gradient.
1515 * @param[in,out] curvature Curvature matrix.
1516 * @param[in,out] npred Number of predicted events.
1517 * @return Likelihood value.
1518 *
1519 * This method evaluates the -(log-likelihood) function for parameter
1520 * optimisation using binned analysis and Poisson statistic.
1521 * The -(log-likelihood) function is given by
1522 *
1523 * \f[
1524 * L=-\sum_i n_i \log e_i - e_i
1525 * \f]
1526 *
1527 * where the sum is taken over all data space bins, \f$n_i\f$ is the
1528 * observed number of counts and \f$e_i\f$ is the model.
1529 * This method also computes the parameter gradients
1530 * \f$\delta L/dp\f$
1531 * and the curvature matrix
1532 * \f$\delta^2 L/dp_1 dp_2\f$
1533 * and also updates the total number of predicted events m_npred.
1534 ***************************************************************************/
1536 GVector* gradients,
1537 GMatrixSparse* curvature,
1538 double* npred) const
1539{
1540 // Initialise likelihood value
1541 double value = 0.0;
1542
1543 // Initialise statistic
1544 #if defined(G_OPT_DEBUG)
1545 int n_bins = 0;
1546 int n_used = 0;
1547 int n_small_model = 0;
1548 int n_zero_data = 0;
1549 int n_exclude_data = 0;
1550 double sum_data = 0.0;
1551 double sum_model = 0.0;
1552 double init_value = value;
1553 #endif
1554
1555 // Get number of events and parameters
1556 int nevents = events()->size();
1557 int npars = gradients->size();
1558
1559 // Allocate some working arrays
1560 int* inx = new int[npars];
1561 double* values = new double[npars];
1562 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1563 double* grads = new double[npars];
1564 #endif
1565 GMatrixSparse wrk_matrix(nevents, npars);
1566 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1567 GVectorSparse wrk_grad(npars);
1568 #else
1569 GVector wrk_grad(npars);
1570 #endif
1571
1572 // Compute model and derivative
1573 GVector model_vector = this->model(models, &wrk_matrix);
1574
1575 // Transpose matrix
1576 wrk_matrix = wrk_matrix.transpose();
1577
1578 // Iterate over all bins
1579 for (int i = 0; i < nevents; ++i) {
1580
1581 // Skip events that should not be used
1582 if (!use_event_for_likelihood(i)) {
1583 continue;
1584 }
1585
1586 // Update number of bins
1587 #if defined(G_OPT_DEBUG)
1588 n_bins++;
1589 #endif
1590
1591 // Get event pointer
1592 const GEventBin* bin =
1593 (*(static_cast<GEventCube*>(const_cast<GEvents*>(events()))))[i];
1594
1595 // Get number of counts in bin
1596 double data = bin->counts();
1597
1598 // Skip bin if data is negative (filtering flag)
1599 if (data < 0) {
1600 #if defined(G_OPT_DEBUG)
1601 n_exclude_data++;
1602 #endif
1603 continue;
1604 }
1605
1606 // Get model value
1607 double model = model_vector[i] * bin->size();
1608
1609 // Skip bin if model is too small (avoids -Inf or NaN gradients)
1610 if (model <= minmod) {
1611 #if defined(G_OPT_DEBUG)
1612 n_small_model++;
1613 #endif
1614 continue;
1615 }
1616
1617 // Update statistic
1618 #if defined(G_OPT_DEBUG)
1619 n_used++;
1620 sum_data += data;
1621 sum_model += model;
1622 #endif
1623
1624 // Update Npred
1625 *npred += model;
1626
1627 // Extract working vector of parameter gradients
1628 wrk_matrix.column_to_vector(i, &wrk_grad);
1629
1630 // Create index array of non-zero derivatives, initialise working
1631 // array, and multiply non-zero derivatives by bin size
1632 int ndev = 0;
1633 double binsize = bin->size();
1634 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1635 for (int ipar = 0; ipar < wrk_grad.elements(); ++ipar) {
1636 values[ipar] = 0.0;
1637 grads[ipar] = wrk_grad.data(ipar);
1638 if (!gammalib::is_infinite(grads[ipar])) {
1639 grads[ipar] *= binsize;
1640 inx[ndev] = wrk_grad.inx(ipar);
1641 ndev++;
1642 }
1643 }
1644 #else
1645 for (int ipar = 0; ipar < npars; ++ipar) {
1646 values[ipar] = 0.0;
1647 if (wrk_grad[ipar] != 0.0 && !gammalib::is_infinite(wrk_grad[ipar])) {
1648 wrk_grad[ipar] *= binsize;
1649 inx[ndev] = ipar;
1650 ndev++;
1651 }
1652 }
1653 #endif
1654
1655 // Update gradient vector and curvature matrix. To avoid
1656 // unneccessary computations we distinguish the case where
1657 // data>0 and data=0. The second case requires much less
1658 // computation since it does not contribute to the curvature
1659 // matrix ...
1660 if (data > 0.0) {
1661
1662 // Update Poissonian statistic (excluding factorial term for
1663 // faster computation)
1664 value -= data * std::log(model) - model;
1665
1666 // Skip bin now if there are no non-zero derivatives
1667 if (ndev < 1) {
1668 continue;
1669 }
1670
1671 // Pre computation
1672 double fb = data / model;
1673 double fc = (1.0 - fb);
1674 double fa = fb / model;
1675
1676 // Loop over columns
1677 for (int jdev = 0; jdev < ndev; ++jdev) {
1678
1679 // Initialise computation
1680 register int jpar = inx[jdev];
1681 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1682 double g = grads[jdev];
1683 #else
1684 double g = wrk_grad[jpar];
1685 #endif
1686 double fa_i = fa * g;
1687
1688 // Update gradient
1689 (*gradients)[jpar] += fc * g;
1690
1691 // Loop over rows
1692 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1693 for (register int idev = 0; idev < ndev; ++idev) {
1694 values[idev] = fa_i * grads[idev];
1695 }
1696 #else
1697 register int* ipar = inx;
1698 for (register int idev = 0; idev < ndev; ++idev, ++ipar) {
1699 values[idev] = fa_i * wrk_grad[*ipar];
1700 }
1701 #endif
1702
1703 // Add column to matrix
1704 curvature->add_to_column(jpar, values, inx, ndev);
1705
1706 } // endfor: looped over columns
1707
1708 } // endif: data was > 0
1709
1710 // ... handle now data=0
1711 else {
1712
1713 // Update statistic
1714 #if defined(G_OPT_DEBUG)
1715 n_zero_data++;
1716 #endif
1717
1718 // Update Poissonian statistic (excluding factorial term for
1719 // faster computation)
1720 value += model;
1721
1722 // Skip bin now if there are no non-zero derivatives
1723 if (ndev < 1) {
1724 continue;
1725 }
1726
1727 // Update gradient
1728 register int* ipar = inx;
1729 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1730 for (register int idev = 0; idev < ndev; ++idev, ++ipar) {
1731 (*gradients)[*ipar] += grads[idev];
1732 }
1733 #else
1734 for (register int idev = 0; idev < ndev; ++idev, ++ipar) {
1735 (*gradients)[*ipar] += wrk_grad[*ipar];
1736 }
1737 #endif
1738
1739 } // endif: data was 0
1740
1741 } // endfor: iterated over all events
1742
1743 // Free temporary memory
1744 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1745 if (grads != NULL) delete [] grads;
1746 #endif
1747 if (values != NULL) delete [] values;
1748 if (inx != NULL) delete [] inx;
1749
1750 // Dump statistic
1751 #if defined(G_OPT_DEBUG)
1752 std::cout << "Number of bins: " << n_bins << std::endl;
1753 std::cout << "Number of bins used for computation: " << n_used << std::endl;
1754 std::cout << "Number of bins excluded from data: " << n_exclude_data << std::endl;
1755 std::cout << "Number of bins excluded due to small model: " << n_small_model << std::endl;
1756 std::cout << "Number of bins with zero data: " << n_zero_data << std::endl;
1757 std::cout << "Sum of data: " << sum_data << std::endl;
1758 std::cout << "Sum of model: " << sum_model << std::endl;
1759 std::cout << "Initial statistic: " << init_value << std::endl;
1760 std::cout << "Statistic: " << value-init_value << std::endl;
1761 #endif
1762
1763 // Return
1764 return value;
1765}
1766
1767
1768/***********************************************************************//**
1769 * @brief Evaluate log-likelihood function for Gaussian statistic and
1770 * binned analysis (version with working arrays)
1771 *
1772 * @param[in] models Models.
1773 * @param[in,out] gradients Gradient.
1774 * @param[in,out] curvature Curvature matrix.
1775 * @param[in,out] npred Number of predicted events.
1776 * @return Likelihood value.
1777 *
1778 * This method evaluates the -(log-likelihood) function for parameter
1779 * optimisation using binned analysis and Poisson statistic.
1780 * The -(log-likelihood) function is given by
1781 *
1782 * \f[
1783 * L = 1/2 \sum_i (n_i - e_i)^2 \sigma_i^{-2}
1784 * \f]
1785 *
1786 * where the sum is taken over all data space bins, \f$n_i\f$ is the
1787 * observed number of counts, \f$e_i\f$ is the model and \f$\sigma_i\f$
1788 * is the statistical uncertainty.
1789 * This method also computes the parameter gradients
1790 * \f$\delta L/dp\f$
1791 * and the curvature matrix
1792 * \f$\delta^2 L/dp_1 dp_2\f$
1793 * and also updates the total number of predicted events m_npred.
1794 ***************************************************************************/
1796 GVector* gradients,
1797 GMatrixSparse* curvature,
1798 double* npred) const
1799{
1800 // Initialise likelihood value
1801 double value = 0.0;
1802
1803 // Get number of events and parameters
1804 int nevents = events()->size();
1805 int npars = gradients->size();
1806
1807 // Allocate some working arrays
1808 int* inx = new int[npars];
1809 double* values = new double[npars];
1810 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1811 double* grads = new double[npars];
1812 #endif
1813 GMatrixSparse wrk_matrix(nevents, npars);
1814 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1815 GVectorSparse wrk_grad(npars);
1816 #else
1817 GVector wrk_grad(npars);
1818 #endif
1819
1820 // Compute model and derivative
1821 GVector model_vector = this->model(models, &wrk_matrix);
1822
1823 // Transpose matrix
1824 wrk_matrix = wrk_matrix.transpose();
1825
1826 // Iterate over all bins
1827 for (int i = 0; i < nevents; ++i) {
1828
1829 // Skip events that should not be used
1830 if (!use_event_for_likelihood(i)) {
1831 continue;
1832 }
1833
1834 // Get event pointer
1835 const GEventBin* bin =
1836 (*(static_cast<GEventCube*>(const_cast<GEvents*>(events()))))[i];
1837
1838 // Get number of counts in bin
1839 double data = bin->counts();
1840
1841 // Skip bin if data is negative (filtering flag)
1842 if (data < 0) {
1843 continue;
1844 }
1845
1846 // Get statistical uncertainty
1847 double sigma = bin->error();
1848
1849 // Skip bin if statistical uncertainty is too small
1850 if (sigma <= minerr) {
1851 continue;
1852 }
1853
1854 // Get model value
1855 double model = model_vector[i] * bin->size();
1856
1857 // Skip bin if model is too small (avoids -Inf or NaN gradients)
1858 if (model <= minmod) {
1859 continue;
1860 }
1861
1862 // Update Npred
1863 *npred += model;
1864
1865 // Extract working vector of parameter gradients
1866 wrk_matrix.column_to_vector(i, &wrk_grad);
1867
1868 // Create index array of non-zero derivatives, initialise working
1869 // array, and multiply non-zero derivatives by bin size
1870 int ndev = 0;
1871 double binsize = bin->size();
1872 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1873 for (int ipar = 0; ipar < wrk_grad.elements(); ++ipar) {
1874 values[ipar] = 0.0;
1875 grads[ipar] = wrk_grad.data(ipar);
1876 if (!gammalib::is_infinite(grads[ipar])) {
1877 grads[ipar] *= binsize;
1878 inx[ndev] = wrk_grad.inx(ipar);
1879 ndev++;
1880 }
1881 }
1882 #else
1883 for (int ipar = 0; ipar < npars; ++ipar) {
1884 values[ipar] = 0.0;
1885 if (wrk_grad[ipar] != 0.0 && !gammalib::is_infinite(wrk_grad[ipar])) {
1886 wrk_grad[ipar] *= binsize;
1887 inx[ndev] = ipar;
1888 ndev++;
1889 }
1890 }
1891 #endif
1892
1893 // Set weight
1894 double weight = 1.0 / (sigma * sigma);
1895
1896 // Update Gaussian statistic
1897 double fa = data - model;
1898 value += 0.5 * (fa * fa * weight);
1899
1900 // Skip bin now if there are no non-zero derivatives
1901 if (ndev < 1) {
1902 continue;
1903 }
1904
1905 // Loop over columns
1906 for (int jdev = 0; jdev < ndev; ++jdev) {
1907
1908 // Initialise computation
1909 register int jpar = inx[jdev];
1910 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1911 double fa_i = grads[jdev] * weight;
1912 #else
1913 double fa_i = wrk_grad[jpar] * weight;
1914 #endif
1915
1916 // Update gradient
1917 (*gradients)[jpar] -= fa * fa_i;
1918
1919 // Loop over rows
1920 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1921 for (register int idev = 0; idev < ndev; ++idev) {
1922 values[idev] = fa_i * grads[idev];
1923 }
1924 #else
1925 register int* ipar = inx;
1926 for (register int idev = 0; idev < ndev; ++idev, ++ipar) {
1927 values[idev] = fa_i * wrk_grad[*ipar];
1928 }
1929 #endif
1930
1931 // Add column to matrix
1932 curvature->add_to_column(jpar, values, inx, ndev);
1933
1934 } // endfor: looped over columns
1935
1936 } // endfor: iterated over all events
1937
1938 // Free temporary memory
1939 #if defined(G_LIKELIHOOD_SPARSE_VECTOR)
1940 if (grads != NULL) delete [] grads;
1941 #endif
1942 if (values != NULL) delete [] values;
1943 if (inx != NULL) delete [] inx;
1944
1945 // Return
1946 return value;
1947}
1948
1949
1950/***********************************************************************//**
1951 * @brief Check whether bin should be used for likelihood analysis
1952 *
1953 * @param[in] index Event index.
1954 * @return True.
1955 *
1956 * This is a dummy virtual method that allows implementation of a hook for
1957 * event selection in the likelihood computation. The dummy method always
1958 * returns true.
1959 ***************************************************************************/
1960bool GObservation::use_event_for_likelihood(const int& index) const
1961{
1962 // Return true
1963 return true;
1964}
1965
1966
1967/*==========================================================================
1968 = =
1969 = Model gradient methods =
1970 = =
1971 ==========================================================================*/
1972
1973/***********************************************************************//**
1974 * @brief Model function evaluation for gradient computation
1975 *
1976 * @param[in] x Function value.
1977 ***************************************************************************/
1978double GObservation::model_func::eval(const double& x)
1979{
1980 // Set value
1981 m_par->factor_value(x);
1982
1983 // Compute model value
1984 double value = m_model->eval(*m_event, *m_parent);
1985
1986 // Return value
1987 return value;
1988}
1989
1990
1991/*==========================================================================
1992 = =
1993 = Npred computation methods =
1994 = =
1995 ==========================================================================*/
1996
1997/***********************************************************************//**
1998 * @brief Npred function evaluation for gradient computation
1999 *
2000 * @param[in] x Function value.
2001 ***************************************************************************/
2002double GObservation::npred_func::eval(const double& x)
2003{
2004 // Set value
2005 m_par->factor_value(x);
2006
2007 // Compute Npred value
2008 double npred = m_parent->npred(*m_model);
2009
2010 // Return value
2011 return npred;
2012}
2013
2014
2015/***********************************************************************//**
2016 * @brief Integration kernel for npred() method
2017 *
2018 * @param[in] x Function value.
2019 ***************************************************************************/
2020double GObservation::npred_kern::eval(const double& x)
2021{
2022 // Convert argument in native reference in seconds
2023 GTime time;
2024 time.secs(x);
2025
2026 // Return value
2027 return (m_parent->npred_spec(*m_model, time));
2028}
2029
2030
2031/***********************************************************************//**
2032 * @brief Integrates spatially integrated Npred kernel spectrally
2033 *
2034 * @param[in] model Gamma-ray source model.
2035 * @param[in] obsTime Measured photon arrival time.
2036 *
2037 * @exception GException::erange_invalid
2038 * Energy range is invalid.
2039 *
2040 * Computes
2041 *
2042 * \f[
2043 * N_{\rm pred}(t') = \int_{E_{\rm bounds}} \int_{\rm ROI}
2044 * P(p',E',t') \, dp' \, dE'
2045 * \f]
2046 *
2047 * of the event probability
2048 *
2049 * \f[
2050 * P(p',E',t') = \int \int \int
2051 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp \, dE \, dt
2052 * \f]
2053 *
2054 * where
2055 * \f$S(p,E,t)\f$ is the source model,
2056 * \f$R(p',E',t'|p,E,t)\f$ is the instrument response function,
2057 * \f$p'\f$ is the measured photon direction,
2058 * \f$E'\f$ is the measured photon energy,
2059 * \f$t'\f$ is the measured photon arrival time,
2060 * \f$p\f$ is the true photon arrival direction,
2061 * \f$E\f$ is the true photon energy, and
2062 * \f$t\f$ is the true photon arrival time.
2063 *
2064 * This method performs the energy intergration over the energy boundaries
2065 * \f$E_{\rm bounds}\f$.
2066 ***************************************************************************/
2068 const GTime& obsTime) const
2069{
2070 // Set number of iterations for Romberg integration.
2071 static const int iter = 8;
2072
2073 // Initialise result
2074 double result = 0.0;
2075
2076 // Get energy boundaries
2077 GEbounds ebounds = events()->ebounds();
2078
2079 // Loop over energy boundaries
2080 for (int i = 0; i < ebounds.size(); ++i) {
2081
2082 // Get boundaries in MeV
2083 double emin = ebounds.emin(i).MeV();
2084 double emax = ebounds.emax(i).MeV();
2085
2086 // Continue only if valid
2087 if (emax > emin) {
2088
2089 // Setup integration function
2090 GObservation::npred_spec_kern integrand(this, &model, &obsTime);
2091 GIntegral integral(&integrand);
2092
2093 // Set number of iterations
2094 integral.fixed_iter(iter);
2095
2096 // Do Romberg integration
2097 #if defined(G_LN_ENERGY_INT)
2098 emin = std::log(emin);
2099 emax = std::log(emax);
2100 #endif
2101 result += integral.romberg(emin, emax);
2102
2103 } // endif: energy interval was valid
2104
2105 } // endfor: looped over energy boundaries
2106
2107 // Compile option: Check for NaN/Inf
2108 #if defined(G_NAN_CHECK)
2109 if (gammalib::is_notanumber(result) || gammalib::is_infinite(result)) {
2110 std::cout << "*** ERROR: GObservation::npred_spec:";
2111 std::cout << " NaN/Inf encountered";
2112 std::cout << " (result=" << result;
2113 std::cout << ")" << std::endl;
2114 }
2115 #endif
2116
2117 // Return result
2118 return result;
2119}
2120
2121
2122/***********************************************************************//**
2123 * @brief Integration kernel for npred_spec() method
2124 *
2125 * @param[in] x Function value.
2126 *
2127 * This method implements the integration kernel needed for the npred_spec()
2128 * method. If G_LN_ENERGY_INT is defined the energy integration is done
2129 * logarithmically, i.e. @p x is given in ln(energy) instead of energy.
2130 ***************************************************************************/
2132{
2133 // Set energy
2134 GEnergy eng;
2135 #if defined(G_LN_ENERGY_INT)
2136 double expx = std::exp(x);
2137 eng.MeV(expx);
2138 #else
2139 eng.MeV(x);
2140 #endif
2141
2142 // Get function value
2143 double value = m_model->npred(eng, *m_time, *m_parent);
2144
2145 // Save value if needed
2146 #if defined(G_NAN_CHECK)
2147 double value_out = value;
2148 #endif
2149
2150 // Correct for variable substitution
2151 #if defined(G_LN_ENERGY_INT)
2152 value *= expx;
2153 #endif
2154
2155 // Compile option: Check for NaN
2156 #if defined(G_NAN_CHECK)
2157 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
2158 std::cout << "*** ERROR: GObservation::npred_spec_kern::eval";
2159 std::cout << "(x=" << x << "): ";
2160 std::cout << " NaN/Inf encountered";
2161 std::cout << " (value=" << value;
2162 std::cout << " (value_out=" << value_out;
2163 #if defined(G_LN_ENERGY_INT)
2164 std::cout << " exp(x)=" << expx;
2165 #endif
2166 std::cout << ")" << std::endl;
2167 }
2168 #endif
2169
2170 // Return value
2171 return value;
2172}
GDerivative class interface definition.
Abstract event bin base class definition.
Abstract event bin container class interface definition.
Abstract event atom container class interface definition.
Exception handler interface definition.
Integration class interface definition.
Sparse matrix class definition.
#define G_NPRED
Abstract data model base class interface definition.
Model parameter class interface definition.
Sky model class interface definition.
Abstract model base class interface definition.
Model container class definition.
#define G_LIKELIHOOD
#define G_EVENTS
#define G_MODEL1
const double minerr
Minimum statistical error.
const double minmod
Minimum model value.
#define G_MODEL2
Abstract observation base class interface definition.
Abstract response base class definition.
Gammalib tools definition.
Sparce vector class interface definition.
Vector class interface definition.
Numerical derivatives class.
double left_difference(const double &x, const double &h)
Returns gradient computed from left-sided function difference.
double right_difference(const double &x, const double &h)
Returns gradient computed from right-sided function difference.
double difference(const double &x, const double &h)
Returns gradient computed from symmetric function difference.
Energy boundaries container class.
Definition GEbounds.hpp:60
const GEnergy & emax(void) const
Return maximum energy of all intervals.
Definition GEbounds.hpp:199
int size(void) const
Return number of energy boundaries.
Definition GEbounds.hpp:163
const GEnergy & emin(void) const
Return minimum energy of all intervals.
Definition GEbounds.hpp:187
Class that handles energies in a unit independent way.
Definition GEnergy.hpp:48
double MeV(void) const
Return energy in MeV.
Definition GEnergy.cpp:342
Abstract interface for the event bin class.
Definition GEventBin.hpp:64
virtual double error(void) const =0
Return error in number of counts.
virtual double counts(void) const =0
virtual double size(void) const =0
Abstract event bin container class.
Abstract event atom container class.
Abstract interface for the event classes.
Definition GEvent.hpp:71
Abstract event container class.
Definition GEvents.hpp:66
virtual double number(void) const =0
void gti(const GGti &gti)
Set Good Time Intervals.
Definition GEvents.cpp:154
void ebounds(const GEbounds &ebounds)
Set energy boundaries.
Definition GEvents.cpp:136
virtual int size(void) const =0
virtual GEvents * clone(void) const =0
Clones object.
GIntegral class interface definition.
Definition GIntegral.hpp:46
double romberg(std::vector< double > bounds, const int &order=5)
Perform Romberg integration.
void fixed_iter(const int &iter)
Set fixed number of iterations.
const int & rows(void) const
Return number of matrix rows.
const int & columns(void) const
Return number of matrix columns.
Sparse matrix class interface definition.
virtual GVector column(const int &column) const
Extract column as vector from matrix.
GMatrixSparse transpose(void) const
Return transposed matrix.
void stack_destroy(void)
Destroy matrix stack.
virtual void add_to_column(const int &column, const GVector &vector)
Add vector column into matrix.
void stack_init(const int &size=0, const int &entries=0)
Initialises matrix filling stack.
void column_to_vector(const int &column, GVector *vector) const
Extract matrix into vector.
void colfill_set_column(const int &column, const GVector &vector)
Set column for column filling.
void colfill_flush(void)
Flush column filling memory into sparse matrix.
void colfill_init(void) const
Initialise memory for column filling.
Model parameter class.
Definition GModelPar.hpp:87
Abstract model class.
Definition GModel.hpp:100
const bool & has_eval_indices(void) const
Signals that parameter indices updated by eval() method were set.
Definition GModel.hpp:412
bool is_valid(const std::string &instruments, const std::string &ids) const
Verifies if model is valid for a given instrument and identifier.
Definition GModel.cpp:585
int size(void) const
Return number of parameters in model.
Definition GModel.hpp:237
virtual double eval(const GEvent &event, const GObservation &obs, const bool &gradients=false) const =0
const std::vector< int > & eval_indices(void) const
Return vector of parameter indices updated by eval() method.
Definition GModel.hpp:424
Model container class.
Definition GModels.hpp:152
int size(void) const
Return number of models in container.
Definition GModels.hpp:260
int npars(void) const
Return number of model parameters in container.
Definition GModels.cpp:853
GModelPar * m_par
Model parameter.
const GModel * m_model
Model.
const GObservation * m_parent
Observation.
double eval(const double &x)
Model function evaluation for gradient computation.
const GEvent * m_event
Event.
double eval(const double &x)
Npred function evaluation for gradient computation.
double eval(const double &x)
Integration kernel for npred() method.
double eval(const double &x)
Integration kernel for npred_spec() method.
Abstract observation base class.
std::string m_statistic
Optimizer statistic.
virtual double ontime(void) const =0
const std::string & statistic(void) const
Return optimizer statistic.
virtual double npred(const GModels &models, GVector *gradients=NULL) const
Return total number (and optionally gradients) of predicted counts for all models.
virtual double model(const GModels &models, const GEvent &event, GVector *gradients=NULL) const
Return model value and (optionally) gradients.
void copy_members(const GObservation &obs)
Copy class members.
const std::string & id(void) const
Return observation identifier.
virtual double likelihood(const GModels &models, GVector *gradients, GMatrixSparse *curvature, double *npred) const
Compute likelihood function.
const std::string & name(void) const
Return observation name.
virtual double nobserved(void) const
Return total number of observed events.
virtual bool use_event_for_likelihood(const int &index) const
Check whether bin should be used for likelihood analysis.
virtual ~GObservation(void)
Destructor.
GObservation(void)
Void constructor.
virtual double likelihood_poisson_unbinned(const GModels &models, GVector *gradients, GMatrixSparse *curvature, double *npred) const
Evaluate log-likelihood function for Poisson statistic and unbinned analysis (version with working ar...
virtual GEvents * events(void)
Return events.
virtual double likelihood_gaussian_binned(const GModels &models, GVector *gradients, GMatrixSparse *curvature, double *npred) const
Evaluate log-likelihood function for Gaussian statistic and binned analysis (version with working arr...
void computed_gradient(const GModel &model, const GModelPar &par) const
Signals that an analytical gradient was computed for a model parameter.
virtual const GResponse * response(void) const =0
bool has_gradient(const GModel &model, const GModelPar &par) const
Check whether a model parameter has an analytical gradient.
void init_members(void)
Initialise class members.
virtual double likelihood_poisson_binned(const GModels &models, GVector *gradients, GMatrixSparse *curvature, double *npred) const
Evaluate log-likelihood function for Poisson statistic and binned analysis (version with working arra...
virtual double npred_spec(const GModel &model, const GTime &obsTime) const
Integrates spatially integrated Npred kernel spectrally.
virtual double grad_step_size(const GModelPar &par) const
Return gradient step size for a given model parameter.
virtual GObservation & operator=(const GObservation &obs)
Assignment operator.
std::vector< std::string > m_pars_with_gradients
GEvents * m_events
Pointer to event container.
virtual std::string instrument(void) const =0
void free_members(void)
Delete class members.
virtual void remove_response_cache(const std::string &name)
Remove response cache for specific model.
std::string m_name
Observation name.
virtual double npred_grad(const GModel &model, const GModelPar &par) const
Returns parameter gradient of Npred.
virtual double model_grad(const GModel &model, const GModelPar &par, const GEvent &event) const
Returns parameter gradient of model for a given event.
std::string m_id
Observation identifier.
const double & factor_max(void) const
Return parameter maximum factor boundary.
const double & factor_value(void) const
Return parameter factor value.
bool is_free(void) const
Signal if parameter is free.
bool has_min(void) const
Signal if parameter has minimum boundary.
const double & factor_gradient(void) const
Return parameter factor gradient.
bool has_max(void) const
Signal if parameter has maximum boundary.
const double & factor_min(void) const
Return parameter minimum factor boundary.
const std::string & name(void) const
Return parameter name.
Abstract instrument response base class.
Definition GResponse.hpp:77
Time class.
Definition GTime.hpp:55
const double & secs(void) const
Return time in seconds in native reference (TT)
Definition GTime.hpp:156
Sparse vector class.
const int & inx(const int &index) const
Return inx for sparse vector element.
const double & data(const int &index) const
Return value for sparse vector element.
const int & elements(void) const
Return number of elements in sparse vector.
Vector class.
Definition GVector.hpp:46
const int & size(void) const
Return size of vector.
Definition GVector.hpp:180
bool is_infinite(const double &x)
Signal if argument is infinite.
Definition GTools.hpp:186
bool is_notanumber(const double &x)
Signal if argument is not a number.
Definition GTools.hpp:203
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition GTools.cpp:508
std::string toupper(const std::string &s)
Convert string to upper case.
Definition GTools.cpp:960