Commit 12c0b27caa6ffa31a4e315cc9a73bde1c898e687

Authored by Jürgen Knödlseder
1 parent 81862a72

Integrate analytical gradients for energy dispersion computation (#3333)

ChangeLog
1   -2020-09-04
  1 +2020-09-11
2 2  
3 3 * Version 1.8.0 released
4 4 ========================
5 5  
  6 + Integrate analytical gradients for energy dispersion computation (#3333)
6 7 Add GFunctions abstract base class (#3328)
7 8 Add GIntegrals class (#3328)
8 9  
... ...
1 1 New Features and Important Changes in GammaLib 1.8.0
2 2  
3   -4 September 2020
  3 +11 September 2020
4 4  
5 5  
6 6 1. Introduction
... ... @@ -76,7 +76,14 @@ of functions (#3328).
76 76  
77 77 10. Observation module
78 78 ----------------------
79   -None
  79 +The GResponse::edisp_kern integration kernel class that is used for the integration
  80 +over true energies in case that energy dispersion should be considered now derives
  81 +from GFunctions. The actual function value is returned in the first slot of the
  82 +GNdarray returned by the GResponse::edisp_kern::eval() method, while the other slots
  83 +contain the analytical parameter gradients. The GIntegrals class is now used in the
  84 +GResponse::convolve() method to perform the true energy integration, integrating not
  85 +only the function but also any available parameter gradients (#3333).
  86 +
80 87  
81 88  
82 89 11. Optimization module
... ...
README.md
1 1 GammaLib information
2 2 ====================
3   -* Version: 1.8.0.dev (4 September 2020)
  3 +* Version: 1.8.0.dev (11 September 2020)
4 4  
5 5 [![Build Status](https://cta-jenkins.irap.omp.eu/buildStatus/icon?job=gammalib-integrate-os)](https://cta-jenkins.irap.omp.eu/job/gammalib-integrate-os/)
6 6  
... ...
doc/source/admin/release_history/1.8.rst
1 1 .. _1.8:
2 2  
3   -GammaLib 1.8 release (3 September 2020)
4   -=======================================
  3 +GammaLib 1.8 release (11 September 2020)
  4 +========================================
5 5  
6 6 Overview
7 7 --------
... ... @@ -21,6 +21,8 @@ Bug fixes
21 21 Improvements
22 22 ------------
23 23  
  24 +* [`3333 <https://cta-redmine.irap.omp.eu/issues/3333>`_] -
  25 + Integrate analytical gradients for energy dispersion computation
24 26  
25 27 New features
26 28 ------------
... ...
include/GIntegrals.hpp
... ... @@ -96,7 +96,7 @@ protected:
96 96 const int& n,
97 97 const int& index,
98 98 const double& x,
99   - GNdarray* dy);
  99 + double* dy);
100 100  
101 101 // Protected data area
102 102 GFunctions* m_kernels; //!< Pointer to function kernels
... ...
include/GResponse.hpp
... ... @@ -33,6 +33,8 @@
33 33 #include "GFunction.hpp"
34 34 #include "GTime.hpp"
35 35 #include "GResponseCache.hpp"
  36 +#include "GFunctions.hpp"
  37 +#include "GNdarray.hpp"
36 38  
37 39 /* __ Forward declarations _______________________________________________ */
38 40 class GMatrix;
... ... @@ -43,6 +45,7 @@ class GSkyDir;
43 45 class GEnergy;
44 46 class GEbounds;
45 47 class GObservation;
  48 +class GModelPar;
46 49 class GModelSky;
47 50 class GModelSpatialRadial;
48 51 class GModelSpatialElliptical;
... ... @@ -136,28 +139,25 @@ protected:
136 139 const GObservation& obs) const;
137 140  
138 141 // Protected classes
139   - class edisp_kern : public GFunction {
  142 + class edisp_kern : public GFunctions {
140 143 public:
141 144 edisp_kern(const GResponse* parent,
142 145 const GObservation* obs,
143 146 const GModelSky* model,
144 147 const GEvent* event,
145 148 const GTime& srcTime,
146   - const bool& grad) :
147   - m_parent(parent),
148   - m_obs(obs),
149   - m_model(model),
150   - m_event(event),
151   - m_srcTime(srcTime),
152   - m_grad(grad) { }
153   - double eval(const double& etrue);
  149 + const bool& grad);
  150 + const GNdarray& array(void) const;
  151 + GNdarray eval(const double& etrue);
154 152 protected:
155   - const GResponse* m_parent; //!< Response
156   - const GObservation* m_obs; //!< Observation
157   - const GModelSky* m_model; //!< Sky model
158   - const GEvent* m_event; //!< Event
159   - GTime m_srcTime; //!< True arrival time
160   - bool m_grad; //!< Gradient flag
  153 + const GResponse* m_parent; //!< Response
  154 + const GObservation* m_obs; //!< Observation
  155 + const GModelSky* m_model; //!< Sky model
  156 + const GEvent* m_event; //!< Event
  157 + GNdarray m_array; //!< Array of values and gradients
  158 + std::vector<GModelPar*> m_pars; //!< Parameter pointers
  159 + GTime m_srcTime; //!< True arrival time
  160 + bool m_grad; //!< Gradient flag
161 161 };
162 162 class irf_radial_kern_theta : public GFunction {
163 163 public:
... ...
include/Makefile.am
... ... @@ -41,8 +41,10 @@ pkginclude_HEADERS = GammaLib.hpp \
41 41 GMatrixSparse.hpp \
42 42 GMatrixSymmetric.hpp \
43 43 GIntegral.hpp \
  44 + GIntegrals.hpp \
44 45 GDerivative.hpp \
45 46 GFunction.hpp \
  47 + GFunctions.hpp \
46 48 GMath.hpp \
47 49 GNdarray.hpp \
48 50 GFft.hpp \
... ...
src/numerics/GIntegrals.cpp
... ... @@ -303,11 +303,12 @@ GNdarray GIntegrals::romberg(const double&amp; a, const double&amp; b, const int&amp; order)
303 303 }
304 304  
305 305 // Allocate temporal storage
306   - GNdarray* s = new GNdarray[max_iter+2];
307 306 double* h = new double[max_iter+2];
  307 + GNdarray* s = new GNdarray[max_iter+2];
308 308  
309   - // Initialise step size
  309 + // Initialise step size and initial result
310 310 h[1] = 1.0;
  311 + s[0] = GNdarray(m_kernels->array().shape());
311 312  
312 313 // Iterative loop
313 314 for (m_iter = 1; m_iter <= max_iter; ++m_iter) {
... ... @@ -333,9 +334,10 @@ GNdarray GIntegrals::romberg(const double&amp; a, const double&amp; b, const int&amp; order)
333 334  
334 335 // Compute result using polynom interpolation
335 336 for (int i = 0; i < result.size(); ++i) {
336   - result(i) = polint(&h[m_iter-order],
337   - &s[m_iter-order],
338   - order, i, 0.0, &dss);
  337 + double dy = 0;
  338 + result(i) = polint(&h[m_iter-order], &s[m_iter-order],
  339 + order, i, 0.0, &dy);
  340 + dss(i) = dy;
339 341 }
340 342  
341 343 // If a fixed number of iterations has been requested and if
... ... @@ -369,8 +371,8 @@ GNdarray GIntegrals::romberg(const double&amp; a, const double&amp; b, const int&amp; order)
369 371 m_abserr = abs(dss);
370 372 m_relerr = m_abserr;
371 373 for (int i = 0; i < result.size(); ++i) {
372   - double abs_result = abs(result(i));
373   - if (abs_result != 0.0) {
  374 + double abs_result = std::abs(result(i));
  375 + if (abs_result > 0.0) {
374 376 m_relerr(i) /= abs_result;
375 377 m_has_relerr = true;
376 378 }
... ... @@ -388,6 +390,10 @@ GNdarray GIntegrals::romberg(const double&amp; a, const double&amp; b, const int&amp; order)
388 390  
389 391 } // endfor: iterative loop
390 392  
  393 + // Delete temporal storage
  394 + delete [] s;
  395 + delete [] h;
  396 +
391 397 // Set status and optionally dump warning
392 398 if (!converged) {
393 399 m_isvalid = false;
... ... @@ -405,10 +411,6 @@ GNdarray GIntegrals::romberg(const double&amp; a, const double&amp; b, const int&amp; order)
405 411 }
406 412 }
407 413  
408   - // Delete temporal storage
409   - delete [] s;
410   - delete [] h;
411   -
412 414 } // endif: integration range was valid
413 415  
414 416 // Return result
... ... @@ -478,7 +480,7 @@ GNdarray GIntegrals::trapzd(const double&amp; a,
478 480 m_calls += 2;
479 481  
480 482 // Compute result
481   - result = 0.5*(b-a)*(y_a + y_b);
  483 + result = 0.5 * (b-a) * (y_a + y_b);
482 484  
483 485 } // endif: only a single step was requested
484 486  
... ... @@ -526,22 +528,20 @@ GNdarray GIntegrals::trapzd(const double&amp; a,
526 528 }
527 529  
528 530 // Sum up values
529   - double x = a + 0.5*del;
  531 + double x = a + 0.5 * del;
530 532 GNdarray sum(result.shape());
531   - for (int j = 0; j < it; ++j, x+=del) {
  533 + for (int j = 0; j < it; ++j, x += del) {
532 534  
533   - // Evaluate integrand
534   - GNdarray y = m_kernels->eval(x);
  535 + // Evaluate and add integrand
  536 + sum += m_kernels->eval(x);
535 537 m_calls++;
536 538  
537   - // Add integrand
538   - sum += y;
539   -
540 539 } // endfor: looped over steps
541 540  
542 541 // Set result
543   - result = 0.5*(result + (b-a)*sum/tnm);
544   - }
  542 + result = 0.5 * (result + del * sum);
  543 +
  544 + } // endelse: Case B
545 545  
546 546 } // endelse: trapeziodal rule was applied
547 547  
... ... @@ -708,7 +708,7 @@ double GIntegrals::polint(const double* xa,
708 708 const int& n,
709 709 const int& index,
710 710 const double& x,
711   - GNdarray* dy)
  711 + double* dy)
712 712 {
713 713 // Initialise result
714 714 double y = 0.0;
... ... @@ -728,8 +728,9 @@ double GIntegrals::polint(const double* xa,
728 728 ns = i;
729 729 dif = dift;
730 730 }
731   - c[i] = ya[i+1](index);
732   - d[i] = ya[i+1](index);
  731 + double value = ya[i+1](index);
  732 + c[i] = value;
  733 + d[i] = value;
733 734 }
734 735  
735 736 // Get initial approximation to y
... ... @@ -760,10 +761,10 @@ double GIntegrals::polint(const double* xa,
760 761 }
761 762  
762 763 // Compute y correction
763   - (*dy)(index) = (2*(ns+1) < (n-m)) ? c[ns+1] : d[ns--];
  764 + *dy = (2*(ns+1) < (n-m)) ? c[ns+1] : d[ns--];
764 765  
765 766 // Update y
766   - y += (*dy)(index);
  767 + y += *dy;
767 768  
768 769 } // endfor: looped over columns of tableau
769 770  
... ...
src/obs/GObservation.cpp
... ... @@ -271,9 +271,12 @@ double GObservation::model(const GModels&amp; models,
271 271 int igrad = 0; // Reset gradient counter
272 272 int grad_size = 0; // Reset gradient size
273 273  
  274 + // Set gradient usage flag
  275 + bool use_grad = (gradient != NULL);
  276 +
274 277 // If gradient is available then reset gradient vector elements to 0
275 278 // and determine vector size
276   - if (gradient != NULL) {
  279 + if (use_grad) {
277 280 (*gradient) = 0.0;
278 281 grad_size = gradient->size();
279 282 }
... ... @@ -289,19 +292,11 @@ double GObservation::model(const GModels&amp; models,
289 292 // observation identifier
290 293 if (mptr->is_valid(instrument(), id())) {
291 294  
292   - // Compute value and add to model. If energy dispersion
293   - // is used, don't compute model gradients as we cannot
294   - // use them. This is somehow a kluge, but makes the
295   - // code faster
296   - model += mptr->eval(event, *this, !response()->use_edisp());
297   -
298   - // Optionally determine model gradients. If the model has a
299   - // gradient then use it, unless we have energy dispersion.
300   - // For energy dispersion, no gradients are available as we
301   - // have not implemented code that integrates the gradients
302   - // over the energy dispersion. Here it's simpler to just use
303   - // numerical gradients.
304   - if (gradient != NULL) {
  295 + // Compute value and add to model
  296 + model += mptr->eval(event, *this, use_grad);
  297 +
  298 + // Optionally determine model gradients
  299 + if (use_grad) {
305 300  
306 301 // Make sure that we have a slot for the gradient
307 302 #if defined(G_RANGE_CHECK)
... ... @@ -334,7 +329,7 @@ double GObservation::model(const GModels&amp; models,
334 329  
335 330 // Set gradient
336 331 if (par.is_free()) {
337   - if (par.has_grad() && !response()->use_edisp()) {
  332 + if (par.has_grad()) {
338 333 (*gradient)[igrad+ipar] = par.factor_gradient();
339 334 }
340 335 else {
... ... @@ -360,7 +355,7 @@ double GObservation::model(const GModels&amp; models,
360 355  
361 356 // Set gradient
362 357 if (par.is_free()) {
363   - if (par.has_grad() && !response()->use_edisp()) {
  358 + if (par.has_grad()) {
364 359 (*gradient)[igrad+ipar] = par.factor_gradient();
365 360 }
366 361 else {
... ... @@ -512,7 +507,7 @@ double GObservation::npred(const GModels&amp; models, GVector* gradient) const
512 507 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp \, dE \, dt
513 508 * \f]
514 509 *
515   - * where
  510 + * where
516 511 * \f$S(p,E,t)\f$ is the source model,
517 512 * \f$R(p',E',t'|p,E,t)\f$ is the instrument response function,
518 513 * \f$p'\f$ is the measured photon direction,
... ...
src/obs/GResponse.cpp
... ... @@ -36,6 +36,7 @@
36 36 #include "GVector.hpp"
37 37 #include "GMatrix.hpp"
38 38 #include "GIntegral.hpp"
  39 +#include "GIntegrals.hpp"
39 40 #include "GResponse.hpp"
40 41 #include "GEvent.hpp"
41 42 #include "GPhoton.hpp"
... ... @@ -165,7 +166,7 @@ GResponse&amp; GResponse::operator=(const GResponse&amp; rsp)
165 166 * @param[in] model Sky model.
166 167 * @param[in] event Event.
167 168 * @param[in] obs Observation.
168   - * @param[in] grad Should model gradients be computed? (default: true)
  169 + * @param[in] grad Should model gradients be computed?
169 170 * @return Event probability.
170 171 *
171 172 * Computes the event probability
... ... @@ -202,6 +203,9 @@ double GResponse::convolve(const GModelSky&amp; model,
202 203 // Retrieve true energy boundaries
203 204 GEbounds ebounds = this->ebounds(event.energy());
204 205  
  206 + // Initialise Ndarray array
  207 + GNdarray array;
  208 +
205 209 // Loop over all boundaries
206 210 for (int i = 0; i < ebounds.size(); ++i) {
207 211  
... ... @@ -214,19 +218,67 @@ double GResponse::convolve(const GModelSky&amp; model,
214 218  
215 219 // Setup integration function
216 220 edisp_kern integrand(this, &obs, &model, &event, srcTime, grad);
217   - GIntegral integral(&integrand);
  221 + GIntegrals integral(&integrand);
218 222  
219 223 // Set number of iterations
220 224 integral.fixed_iter(iter);
221 225  
222 226 // Do Romberg integration
223   - prob += integral.romberg(etrue_min, etrue_max);
  227 + if (array.size() == 0) {
  228 + array = integral.romberg(etrue_min, etrue_max);
  229 + }
  230 + else {
  231 + array += integral.romberg(etrue_min, etrue_max);
  232 + }
224 233  
225 234 } // endif: interval was valid
226 235  
227 236 } // endfor: looped over intervals
228 237  
229   - }
  238 + // Initialise array index
  239 + int index = 0;
  240 +
  241 + // Get probability
  242 + prob = array(index++);
  243 +
  244 + // Set gradients
  245 + if (grad) {
  246 +
  247 + // Set spectral gradients
  248 + if (model.spectral() != NULL) {
  249 + for (int i = 0; i < model.spectral()->size(); ++i) {
  250 + GModelPar& par = (*(model.spectral()))[i];
  251 + if (par.is_free() && par.has_grad()) {
  252 + par.factor_gradient(array(index++));
  253 + }
  254 + }
  255 + }
  256 +
  257 + // Set temporal gradients
  258 + if (model.temporal() != NULL) {
  259 + for (int i = 0; i < model.temporal()->size(); ++i) {
  260 + GModelPar& par = (*(model.temporal()))[i];
  261 + if (par.is_free() && par.has_grad()) {
  262 + par.factor_gradient(array(index++));
  263 + }
  264 + }
  265 + }
  266 +
  267 + // Optionally set scale gradient for instrument
  268 + if (model.has_scales()) {
  269 + for (int i = 0; i < model.scales(); ++i) {
  270 + const GModelPar& par = model.scale(i);
  271 + if (par.name() == obs.instrument()) {
  272 + if (par.is_free() && par.has_grad()) {
  273 + par.factor_gradient(array(index++));
  274 + }
  275 + }
  276 + }
  277 + }
  278 +
  279 + } // endif: set gradients
  280 +
  281 + } // endif: Case A
230 282  
231 283 // Case B: No integration (assume no energy dispersion)
232 284 else {
... ... @@ -237,7 +289,7 @@ double GResponse::convolve(const GModelSky&amp; model,
237 289 // Evaluate probability
238 290 prob = eval_prob(model, event, srcEng, srcTime, obs, grad);
239 291  
240   - }
  292 + } // endelse: Case B
241 293  
242 294 // Compile option: Check for NaN/Inf
243 295 #if defined(G_NAN_CHECK)
... ... @@ -1032,36 +1084,133 @@ double GResponse::eval_prob(const GModelSky&amp; model,
1032 1084  
1033 1085  
1034 1086 /***********************************************************************//**
1035   - * @brief Integration kernel for GResponse::edisp_kern() class
  1087 + * @brief Constructor for energy dispersion integration kernel class
  1088 + *
  1089 + * @param[in] parent Pointer to response.
  1090 + * @param[in] obs Pointer to observation.
  1091 + * @param[in] model Pointer to sky model.
  1092 + * @param[in] event Pointer to event.
  1093 + * @param[in] srcTime True time.
  1094 + * @param[in] grad Evaluate gradients?
  1095 + *
  1096 + * This method constructs the integration kernel needed for the energy
  1097 + * dispersion computation.
  1098 + ***************************************************************************/
  1099 +GResponse::edisp_kern::edisp_kern(const GResponse* parent,
  1100 + const GObservation* obs,
  1101 + const GModelSky* model,
  1102 + const GEvent* event,
  1103 + const GTime& srcTime,
  1104 + const bool& grad)
  1105 +{
  1106 + // Set members
  1107 + m_parent = parent;
  1108 + m_obs = obs;
  1109 + m_model = model;
  1110 + m_event = event;
  1111 + m_srcTime = srcTime;
  1112 + m_grad = grad;
  1113 +
  1114 + // If gradients are requested then put pointers to all relevant parameter
  1115 + // in the parameter pointer vector
  1116 + m_pars.clear();
  1117 + if (m_grad) {
  1118 + if (m_model->spectral() != NULL) {
  1119 + for (int i = 0; i < m_model->spectral()->size(); ++i) {
  1120 + GModelPar& par = (*(m_model->spectral()))[i];
  1121 + if (par.is_free() && par.has_grad()) {
  1122 + m_pars.push_back(&par);
  1123 + }
  1124 + }
  1125 + }
  1126 + if (m_model->temporal() != NULL) {
  1127 + for (int i = 0; i < m_model->temporal()->size(); ++i) {
  1128 + GModelPar& par = (*(m_model->temporal()))[i];
  1129 + if (par.is_free() && par.has_grad()) {
  1130 + m_pars.push_back(&par);
  1131 + }
  1132 + }
  1133 + }
  1134 + if (m_model->has_scales()) {
  1135 + for (int i = 0; i < m_model->scales(); ++i) {
  1136 + GModelPar& par = const_cast<GModelPar&>(m_model->scale(i));
  1137 + if (par.name() == m_obs->instrument()) {
  1138 + if (par.is_free() && par.has_grad()) {
  1139 + m_pars.push_back(&par);
  1140 + }
  1141 + }
  1142 + }
  1143 + }
  1144 + }
  1145 +
  1146 + // Allocate Ndarray to be returned as return type
  1147 + m_array = GNdarray(m_pars.size()+1);
  1148 +
  1149 + // Return
  1150 + return;
  1151 +}
  1152 +
  1153 +
  1154 +/***********************************************************************//**
  1155 + * @brief Return energy dispersion integration kernel
  1156 + *
  1157 + * @return Ndarray.
  1158 + *
  1159 + * This method returns an Ndarray with the last values that were computed
  1160 + * by the eval() method. In case that eval() was not called before, the
  1161 + * method returns an Ndarray with the right dimension and with all values
  1162 + * set to zero.
  1163 + ***************************************************************************/
  1164 +const GNdarray& GResponse::edisp_kern::array(void) const
  1165 +{
  1166 + // Return Ndarray
  1167 + return m_array;
  1168 +}
  1169 +
  1170 +
  1171 +/***********************************************************************//**
  1172 + * @brief Evaluate energy dispersion integration kernel
1036 1173 *
1037 1174 * @param[in] etrue True photon energy in MeV.
1038 1175 *
1039 1176 * This method implements the integration kernel needed for the
1040 1177 * GResponse::edisp_kern() class.
1041 1178 ***************************************************************************/
1042   -double GResponse::edisp_kern::eval(const double& etrue)
  1179 +GNdarray GResponse::edisp_kern::eval(const double& etrue)
1043 1180 {
  1181 + // Initialise Ndarray array index
  1182 + int index = 0;
  1183 +
1044 1184 // Set true energy
1045 1185 GEnergy srcEng;
1046 1186 srcEng.MeV(etrue);
1047 1187  
1048 1188 // Get function value
1049   - double value = m_parent->eval_prob(*m_model, *m_event,
1050   - srcEng, m_srcTime, *m_obs, m_grad);
  1189 + m_array(index++) = m_parent->eval_prob(*m_model, *m_event,
  1190 + srcEng, m_srcTime, *m_obs,
  1191 + m_grad);
  1192 +
  1193 + // If gradients are requested then extract them and put them into the
  1194 + // array
  1195 + if (m_grad) {
  1196 + for (int i = 0; i < m_pars.size(); ++i, ++index) {
  1197 + m_array(index) = m_pars[i]->factor_gradient();
  1198 + }
  1199 + }
1051 1200  
1052 1201 // Compile option: Check for NaN
1053 1202 #if defined(G_NAN_CHECK)
1054   - if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
  1203 + if (gammalib::is_notanumber(m_array(0)) || gammalib::is_infinite(m_array(0))) {
1055 1204 std::cout << "*** ERROR: GResponse::edisp_kern::eval";
1056 1205 std::cout << "(etrue=" << etrue << "): ";
1057 1206 std::cout << " NaN/Inf encountered";
1058   - std::cout << " (value=" << value;
  1207 + std::cout << " (value=" << m_array(0);
1059 1208 std::cout << ")" << std::endl;
1060 1209 }
1061 1210 #endif
1062 1211  
1063   - // Return value
1064   - return value;
  1212 + // Return array
  1213 + return m_array;
1065 1214 }
1066 1215  
1067 1216  
... ...