Commit 79188e78f27db4694997e2ec6c9af1e0b81c7547

Authored by Jürgen Knödlseder
1 parent ac820888

Implement model vector evaluation (#3203)

Implemented the following methods:
- GVector GModel::eval(GObservation&, GMatrixSparse*)
- GVector GModelSky::eval(GObservation&, GMatrixSparse*)
- GVector GResponse::convolve(GModelSky&, GObservation&, GMatrixSparse*)
- GVector GObservation::model(GModels&, GMatrixSparse*)
- GVector GObservation::model_grad(GModel&, GModelPar&)
... ... @@ -22,7 +22,11 @@ The following classes have been renamed:
22 22 - none
23 23  
24 24 The following methods have been added:
25   -- none
  25 +- GVector GModel::eval(GObservation&, GMatrixSparse*)
  26 +- GVector GModelSky::eval(GObservation&, GMatrixSparse*)
  27 +- GVector GResponse::convolve(GModelSky&, GObservation&, GMatrixSparse*)
  28 +- GVector GObservation::model(GModels&, GMatrixSparse*)
  29 +- GVector GObservation::model_grad(GModel&, GModelPar&)
26 30  
27 31 The following methods have been removed:
28 32 - none
... ...
include/GModel.hpp
... ... @@ -37,6 +37,8 @@
37 37 #include "GTime.hpp"
38 38  
39 39 /* __ Forward declarations _______________________________________________ */
  40 +class GVector;
  41 +class GMatrixSparse;
40 42 class GEvent;
41 43 class GObservation;
42 44  
... ... @@ -125,6 +127,10 @@ public:
125 127 virtual void write(GXmlElement& xml) const = 0;
126 128 virtual std::string print(const GChatter& chatter = NORMAL) const = 0;
127 129  
  130 + // Virtual methods
  131 + virtual GVector eval(const GObservation& obs,
  132 + GMatrixSparse* gradients = NULL) const;
  133 +
128 134 // Implemented methods
129 135 int size(void) const;
130 136 int scales(void) const;
... ...
include/GModelData.hpp
... ... @@ -77,7 +77,7 @@ public:
77 77 virtual void read(const GXmlElement& xml) = 0;
78 78 virtual void write(GXmlElement& xml) const = 0;
79 79 virtual std::string print(const GChatter& chatter = NORMAL) const = 0;
80   -
  80 +
81 81 protected:
82 82 // Protected methods
83 83 void init_members(void);
... ...
include/GModelSky.hpp
1 1 /***************************************************************************
2 2 * GModelSky.hpp - Sky model class *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2011-2016 by Juergen Knoedlseder *
  4 + * copyright (C) 2011-2020 by Juergen Knoedlseder *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ... @@ -156,6 +156,10 @@ public:
156 156 virtual void write(GXmlElement& xml) const;
157 157 virtual std::string print(const GChatter& chatter = NORMAL) const;
158 158  
  159 + // Reimplemented virtual base class methods
  160 + virtual GVector eval(const GObservation& obs,
  161 + GMatrixSparse* gradients = NULL) const;
  162 +
159 163 // Other methods
160 164 GModelSpatial* spatial(void) const;
161 165 GModelSpectral* spectral(void) const;
... ...
include/GObservation.hpp
... ... @@ -102,6 +102,8 @@ public:
102 102 virtual double model(const GModels& models,
103 103 const GEvent& event,
104 104 GVector* gradient = NULL) const;
  105 + virtual GVector model(const GModels& models,
  106 + GMatrixSparse* gradient = NULL) const;
105 107 virtual int nobserved(void) const;
106 108 virtual double npred(const GModels& models,
107 109 GVector* gradient = NULL) const;
... ... @@ -109,14 +111,12 @@ public:
109 111 virtual double model_grad(const GModel& model,
110 112 const GModelPar& par,
111 113 const GEvent& event) const;
  114 + virtual GVector model_grad(const GModel& model,
  115 + const GModelPar& par) const;
112 116 virtual double npred_grad(const GModel& model,
113 117 const GModelPar& par) const;
114 118 virtual void remove_response_cache(const std::string& name);
115 119  
116   - // New methods
117   - virtual GVector model(const GModels& models,
118   - GMatrixSparse* gradient = NULL) const;
119   -
120 120 // Implemented methods
121 121 bool has_events(void) const;
122 122 void name(const std::string& name);
... ...
include/GResponse.hpp
... ... @@ -37,7 +37,9 @@
37 37 #include "GNdarray.hpp"
38 38  
39 39 /* __ Forward declarations _______________________________________________ */
  40 +class GVector;
40 41 class GMatrix;
  42 +class GMatrixSparse;
41 43 class GEvent;
42 44 class GPhoton;
43 45 class GSource;
... ... @@ -104,6 +106,9 @@ public:
104 106 const GEvent& event,
105 107 const GObservation& obs,
106 108 const bool& grad = true) const;
  109 + virtual GVector convolve(const GModelSky& model,
  110 + const GObservation& obs,
  111 + GMatrixSparse* gradients = NULL) const;
107 112 virtual double irf_spatial(const GEvent& event,
108 113 const GSource& source,
109 114 const GObservation& obs) const;
... ...
src/model/GModel.cpp
... ... @@ -31,9 +31,13 @@
31 31 #include "GTools.hpp"
32 32 #include "GException.hpp"
33 33 #include "GModel.hpp"
  34 +#include "GVector.hpp"
  35 +#include "GMatrixSparse.hpp"
  36 +#include "GObservation.hpp"
34 37  
35 38 /* __ Method name definitions ____________________________________________ */
36 39 #define G_ACCESS "GModel::operator[](std::string&)"
  40 +#define G_EVAL "GModel::eval(GObservation&, GMatrixSparse*)"
37 41 #define G_AT "GModel::at(int&)"
38 42 #define G_SCALE "GModelPar& GModel::scale(int&)"
39 43 #define G_WRITE_SCALES "GModel::write_scales(GXmlElement&)"
... ... @@ -221,6 +225,91 @@ const GModelPar& GModel::operator[](const std::string& name) const
221 225 ==========================================================================*/
222 226  
223 227 /***********************************************************************//**
  228 + * @brief Return model values and gradients
  229 + *
  230 + * @param[in] obs Observation.
  231 + * @param[out] gradients Pointer to matrix of gradients.
  232 + * @return Model values.
  233 + *
  234 + * Evaluates the model values and parameter gradients for all events in an
  235 + * observation. Gradients are only returned if the @p gradients pointer is
  236 + * not NULL.
  237 + *
  238 + * The matrix of gradients is a sparse matrix where the number of rows
  239 + * corresponds to the number of model parameters and the number of columns
  240 + * corresponds to the number of events. An exception is thrown if the
  241 + * dimension of the @p gradients matrix is not compatible with the model
  242 + * and the observations.
  243 + ***************************************************************************/
  244 +GVector GModel::eval(const GObservation& obs,
  245 + GMatrixSparse* gradients) const
  246 +{
  247 + // Get number of model parameters and number of events
  248 + int npars = size();
  249 + int nevents = obs.events()->size();
  250 +
  251 + // Initialise gradients flag
  252 + bool grad = (gradients != NULL);
  253 +
  254 + // Check matrix consistency
  255 + if (grad) {
  256 + if (gradients->rows() != npars) {
  257 + std::string msg = "Number of "+gammalib::str(gradients->rows())+
  258 + " rows in gradient matrix differs from number "
  259 + "of "+gammalib::str(npars)+" parameters "
  260 + "in model. Please provide a compatible gradient "
  261 + "matrix.";
  262 + throw GException::invalid_argument(G_EVAL, msg);
  263 + }
  264 + if (gradients->columns() != nevents) {
  265 + std::string msg = "Number of "+gammalib::str(gradients->columns())+
  266 + " columns in gradient matrix differs from number "
  267 + "of "+gammalib::str(nevents)+" events in "
  268 + "observation. Please provide a compatible "
  269 + "gradient matrix.";
  270 + throw GException::invalid_argument(G_EVAL, msg);
  271 + }
  272 + }
  273 +
  274 + // Initialise values
  275 + GVector values(nevents);
  276 +
  277 + // Loop over events
  278 + for (int k = 0; k < nevents; ++k) {
  279 +
  280 + // Get reference to event
  281 + const GEvent& event = *((*obs.events())[k]);
  282 +
  283 + // Evaluate probability
  284 + values[k] = eval(event, obs, grad);
  285 +
  286 + // Set gradients
  287 + if (grad) {
  288 +
  289 + // Initialise gradient vector
  290 + GVector gradient(npars);
  291 +
  292 + // Extract relevant parameter gradients
  293 + for (int i = 0; i < npars; ++i) {
  294 + const GModelPar& par = (*this)[i];
  295 + if (par.is_free() && par.has_grad()) {
  296 + gradient[i] = par.factor_gradient();
  297 + }
  298 + }
  299 +
  300 + // Insert gradient vector into matrix
  301 + gradients->add_to_column(k, gradient);
  302 +
  303 + } // endif: looped over gradients
  304 +
  305 + } // endfor: looped over events
  306 +
  307 + // Return values
  308 + return values;
  309 +}
  310 +
  311 +
  312 +/***********************************************************************//**
224 313 * @brief Returns reference to model parameter by index
225 314 *
226 315 * @param[in] index Parameter index [0,...,size()-1].
... ... @@ -298,6 +387,8 @@ bool GModel::has_par(const std::string&amp; name) const
298 387 /***********************************************************************//**
299 388 * @brief Returns instruments to which model applies
300 389 *
  390 + * @return Instruments.
  391 + *
301 392 * Returns a comma separated list of instruments to which model applies. If
302 393 * no instrument exists then an empty string is returned.
303 394 ***************************************************************************/
... ... @@ -485,6 +576,8 @@ void GModel::scale(const GModelPar&amp; par)
485 576 /***********************************************************************//**
486 577 * @brief Returns observation identifiers to which model applies
487 578 *
  579 + * @return Observation identifiers.
  580 + *
488 581 * Returns a comma separated list of observation identifiers to which model
489 582 * applies. If no observation identifier exists then an empty string is
490 583 * returned.
... ... @@ -542,7 +635,7 @@ void GModel::ids(const std::string&amp; ids)
542 635 *
543 636 * @param[in] instrument Instrument name.
544 637 * @param[in] id Observation identifier.
545   - * @return Validity flag
  638 + * @return Validity flag.
546 639 *
547 640 * Checks if specified instrument name and observation identifier is in list
548 641 * of applicable instruments and identifiers. The check is case sensitive.
... ...
src/model/GModelSky.cpp
... ... @@ -574,6 +574,30 @@ double GModelSky::eval(const GEvent&amp; event,
574 574  
575 575  
576 576 /***********************************************************************//**
  577 + * @brief Evaluate sky model for all events of an observation
  578 + *
  579 + * @param[in] obs Observation.
  580 + * @param[out] gradients Pointer to sparse matrix holding model gradients.
  581 + * @return Values of sky model.
  582 + *
  583 + * Evalutes the value of the sky model for an @p event of a specific
  584 + * observation @p obs.
  585 + *
  586 + * If the @p gradients flag is true the method will also compute the
  587 + * parameter gradients for all model parameters.
  588 + ***************************************************************************/
  589 +GVector GModelSky::eval(const GObservation& obs,
  590 + GMatrixSparse* gradients) const
  591 +{
  592 + // Evaluate model
  593 + GVector values = obs.response()->convolve(*this, obs, gradients);
  594 +
  595 + // Return
  596 + return values;
  597 +}
  598 +
  599 +
  600 +/***********************************************************************//**
577 601 * @brief Return spatially integrated sky model
578 602 *
579 603 * @param[in] obsEng Measured photon energy.
... ... @@ -595,7 +619,7 @@ double GModelSky::eval(const GEvent&amp; event,
595 619 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp \, dE \, dt
596 620 * \f]
597 621 *
598   - * where
  622 + * where
599 623 * \f$S(p,E,t)\f$ is the source model,
600 624 * \f$R(p',E',t'|p,E,t)\f$ is the instrument response function,
601 625 * \f$p'\f$ is the measured photon direction,
... ... @@ -1118,7 +1142,7 @@ void GModelSky::copy_members(const GModelSky&amp; model)
1118 1142 {
1119 1143 // Copy attributes
1120 1144 m_type = model.m_type;
1121   -
  1145 +
1122 1146 // Clone model components
1123 1147 m_spatial = (model.m_spatial != NULL) ? model.m_spatial->clone() : NULL;
1124 1148 m_spectral = (model.m_spectral != NULL) ? model.m_spectral->clone() : NULL;
... ... @@ -1218,12 +1242,12 @@ void GModelSky::set_type(void)
1218 1242  
1219 1243 // Continue only if we have a spatial model component
1220 1244 if (m_spatial != NULL) {
1221   -
  1245 +
1222 1246 // Is spatial model a point source?
1223 1247 if (dynamic_cast<const GModelSpatialPointSource*>(m_spatial) != NULL) {
1224 1248 m_type = "PointSource";
1225 1249 }
1226   -
  1250 +
1227 1251 // ... otherwise, is spatial model a radial source?
1228 1252 else if (dynamic_cast<const GModelSpatialRadial*>(m_spatial) != NULL) {
1229 1253 m_type = "ExtendedSource";
... ... @@ -1243,7 +1267,7 @@ void GModelSky::set_type(void)
1243 1267 else {
1244 1268 m_type = "DiffuseSource";
1245 1269 }
1246   -
  1270 +
1247 1271 } // endif: there was a spatial model component
1248 1272  
1249 1273 // Return
... ...
src/obs/GObservation.cpp
... ... @@ -48,8 +48,8 @@
48 48 /* __ Method name definitions ____________________________________________ */
49 49 #define G_LIKELIHOOD "GObservation::likelihood(GModels&, GVector*,"\
50 50 " GMatrixSparse*, double*)"
51   -#define G_MODEL "GObservation::model(GModels&, GPointing&,"\
52   - " GInstDir&, GEnergy&, GTime&, GVector*)"
  51 +#define G_MODEL1 "GObservation::model(GModels&, GEvent&, GVector*)"
  52 +#define G_MODEL2 "GObservation::model(GModels&, GMatrixSparse*)"
53 53 #define G_EVENTS "GObservation::events()"
54 54 #define G_NPRED "GObservation::npred(GModel&)"
55 55 #define G_NPRED_SPEC "GObservation::npred_spec(GModel&, GTime&)"
... ... @@ -248,6 +248,7 @@ double GObservation::likelihood(const GModels&amp; models,
248 248 * @param[in] models Model container.
249 249 * @param[in] event Observed event.
250 250 * @param[out] gradient Pointer to gradient vector (optional).
  251 + * @return Model value.
251 252 *
252 253 * @exception GException::invalid_value
253 254 * Dimension of gradient vector mismatches number of parameters.
... ... @@ -306,7 +307,7 @@ double GObservation::model(const GModels&amp; models,
306 307 gammalib::str(models.npars())+" elements "
307 308 "requested while vector only contains "+
308 309 gammalib::str(gradient->size())+" elements.";
309   - throw GException::invalid_value(G_MODEL, msg);
  310 + throw GException::invalid_value(G_MODEL1, msg);
310 311 }
311 312 #endif
312 313  
... ... @@ -390,41 +391,55 @@ double GObservation::model(const GModels&amp; models,
390 391 * @brief Return model values and (optionally) gradients
391 392 *
392 393 * @param[in] models Model container.
393   - * @param[out] gradient Pointer to gradient vector (optional).
  394 + * @param[out] gradient Pointer to sparse gradient matrix.
  395 + * @return Vector of model values.
394 396 *
395 397 * @exception GException::invalid_argument
396 398 * Gradient matrix mismatches number of events or model parameters.
  399 + *
  400 + * Returns the model values for each event in the observation.
  401 + *
  402 + * If @p gradient is not NULL, the matrix contains on output the model
  403 + * factor gradients for all events. Each row of the @p gradient matrix
  404 + * corresponds to one event, the columns correspond to the parameters
  405 + * of the @p models container.
397 406 ***************************************************************************/
398   -#define G_MODEL2 "GObservation::model(GModels&, GMatrixSparse*)"
399 407 GVector GObservation::model(const GModels& models,
400 408 GMatrixSparse* gradient) const
401 409 {
402   - // Initialise model values
403   - GVector model(events()->size());
404   -
405   - // Set gradient usage flag and initialise gradient counter
  410 + // Initialise variables
  411 + int nevents = events()->size();
406 412 bool use_grad = (gradient != NULL);
407   - int igrad = 0; // Reset gradient counter
  413 + int igrad = 0;
408 414  
409   - // If gradient is available then gradient size
  415 + // Initialise model values
  416 + GVector values(nevents);
  417 +
  418 + // If gradient is available then check gradient size and initialise
  419 + // sparse matrix stack
410 420 if (use_grad) {
411   - if (gradient->columns() != models.size()) {
  421 +
  422 + // Check number of columns
  423 + if (gradient->columns() != models.npars()) {
412 424 std::string msg = "Number of "+gammalib::str(gradient->columns())+
413 425 " columns in gradient matrix differs from number "
414   - "of "+gammalib::str(models.size())+" parameters "
415   - "in model container Please specify compatible "
  426 + "of "+gammalib::str(models.npars())+" parameters "
  427 + "in model container. Please specify compatible "
416 428 "arguments.";
417 429 throw GException::invalid_argument(G_MODEL2, msg);
418 430 }
419   - if (gradient->rows() != events()->size()) {
  431 +
  432 + // Check number of rows
  433 + if (gradient->rows() != nevents) {
420 434 std::string msg = "Number of "+gammalib::str(gradient->rows())+
421 435 " rows in gradient matrix differs from number "
422   - "of "+gammalib::str(events()->size())+" events "
  436 + "of "+gammalib::str(nevents)+" events "
423 437 "in observation. Please specify compatible "
424 438 "arguments.";
425 439 throw GException::invalid_argument(G_MODEL2, msg);
426 440 }
427   - }
  441 +
  442 + } // endif: Gradient requested
428 443  
429 444 // Loop over models
430 445 for (int i = 0; i < models.size(); ++i) {
... ... @@ -437,83 +452,91 @@ GVector GObservation::model(const GModels&amp; models,
437 452 // observation identifier
438 453 if (mptr->is_valid(instrument(), id())) {
439 454  
440   - // Loop over events
441   - for (int k = 0; k < events()->size(); ++k) {
  455 + // If no gradients should be used then evaluate model and
  456 + // add it to values
  457 + if (!use_grad) {
  458 + values += mptr->eval(*this, NULL);
  459 + }
442 460  
443   - // Get reference to event
444   - const GEvent& event = *((*events())[k]);
  461 + // ... otherwise evaluate model and gradients and add to
  462 + // model vector and gradient matrix
  463 + else {
445 464  
446   - // Compute value and add to model
447   - model[k] += mptr->eval(event, *this, use_grad);
  465 + // Allocate gradient matrix
  466 + GMatrixSparse gradients(mptr->size(), nevents);
448 467  
449   - // Optionally determine model gradients
450   - if (use_grad) {
  468 + // Initialise sparse matrix stack
  469 + gradients.stack_init(mptr->size()*1000, 1000);
451 470  
452   - // If model provides the parameter indices that were
453   - // updated by the eval() method then use them as this
454   - // is less time consuming than looping over all indices
455   - if (mptr->has_eval_indices()) {
  471 + // Evaluate model and add to values
  472 + values += mptr->eval(*this, &gradients);
456 473  
457   - // Get number of relevant parameters
458   - int npars = mptr->eval_indices().size();
  474 + // Destroy sparse matrix stack (fills stack in matrix)
  475 + gradients.stack_destroy();
459 476  
460   - // Loop over all relevant parameters
461   - for (int index = 0; index < npars; ++index) {
  477 + // If model provides the parameter indices that were
  478 + // updated by the eval() method then use them as this
  479 + // is less time consuming than looping over all indices
  480 + if (mptr->has_eval_indices()) {
462 481  
463   - // Retrieve parameter index
464   - int ipar = mptr->eval_indices()[index];
  482 + // Get number of relevant parameters
  483 + int npars = mptr->eval_indices().size();
465 484  
466   - // Get reference to model parameter
467   - const GModelPar& par = (*mptr)[ipar];
  485 + // Loop over all relevant parameters
  486 + for (int index = 0; index < npars; ++index) {
  487 +
  488 + // Retrieve parameter index
  489 + int ipar = mptr->eval_indices()[index];
468 490  
469   - // Determine gradient
470   - double grad = 0.0;
471   - if (par.is_free()) {
472   - if (par.has_grad()) {
473   - grad = par.factor_gradient();
474   - }
475   - else {
476   - grad = model_grad(*mptr, par, event);
477   - }
  491 + // Get reference to model parameter
  492 + const GModelPar& par = (*mptr)[ipar];
  493 +
  494 + // Determine gradient
  495 + GVector grad(nevents);
  496 + if (par.is_free()) {
  497 + if (par.has_grad()) {
  498 + grad = gradients.row(ipar);
  499 + }
  500 + else {
  501 + grad = model_grad(*mptr, par);
478 502 }
  503 + }
479 504  
480   - // Set gradient
481   - (*gradient)(k,igrad+ipar) = grad;
  505 + // Set gradient
  506 + gradient->column(igrad+ipar, grad);
482 507  
483   - } // endfor: looped over all parameter indices
  508 + } // endfor: looped over all parameter indices
484 509  
485   - } // endif: parameter indices were available
  510 + } // endif: parameter indices were available
486 511  
487   - // ... otherwise loop over all parameter indices
488   - else {
  512 + // ... otherwise loop over all parameter indices
  513 + else {
489 514  
490   - // Loop over all parameters
491   - for (int ipar = 0; ipar < mptr->size(); ++ipar) {
  515 + // Loop over all parameters
  516 + for (int ipar = 0; ipar < mptr->size(); ++ipar) {
492 517  
493   - // Get reference to model parameter
494   - const GModelPar& par = (*mptr)[ipar];
  518 + // Get reference to model parameter
  519 + const GModelPar& par = (*mptr)[ipar];
495 520  
496   - // Determine gradient
497   - double grad = 0.0;
498   - if (par.is_free()) {
499   - if (par.has_grad()) {
500   - grad = par.factor_gradient();
501   - }
502   - else {
503   - grad = model_grad(*mptr, par, event);
504   - }
  521 + // Determine gradient
  522 + GVector grad(nevents);
  523 + if (par.is_free()) {
  524 + if (par.has_grad()) {
  525 + grad = gradients.row(ipar);
505 526 }
  527 + else {
  528 + grad = model_grad(*mptr, par);
  529 + }
  530 + }
506 531  
507   - // Set gradient
508   - (*gradient)(k,igrad+ipar) = grad;
509   -
510   - } // endfor: looped over all parameter indices
  532 + // Set gradient
  533 + gradient->column(igrad+ipar, grad);
511 534  
512   - } // endelse: no parameter indices were available
  535 + } // endfor: looped over all parameter indices
513 536  
514   - } // endif: use model gradients
  537 + } // endelse: no parameter indices were available
515 538  
516   - } // endfor: loop over events
  539 + } // endif: use model gradients
517 540  
518 541 } // endif: model component was valid for instrument
519 542  
... ... @@ -524,8 +547,8 @@ GVector GObservation::model(const GModels&amp; models,
524 547  
525 548 } // endfor: Looped over models
526 549  
527   - // Return
528   - return model;
  550 + // Return values
  551 + return values;
529 552 }
530 553  
531 554  
... ... @@ -819,6 +842,123 @@ double GObservation::model_grad(const GModel&amp; model,
819 842  
820 843  
821 844 /***********************************************************************//**
  845 + * @brief Returns parameter gradients of model for all events
  846 + *
  847 + * @param[in] model Model.
  848 + * @param[in] par Model parameter.
  849 + *
  850 + * This method uses a robust but simple difference method to estimate
  851 + * parameter gradients that have not been provided by the model. We use here
  852 + * a simple method as this method is likely used for spatial model parameters,
  853 + * and the spatial model may eventually be noisy due to numerical integration
  854 + * limits.
  855 + *
  856 + * The step size for the simple method has been fixed to 0.0002, which
  857 + * corresponds to about 1 arcsec for parameters that are given in degrees.
  858 + * The reasoning behind this value is that parameters that use numerical
  859 + * gradients are typically angles, such as for example the position, and
  860 + * we want to achieve arcsec precision with this method.
  861 + ***************************************************************************/
  862 +GVector GObservation::model_grad(const GModel& model,
  863 + const GModelPar& par) const
  864 +{
  865 + // Initialise gradients
  866 + GVector gradients(events()->size());
  867 +
  868 + // Compute gradient only if parameter is free
  869 + if (par.is_free()) {
  870 +
  871 + // Get non-const model pointer
  872 + GModelPar* ptr = const_cast<GModelPar*>(&par);
  873 +
  874 + // Save current model parameter
  875 + GModelPar current = par;
  876 +
  877 + // Get actual parameter value
  878 + double x = par.factor_value();
  879 +
  880 + // Set fixed step size for computation of derivative.
  881 + // By default, the step size is fixed to 0.0002.
  882 + const double step_size = 0.0002; // ~1 arcsec
  883 + double h = step_size;
  884 +
  885 + // Re-adjust the step-size h in case that the initial step size is
  886 + // larger than the allowed parameter range
  887 + if (par.has_min() && par.has_max()) {
  888 + double par_h = par.factor_max() - par.factor_min();
  889 + if (par_h < h) {
  890 + h = par_h;
  891 + }
  892 + }
  893 +
  894 + // Continue only if step size is positive
  895 + if (h > 0.0) {
  896 +
  897 + // Setup derivative function
  898 + //GObservation::model_func function(this, &model, ptr, &event);
  899 + //GDerivative derivative(&function);
  900 +
  901 + // If we are too close to the minimum boundary use a right sided
  902 + // difference ...
  903 + if (par.has_min() && ((x-par.factor_min()) < h)) {
  904 + //grad = derivative.right_difference(x, h);
  905 + // Compute fs1
  906 + ptr->factor_value(x);
  907 + GVector fs1 = model.eval(*this, NULL);
  908 +
  909 + // Compute fs2
  910 + ptr->factor_value(x+h);
  911 + GVector fs2 = model.eval(*this, NULL);
  912 +
  913 + // Compute derivative
  914 + gradients = (fs1 - fs2) / h;
  915 +
  916 + }
  917 +
  918 + // ... otherwise if we are too close to the maximum boundary use
  919 + // a left sided difference ...
  920 + else if (par.has_max() && ((par.factor_max()-x) < h)) {
  921 + //grad = derivative.left_difference(x, h);
  922 + // Compute fs1
  923 + ptr->factor_value(x);
  924 + GVector fs1 = model.eval(*this, NULL);
  925 +
  926 + // Compute fs2
  927 + ptr->factor_value(x-h);
  928 + GVector fs2 = model.eval(*this, NULL);
  929 +
  930 + // Compute derivative
  931 + gradients = (fs1 - fs2) / h;
  932 + }
  933 +
  934 + // ... otherwise use a symmetric difference
  935 + else {
  936 + //grad = derivative.difference(x, h);
  937 + // Compute fs1
  938 + ptr->factor_value(x+h);
  939 + GVector fs1 = model.eval(*this, NULL);
  940 +
  941 + // Compute fs2
  942 + ptr->factor_value(x-h);
  943 + GVector fs2 = model.eval(*this, NULL);
  944 +
  945 + // Compute derivative
  946 + gradients = 0.5 * (fs1 - fs2) / h;
  947 + }
  948 +
  949 + } // endif: step size was positive
  950 +
  951 + // Restore current model parameter
  952 + *ptr = current;
  953 +
  954 + } // endif: model parameter was free
  955 +
  956 + // Return gradients
  957 + return gradients;
  958 +}
  959 +
  960 +
  961 +/***********************************************************************//**
822 962 * @brief Returns parameter gradient of Npred
823 963 *
824 964 * @param[in] model Gamma-ray source model.
... ... @@ -1159,7 +1299,7 @@ double GObservation::likelihood_poisson_unbinned(const GModels&amp; models,
1159 1299  
1160 1300 // Update Poissonian statistic (excluding factorial term for faster
1161 1301 // computation)
1162   - value -= log(model);
  1302 + value -= std::log(model);
1163 1303  
1164 1304 // Skip bin now if there are no non-zero derivatives
1165 1305 if (ndev < 1) {
... ... @@ -1255,10 +1395,10 @@ double GObservation::likelihood_poisson_binned(const GModels&amp; models,
1255 1395 // Allocate some working arrays
1256 1396 int* inx = new int[npars];
1257 1397 double* values = new double[npars];
1258   - GMatrixSparse wrk_grad(nevents, npars);
  1398 + GMatrixSparse wrk_matrix(nevents, npars);
1259 1399  
1260 1400 // Compute model and derivative
1261   - GVector model_vector = this->model(models, &wrk_grad);
  1401 + GVector model_vector = this->model(models, &wrk_matrix);
1262 1402  
1263 1403 // Iterate over all bins
1264 1404 for (int i = 0; i < events()->size(); ++i) {
... ... @@ -1307,15 +1447,15 @@ double GObservation::likelihood_poisson_binned(const GModels&amp; models,
1307 1447 // Update Npred
1308 1448 *npred += model;
1309 1449  
1310   - // Multiply gradient by bin size
1311   - //wrk_grad *= bin->size();
  1450 + // Extract working gradient multiplied by bin size
  1451 + GVector wrk_grad = wrk_matrix.row(i) * bin->size();
1312 1452  
1313 1453 // Create index array of non-zero derivatives and initialise working
1314 1454 // array
1315 1455 int ndev = 0;
1316 1456 for (int k = 0; k < npars; ++k) {
1317 1457 values[k] = 0.0;
1318   - if (wrk_grad(i,k) != 0.0 && !gammalib::is_infinite(wrk_grad(i,k))) {
  1458 + if (wrk_grad[k] != 0.0 && !gammalib::is_infinite(wrk_grad[k])) {
1319 1459 inx[ndev] = k;
1320 1460 ndev++;
1321 1461 }
... ... @@ -1330,7 +1470,7 @@ double GObservation::likelihood_poisson_binned(const GModels&amp; models,
1330 1470  
1331 1471 // Update Poissonian statistic (excluding factorial term for
1332 1472 // faster computation)
1333   - value -= data * log(model) - model;
  1473 + value -= data * std::log(model) - model;
1334 1474  
1335 1475 // Skip bin now if there are no non-zero derivatives
1336 1476 if (ndev < 1) {
... ... @@ -1347,8 +1487,8 @@ double GObservation::likelihood_poisson_binned(const GModels&amp; models,
1347 1487  
1348 1488 // Initialise computation
1349 1489 register int jpar = inx[jdev];
1350   - double g = wrk_grad(i,jpar) * bin->size();
1351   - double fa_i = fa * g * bin->size();
  1490 + double g = wrk_grad[jpar];
  1491 + double fa_i = fa * g;
1352 1492  
1353 1493 // Update gradient
1354 1494 (*gradient)[jpar] += fc * g;
... ... @@ -1356,7 +1496,7 @@ double GObservation::likelihood_poisson_binned(const GModels&amp; models,
1356 1496 // Loop over rows
1357 1497 register int* ipar = inx;
1358 1498 for (register int idev = 0; idev < ndev; ++idev, ++ipar) {
1359   - values[idev] = fa_i * wrk_grad(i,*ipar);
  1499 + values[idev] = fa_i * wrk_grad[*ipar];
1360 1500 }
1361 1501  
1362 1502 // Add column to matrix
... ... @@ -1386,7 +1526,7 @@ double GObservation::likelihood_poisson_binned(const GModels&amp; models,
1386 1526 // Update gradient
1387 1527 register int* ipar = inx;
1388 1528 for (register int idev = 0; idev < ndev; ++idev, ++ipar) {
1389   - (*gradient)[*ipar] += wrk_grad(i,*ipar) * bin->size();
  1529 + (*gradient)[*ipar] += wrk_grad[*ipar];
1390 1530 }
1391 1531  
1392 1532 } // endif: data was 0
... ...
src/obs/GObservations.cpp
1 1 /***************************************************************************
2 2 * GObservations.cpp - Observation container class *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2009-2019 by Juergen Knoedlseder *
  4 + * copyright (C) 2009-2020 by Juergen Knoedlseder *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ... @@ -31,6 +31,8 @@
31 31 #include "GTools.hpp"
32 32 #include "GException.hpp"
33 33 #include "GFilename.hpp"
  34 +#include "GXml.hpp"
  35 +#include "GModel.hpp"
34 36 #include "GObservations.hpp"
35 37 #include "GObservationRegistry.hpp"
36 38 #include "GMatrixSparse.hpp"
... ...
src/obs/GObservations_likelihood.cpp
1 1 /***************************************************************************
2 2 * GObservations_likelihood.cpp - Likelihood function class *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2009-2017 by Juergen Knoedlseder *
  4 + * copyright (C) 2009-2020 by Juergen Knoedlseder *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ... @@ -31,6 +31,7 @@
31 31 #include "GObservations.hpp"
32 32 #include "GTools.hpp"
33 33 #include "GFilename.hpp"
  34 +#include "GModel.hpp"
34 35 #include "GEvent.hpp"
35 36 #include "GEventList.hpp"
36 37 #include "GEventCube.hpp"
... ...
src/obs/GResponse.cpp
... ... @@ -35,6 +35,7 @@
35 35 #include "GMath.hpp"
36 36 #include "GVector.hpp"
37 37 #include "GMatrix.hpp"
  38 +#include "GMatrixSparse.hpp"
38 39 #include "GIntegral.hpp"
39 40 #include "GIntegrals.hpp"
40 41 #include "GResponse.hpp"
... ... @@ -56,6 +57,8 @@
56 57 #include "GModelSpatialComposite.hpp"
57 58  
58 59 /* __ Method name definitions ____________________________________________ */
  60 +#define G_CONVOLVE "GResponse::convolve(GModelSky&, GObservation&, "\
  61 + "GMatrixSparse*)"
59 62 #define G_IRF_RADIAL "GResponse::irf_radial(GEvent&, GSource&,"\
60 63 " GObservation&)"
61 64 #define G_IRF_ELLIPTICAL "GResponse::irf_elliptical(GEvent&, GSource&,"\
... ... @@ -311,6 +314,254 @@ double GResponse::convolve(const GModelSky&amp; model,
311 314  
312 315  
313 316 /***********************************************************************//**
  317 + * @brief Convolve sky model with the instrument response
  318 + *
  319 + * @param[in] model Sky model.
  320 + * @param[in] obs Observation.
  321 + * @param[out] gradients Pointer to matrix of gradients.
  322 + * @return Event probabilities.
  323 + *
  324 + * Computes the event probability
  325 + *
  326 + * \f[
  327 + * P(p',E',t') = \int \int \int
  328 + * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp \, dE \, dt
  329 + * \f]
  330 + *
  331 + * without taking into account any time dispersion. Energy dispersion is
  332 + * correctly handled by this method. If time dispersion is indeed needed,
  333 + * an instrument specific method needs to be provided.
  334 + ***************************************************************************/
  335 +GVector GResponse::convolve(const GModelSky& model,
  336 + const GObservation& obs,
  337 + GMatrixSparse* gradients) const
  338 +{
  339 + // Set number of iterations for Romberg integration.
  340 + static const int iter = 6;
  341 +
  342 + // Get number of model parameters and number of events
  343 + int npars = model.size();
  344 + int nevents = obs.events()->size();
  345 +
  346 + // Initialise gradients flag
  347 + bool grad = (gradients != NULL);
  348 +
  349 + // Check matrix consistency
  350 + if (grad) {
  351 + if (gradients->rows() != npars) {
  352 + std::string msg = "Number of "+gammalib::str(gradients->rows())+
  353 + " rows in gradient matrix differs from number "
  354 + "of "+gammalib::str(npars)+" parameters "
  355 + "in model. Please provide a compatible gradient "
  356 + "matrix.";
  357 + throw GException::invalid_argument(G_CONVOLVE, msg);
  358 + }
  359 + if (gradients->columns() != nevents) {
  360 + std::string msg = "Number of "+gammalib::str(gradients->columns())+
  361 + " columns in gradient matrix differs from number "
  362 + "of "+gammalib::str(nevents)+" events in "
  363 + "observation. Please provide a compatible "
  364 + "gradient matrix.";
  365 + throw GException::invalid_argument(G_CONVOLVE, msg);
  366 + }
  367 + }
  368 +
  369 + // Initialise result
  370 + GVector probs(nevents);
  371 +
  372 + // Continue only if the model has a spatial component
  373 + if (model.spatial() != NULL) {
  374 +
  375 + // Loop over events
  376 + for (int k = 0; k < nevents; ++k) {
  377 +
  378 + // Get reference to event
  379 + const GEvent& event = *((*obs.events())[k]);
  380 +
  381 + // Get source time (no dispersion)
  382 + GTime srcTime = event.time();
  383 +
  384 + // Case A: Integration
  385 + if (use_edisp()) {
  386 +
  387 + // Retrieve true energy boundaries
  388 + GEbounds ebounds = this->ebounds(event.energy());
  389 +
  390 + // Initialise Ndarray array
  391 + GNdarray array;
  392 +
  393 + // Loop over all boundaries
  394 + for (int i = 0; i < ebounds.size(); ++i) {
  395 +
  396 + // Get true energy boundaries in MeV
  397 + double etrue_min = ebounds.emin(i).MeV();
  398 + double etrue_max = ebounds.emax(i).MeV();
  399 +
  400 + // Continue only if valid
  401 + if (etrue_max > etrue_min) {
  402 +
  403 + // Setup integration function
  404 + edisp_kern integrand(this, &obs, &model, &event, srcTime, grad);
  405 + GIntegrals integral(&integrand);
  406 +
  407 + // Set number of iterations
  408 + integral.fixed_iter(iter);
  409 +
  410 + // Do Romberg integration
  411 + if (array.size() == 0) {
  412 + array = integral.romberg(etrue_min, etrue_max);
  413 + }
  414 + else {
  415 + array += integral.romberg(etrue_min, etrue_max);
  416 + }
  417 +
  418 + } // endif: interval was valid
  419 +
  420 + } // endfor: looped over intervals
  421 +
  422 + // Initialise array index
  423 + int index = 0;
  424 +
  425 + // Get probability
  426 + probs[k] = array(index++);
  427 +
  428 + // Set gradients
  429 + if (grad) {
  430 +
  431 + // Initialise gradient vector
  432 + GVector gradient(npars);
  433 +
  434 + // Get number of spatial, spectral and temporal parameters
  435 + int n_spat = model.spatial()->size();
  436 + int n_spec = model.spectral()->size();
  437 + int n_temp = model.temporal()->size();
  438 +
  439 + // Set spectral gradients
  440 + if (model.spectral() != NULL) {
  441 + int offset = n_spat;
  442 + for (int i = 0; i < n_spec; ++i) {
  443 + GModelPar& par = (*(model.spectral()))[i];
  444 + if (par.is_free() && par.has_grad()) {
  445 + gradient[offset+i] = array(index++);
  446 + }
  447 + }
  448 + }
  449 +
  450 + // Set temporal gradients
  451 + if (model.temporal() != NULL) {
  452 + int offset = n_spat + n_spec;
  453 + for (int i = 0; i < n_temp; ++i) {
  454 + GModelPar& par = (*(model.temporal()))[i];
  455 + if (par.is_free() && par.has_grad()) {
  456 + gradient[offset+i] = array(index++);
  457 + }
  458 + }
  459 + }
  460 +
  461 + // Optionally set scale gradient for instrument
  462 + if (model.has_scales()) {
  463 + int offset = n_spat + n_spec + n_temp;
  464 + for (int i = 0; i < model.scales(); ++i) {
  465 + const GModelPar& par = model.scale(i);
  466 + if (par.name() == obs.instrument()) {
  467 + if (par.is_free() && par.has_grad()) {
  468 + gradient[offset+i] = array(index++);
  469 + }
  470 + }
  471 + }
  472 + }
  473 +
  474 + // Insert gradient vector into matrix
  475 + gradients->add_to_column(k, gradient);
  476 +
  477 + } // endif: set gradients
  478 +
  479 + } // endif: Case A
  480 +
  481 + // Case B: No integration (assume no energy dispersion)
  482 + else {
  483 +
  484 + // Get source energy (no dispersion)
  485 + GEnergy srcEng = event.energy();
  486 +
  487 + // Evaluate probability
  488 + probs[k] = eval_prob(model, event, srcEng, srcTime, obs, grad);
  489 +
  490 + // Set gradients
  491 + if (grad) {
  492 +
  493 + // Initialise gradient vector
  494 + GVector gradient(npars);
  495 +
  496 + // Get number of spatial, spectral and temporal parameters
  497 + int n_spat = model.spatial()->size();
  498 + int n_spec = model.spectral()->size();
  499 + int n_temp = model.temporal()->size();
  500 +
  501 + // Set spectral gradients
  502 + if (model.spectral() != NULL) {
  503 + int offset = n_spat;
  504 + for (int i = 0; i < n_spec; ++i) {
  505 + GModelPar& par = (*(model.spectral()))[i];
  506 + if (par.is_free() && par.has_grad()) {
  507 + gradient[offset+i] = par.factor_gradient();
  508 + }
  509 + }
  510 + }
  511 +
  512 + // Set temporal gradients
  513 + if (model.temporal() != NULL) {
  514 + int offset = n_spat + n_spec;
  515 + for (int i = 0; i < n_temp; ++i) {
  516 + GModelPar& par = (*(model.temporal()))[i];
  517 + if (par.is_free() && par.has_grad()) {
  518 + gradient[offset+i] = par.factor_gradient();
  519 + }
  520 + }
  521 + }
  522 +
  523 + // Optionally set scale gradient for instrument
  524 + if (model.has_scales()) {
  525 + int offset = n_spat + n_spec + n_temp;
  526 + for (int i = 0; i < model.scales(); ++i) {
  527 + const GModelPar& par = model.scale(i);
  528 + if (par.name() == obs.instrument()) {
  529 + if (par.is_free() && par.has_grad()) {
  530 + gradient[offset+i] = par.factor_gradient();
  531 + }
  532 + }
  533 + }
  534 + }
  535 +
  536 + // Insert gradient vector into matrix
  537 + gradients->add_to_column(k, gradient);
  538 +
  539 + } // endif: set gradients
  540 +
  541 + } // endelse: Case B
  542 +
  543 + // Compile option: Check for NaN/Inf
  544 + #if defined(G_NAN_CHECK)
  545 + if (gammalib::is_notanumber(probs[k]) || gammalib::is_infinite(probs[k])) {
  546 + std::cout << "*** ERROR: GResponse::convolve:";
  547 + std::cout << " NaN/Inf encountered";
  548 + std::cout << " (probs[" << k << "]=" << probs[k];
  549 + std::cout << ", event=" << event;
  550 + std::cout << ", srcTime=" << srcTime;
  551 + std::cout << ")" << std::endl;
  552 + }
  553 + #endif
  554 +
  555 + } // endfor: looped over events
  556 +
  557 + } // endif: spatial component valid
  558 +
  559 + // Return probabilities
  560 + return probs;
  561 +}
  562 +
  563 +
  564 +/***********************************************************************//**
314 565 * @brief Return instrument response integrated over the spatial model
315 566 *
316 567 * @param[in] event Event.
... ...