Commit f2695a7538b9d5983cc293f9c4bef44cd38d1167

Authored by Jürgen Knödlseder
1 parent 2de42d5d

GCTAEdisp2D::operator() now returns energy dispersion per MeV (#2704)

The previous code returned the energy dispersion as function of the base 10 logarithm of the energy. It is not really clear why this was the case, as the GCTAResponseIrf::edisp() class returns energy dispersion per MeV, and all high-level code assumes that energy dispersion is per MeV. Therefore, to avoid confusion and renormalization of energy dispersion information, also the GCTAEdisp2D::operator() now also returns the information per MeV.

The data is stored per migration bin, and hence a simple integration over migration should be unity for a give for a given offset angle and energy. The class was adapted so that this is now the case.
inst/cta/include/GCTAEdisp2D.hpp
@@ -52,14 +52,47 @@ namespace gammalib { @@ -52,14 +52,47 @@ namespace gammalib {
52 * @brief CTA 2D energy dispersion class 52 * @brief CTA 2D energy dispersion class
53 * 53 *
54 * This class implements the energy dispersion for the CTA 2D response. The 54 * This class implements the energy dispersion for the CTA 2D response. The
55 - * energy dispersion is defined as 55 + * CTA 2D energy dispersion is in fact a 3-dimensional function, where the
  56 + * energy dispersion is given as function of true energy \f$E_{\rm true}\f$,
  57 + * migration value \f$m = E_{\rm reco}/E_{\rm true}\f$ and offset angle
  58 + * \f$\theta\f$
56 * 59 *
57 * \f[ 60 * \f[
58 - * E_{\rm disp}(\log_{10} E_{\rm reco} | \log_{10} E_{\rm true}, \theta) =  
59 - * \frac{dP}{d\log_{10} E_{\rm reco}} 61 + * E_{\rm disp}(E_{\rm true}, m, \theta)
60 * \f] 62 * \f]
61 * 63 *
62 - * and given in units of \f$(\log_{10} MeV)^{-1}\f$. 64 + * with
  65 + *
  66 + * \f[
  67 + * \int_{m_{\rm min}}^{m_{\rm max}}
  68 + * E_{\rm disp}(E_{\rm true}, m, \theta) \, dm = 1
  69 + * \f]
  70 + *
  71 + * The energy dispersion as function of reconstructed energy
  72 + * \f$E_{\rm reco}\f$ is given by
  73 + *
  74 + * \f[
  75 + * E_{\rm disp}(E_{\rm true}, E_{\rm reco}, \theta) =
  76 + * \frac{E_{\rm disp}(E_{\rm true}, m, \theta)}{E_{\rm true}}
  77 + * \f]
  78 + *
  79 + * the energy dispersion as function of the natural logarithm of
  80 + * reconstructed energy \f$\log E_{\rm reco}\f$ is given by
  81 + *
  82 + * \f[
  83 + * E_{\rm disp}(E_{\rm true}, \log E_{\rm reco}, \theta) =
  84 + * E_{\rm disp}(E_{\rm true}, m, \theta) \times
  85 + * \frac{E_{\rm reco}}{E_{\rm true}}
  86 + * \f]
  87 + *
  88 + * and the energy dispersion as function of the base 10 logarithm of
  89 + * reconstructed energy \f$\log_{10} E_{\rm reco}\f$ is given by
  90 + *
  91 + * \f[
  92 + * E_{\rm disp}(E_{\rm true}, \log_{10} E_{\rm reco}, \theta) =
  93 + * E_{\rm disp}(E_{\rm true}, m, \theta) \times
  94 + * \frac{\log 10 \times E_{\rm reco}}{E_{\rm true}}
  95 + * \f]
63 ***************************************************************************/ 96 ***************************************************************************/
64 class GCTAEdisp2D : public GCTAEdisp { 97 class GCTAEdisp2D : public GCTAEdisp {
65 98
@@ -135,7 +168,6 @@ private: @@ -135,7 +168,6 @@ private:
135 void set_table(void); 168 void set_table(void);
136 void set_boundaries(void); 169 void set_boundaries(void);
137 void set_max_edisp(void) const; 170 void set_max_edisp(void) const;
138 - void convert_dm2dlogE(void);  
139 void normalize_table(void); 171 void normalize_table(void);
140 int table_index(const int& ietrue, 172 int table_index(const int& ietrue,
141 const int& imigra, 173 const int& imigra,
@@ -188,7 +220,7 @@ private: @@ -188,7 +220,7 @@ private:
188 m_parent(parent), 220 m_parent(parent),
189 m_logEsrc(logEsrc), 221 m_logEsrc(logEsrc),
190 m_theta(theta) { } 222 m_theta(theta) { }
191 - double eval(const double& x); 223 + double eval(const double& logEobs);
192 protected: 224 protected:
193 const GCTAEdisp2D* m_parent; //!< Pointer to parent class 225 const GCTAEdisp2D* m_parent; //!< Pointer to parent class
194 double m_logEsrc; //!< True photon energy 226 double m_logEsrc; //!< True photon energy
inst/cta/src/GCTAEdisp2D.cpp
@@ -178,7 +178,7 @@ GCTAEdisp2D&amp; GCTAEdisp2D::operator=(const GCTAEdisp2D&amp; edisp) @@ -178,7 +178,7 @@ GCTAEdisp2D&amp; GCTAEdisp2D::operator=(const GCTAEdisp2D&amp; edisp)
178 178
179 179
180 /***********************************************************************//** 180 /***********************************************************************//**
181 - * @brief Return energy dispersion in units of \f$(\log_{10} MeV)^{-1}\f$ 181 + * @brief Return energy dispersion in units of MeV\f$^{-1}\f$
182 * 182 *
183 * @param[in] logEobs Log10 of the measured energy (TeV). 183 * @param[in] logEobs Log10 of the measured energy (TeV).
184 * @param[in] logEsrc Log10 of the true photon energy (TeV). 184 * @param[in] logEsrc Log10 of the true photon energy (TeV).
@@ -186,19 +186,20 @@ GCTAEdisp2D&amp; GCTAEdisp2D::operator=(const GCTAEdisp2D&amp; edisp) @@ -186,19 +186,20 @@ GCTAEdisp2D&amp; GCTAEdisp2D::operator=(const GCTAEdisp2D&amp; edisp)
186 * @param[in] phi Azimuth angle in camera system (rad). Not used in this method. 186 * @param[in] phi Azimuth angle in camera system (rad). Not used in this method.
187 * @param[in] zenith Zenith angle in Earth system (rad). Not used in this method. 187 * @param[in] zenith Zenith angle in Earth system (rad). Not used in this method.
188 * @param[in] azimuth Azimuth angle in Earth system (rad). Not used in this method. 188 * @param[in] azimuth Azimuth angle in Earth system (rad). Not used in this method.
  189 + * @return Energy dispersion (MeV\f$^{-1}\f$)
189 * 190 *
190 * Returns the energy dispersion 191 * Returns the energy dispersion
191 * 192 *
192 * \f[ 193 * \f[
193 - * E_{\rm disp}(\log_{10} E_{\rm reco} | \log_{10} E_{\rm true}, \theta) =  
194 - * \frac{dP}{d\log_{10} E_{\rm reco}} 194 + * E_{\rm disp}(E_{\rm true}, E_{\rm reco}, \theta) =
  195 + * \frac{E_{\rm disp}(E_{\rm true}, m, \theta)}{E_{\rm true}}
195 * \f] 196 * \f]
196 * 197 *
197 - * in units of \f$(\log_{10} MeV)^{-1}\f$ where  
198 - * \f$\log_{10} E_{\rm reco}\f$ is the logarithm of the reconstructed energy  
199 - * in TeV,  
200 - * \f$\log_{10} E_{\rm true}\f$ is the logarithm of the true energy in TeV and  
201 - * \f$\theta\f$ is the offset angle in radians. 198 + * in units of MeV\f$^{-1}\f$ where
  199 + * \f$\log_{10} E_{\rm reco}\f$ is the base 10 logarithm of the reconstructed
  200 + * energy in TeV,
  201 + * \f$\log_{10} E_{\rm true}\f$ is the base 10 logarithm of the true energy
  202 + * in TeV and \f$\theta\f$ is the offset angle in radians.
202 ***************************************************************************/ 203 ***************************************************************************/
203 double GCTAEdisp2D::operator()(const double& logEobs, 204 double GCTAEdisp2D::operator()(const double& logEobs,
204 const double& logEsrc, 205 const double& logEsrc,
@@ -237,6 +238,19 @@ double GCTAEdisp2D::operator()(const double&amp; logEobs, @@ -237,6 +238,19 @@ double GCTAEdisp2D::operator()(const double&amp; logEobs,
237 edisp = 0.0; 238 edisp = 0.0;
238 } 239 }
239 240
  241 + // If energy dispersion is positive then compute true energy
  242 + // in MeV and divide the energy dispersion value by this
  243 + // quantity to get the energy dispersion per MeV
  244 + else {
  245 +
  246 + // Compute true energy
  247 + double etrue = std::pow(10.0, logEsrc+6.0);
  248 +
  249 + // Compute energy dispersion per MeV
  250 + edisp /= etrue;
  251 +
  252 + }
  253 +
240 } // endif: migration was in valid range 254 } // endif: migration was in valid range
241 255
242 } // endif: true energy and offset angle was in valid range 256 } // endif: true energy and offset angle was in valid range
@@ -325,7 +339,7 @@ void GCTAEdisp2D::table(const GCTAResponseTable&amp; table) @@ -325,7 +339,7 @@ void GCTAEdisp2D::table(const GCTAResponseTable&amp; table)
325 * MATRIX - Migration matrix 339 * MATRIX - Migration matrix
326 * 340 *
327 * The data are stored in the m_edisp member. The energy axis will be set to 341 * The data are stored in the m_edisp member. The energy axis will be set to
328 - * log10, the offset angle axis to radians. 342 + * \f$log_{10} E_{\rm true}\f$, the offset angle axis to radians.
329 * 343 *
330 * The method assures that the energy dispersion is properly normalised. 344 * The method assures that the energy dispersion is properly normalised.
331 ***************************************************************************/ 345 ***************************************************************************/
@@ -405,7 +419,7 @@ void GCTAEdisp2D::load(const GFilename&amp; filename) @@ -405,7 +419,7 @@ void GCTAEdisp2D::load(const GFilename&amp; filename)
405 * @brief Save energy dispersion table into FITS file 419 * @brief Save energy dispersion table into FITS file
406 * 420 *
407 * @param[in] filename FITS file name. 421 * @param[in] filename FITS file name.
408 - * @param[in] clobber Overwrite existing file? (default: false) 422 + * @param[in] clobber Overwrite existing file?
409 * 423 *
410 * Saves energy dispersion into a FITS file. If a file with the given 424 * Saves energy dispersion into a FITS file. If a file with the given
411 * @p filename does not yet exist it will be created, otherwise the method 425 * @p filename does not yet exist it will be created, otherwise the method
@@ -901,7 +915,7 @@ double GCTAEdisp2D::prob_erecobin(const GEnergy&amp; ereco_min, @@ -901,7 +915,7 @@ double GCTAEdisp2D::prob_erecobin(const GEnergy&amp; ereco_min,
901 } 915 }
902 916
903 // Compute integral 917 // Compute integral
904 - prob += 0.5 * (f1 + f2) * std::log10(x2/x1); 918 + prob += 0.5 * (f1 + f2) * (x2 - x1);
905 919
906 // Set second node as first node 920 // Set second node as first node
907 x1 = x2; 921 x1 = x2;
@@ -923,7 +937,7 @@ double GCTAEdisp2D::prob_erecobin(const GEnergy&amp; ereco_min, @@ -923,7 +937,7 @@ double GCTAEdisp2D::prob_erecobin(const GEnergy&amp; ereco_min,
923 etrue_nodes.wgt_right(), 937 etrue_nodes.wgt_right(),
924 theta_nodes.wgt_left(), 938 theta_nodes.wgt_left(),
925 theta_nodes.wgt_right(), x2); 939 theta_nodes.wgt_right(), x2);
926 - prob += 0.5 * (f1 + f2) * std::log10(x2/x1); 940 + prob += 0.5 * (f1 + f2) * (x2 - x1);
927 } 941 }
928 942
929 } // endif: logEsrc, theta and migration range were valid 943 } // endif: logEsrc, theta and migration range were valid
@@ -1329,8 +1343,18 @@ void GCTAEdisp2D::compute_ebounds_src(const double&amp; theta, @@ -1329,8 +1343,18 @@ void GCTAEdisp2D::compute_ebounds_src(const double&amp; theta,
1329 * @brief Set table 1343 * @brief Set table
1330 * 1344 *
1331 * After assigning or loading a response table, performs all necessary steps 1345 * After assigning or loading a response table, performs all necessary steps
1332 - * to set the table. The method also smoothes the table get rid of numerical  
1333 - * fluctuations and normalises the table properly. 1346 + * to set the table. Specifically, the method normalizes the energy
  1347 + * dispersion for each offset angle and true energy so that
  1348 + *
  1349 + * \f[
  1350 + * \int_{m_{\rm min}}^{m_{\rm max}}
  1351 + * E_{\rm disp}(E_{\rm true}, m, \theta) \, dm = 1
  1352 + * \f]
  1353 + *
  1354 + * where \f$m=E_{\rm reco}/E_{\rm true}\f$.
  1355 + *
  1356 + * For CTA, the method applies a kludge that smoothes the energy dispersion
  1357 + * table to get rid of numerical fluctuations.
1334 * 1358 *
1335 * Sets the data members 1359 * Sets the data members
1336 * m_inx_etrue 1360 * m_inx_etrue
@@ -1367,9 +1391,6 @@ void GCTAEdisp2D::set_table(void) @@ -1367,9 +1391,6 @@ void GCTAEdisp2D::set_table(void)
1367 // Set table boundaries 1391 // Set table boundaries
1368 set_boundaries(); 1392 set_boundaries();
1369 1393
1370 - // Convert table from dP/dm to dP/dlogE  
1371 - convert_dm2dlogE();  
1372 -  
1373 // Smooth energy dispersion table (kludge only for CTA) 1394 // Smooth energy dispersion table (kludge only for CTA)
1374 #if defined(G_SMOOTH_EDISP_KLUDGE) 1395 #if defined(G_SMOOTH_EDISP_KLUDGE)
1375 if (gammalib::strip_whitespace(m_edisp.telescope()) == "CTA") { 1396 if (gammalib::strip_whitespace(m_edisp.telescope()) == "CTA") {
@@ -1451,21 +1472,17 @@ void GCTAEdisp2D::set_max_edisp(void) const @@ -1451,21 +1472,17 @@ void GCTAEdisp2D::set_max_edisp(void) const
1451 * Normalize the energy dispersion table using 1472 * Normalize the energy dispersion table using
1452 * 1473 *
1453 * \f[ 1474 * \f[
1454 - * \int_{\log_{10} E_{\rm reco}^{\rm min}}^{\log_{10} E_{\rm reco}^{\rm max}}  
1455 - * E_{\rm disp}(\log_{10} E_{\rm reco} | \log_{10} E_{\rm true}, \theta)  
1456 - * \, d(\log_{10} E_{\rm reco}) = 1 1475 + * \int_{E_{\rm reco}^{\rm min}}^{E_{\rm reco}^{\rm max}}
  1476 + * E_{\rm disp}(E_{\rm true}, E_{\rm reco}, \theta) \, dE_{\rm reco} = 1
1457 * \f] 1477 * \f]
1458 * 1478 *
1459 * where 1479 * where
1460 - * \f$E_{\rm reco}^{\rm min}\f$ is the minimum and  
1461 - * \f$E_{\rm reco}^{\rm max}\f$ the maximum reconstructed energy for a given  
1462 - * true energy, returned by the method ebounds_obs(),  
1463 - * \f$E_{\rm disp}(\log_{10} E_{\rm reco} | \log_{10} E_{\rm true}, \theta)\f$  
1464 - * is the energy dispersion returned by the GCTAEdisp2D::operator(), given in  
1465 - * units of \f$(\log_{10} MeV)^{-1}\f$,  
1466 - * \f$\log_{10} E_{\rm reco}\f$ is the logarithm of the reconstructued energy  
1467 - * in TeV, and  
1468 - * \f$\log_{10} E_{\rm true}\f$ is the logarithm of the true energy in TeV. 1480 + * \f$E_{\rm reco}_{\rm min}\f$ and \f$E_{\rm reco}_{\rm max}\f$ is the
  1481 + * minimum and maximum migration value for a given true energy as returned
  1482 + * by the ebounds_obs() method, and
  1483 + * \f$E_{\rm disp}(E_{\rm true}, E_{\rm reco}, \theta)\f$ is the energy
  1484 + * dispersion returned by the GCTAEdisp2D::operator(), given in units of
  1485 + * \f$MeV^{-1}\f$.
1469 * 1486 *
1470 * The normalisation is performed for each \f$E_{\rm true}\f$ and 1487 * The normalisation is performed for each \f$E_{\rm true}\f$ and
1471 * \f$\theta\f$ bin using a Romberg integration method. Since the 1488 * \f$\theta\f$ bin using a Romberg integration method. Since the
@@ -1502,7 +1519,7 @@ void GCTAEdisp2D::normalize_table(void) @@ -1502,7 +1519,7 @@ void GCTAEdisp2D::normalize_table(void)
1502 // Loop over true photon energy 1519 // Loop over true photon energy
1503 for (int i_etrue = 0; i_etrue < etrue_size; ++i_etrue) { 1520 for (int i_etrue = 0; i_etrue < etrue_size; ++i_etrue) {
1504 1521
1505 - // Get energy 1522 + // Get the base 10 logarithm of the true energy
1506 double emin = std::log10(m_edisp.axis_lo(m_inx_etrue,i_etrue)); 1523 double emin = std::log10(m_edisp.axis_lo(m_inx_etrue,i_etrue));
1507 double emax = std::log10(m_edisp.axis_hi(m_inx_etrue,i_etrue)); 1524 double emax = std::log10(m_edisp.axis_hi(m_inx_etrue,i_etrue));
1508 double logEsrc = 0.5*(emin+emax); 1525 double logEsrc = 0.5*(emin+emax);
@@ -1516,9 +1533,9 @@ void GCTAEdisp2D::normalize_table(void) @@ -1516,9 +1533,9 @@ void GCTAEdisp2D::normalize_table(void)
1516 // Loop over all energy intervals 1533 // Loop over all energy intervals
1517 for (int i = 0; i < ebounds.size(); ++i) { 1534 for (int i = 0; i < ebounds.size(); ++i) {
1518 1535
1519 - // Get energy boundaries  
1520 - emin = ebounds.emin(i).log10TeV();  
1521 - emax = ebounds.emax(i).log10TeV(); 1536 + // Get energy boundaries in log10 of energy in MeV
  1537 + emin = ebounds.emin(i).log10MeV();
  1538 + emax = ebounds.emax(i).log10MeV();
1522 1539
1523 // Setup integration function 1540 // Setup integration function
1524 edisp_kern integrand(this, logEsrc, theta); 1541 edisp_kern integrand(this, logEsrc, theta);
@@ -1561,106 +1578,6 @@ void GCTAEdisp2D::normalize_table(void) @@ -1561,106 +1578,6 @@ void GCTAEdisp2D::normalize_table(void)
1561 1578
1562 1579
1563 /***********************************************************************//** 1580 /***********************************************************************//**
1564 - * @brief Convert table from dP/dm to dP/dlog10E  
1565 - *  
1566 - * The energy dispersion table when read-in is normalized so that  
1567 - *  
1568 - * \f[  
1569 - * \int_{{\rm migra}_{\rm min}}^{{\rm migra}_{\rm max}}  
1570 - * E_{\rm disp}({\rm migra}) \, d{\rm migra} = 1  
1571 - * \f]  
1572 - *  
1573 - * while the class needs values that are normalize according to  
1574 - *  
1575 - * \f[  
1576 - * \int_{\log_{10} E_{\rm reco}^{\rm min}}^{\log_{10} E_{\rm reco}^{\rm max}}  
1577 - * E_{\rm disp}(\log_{10} E_{\rm reco}) \, d(\log_{10} E_{\rm reco}) = 1  
1578 - * \f]  
1579 - *  
1580 - * The method thus multiplies each matrix element with  
1581 - *  
1582 - * \f[  
1583 - * \frac{{\rm migra}_{\rm max} - {\rm migra}_{\rm min}}  
1584 - * {\log_{10} E_{\rm reco}^{\rm max} - \log_{10} E_{\rm reco}^{\rm min}}  
1585 - * \f]  
1586 - *  
1587 - * where  
1588 - * \f${\rm migra}_{\rm min}\f$ and \f${\rm migra}_{\rm max}\f$ are the  
1589 - * minimum and maximum migration values of a given bin and  
1590 - * \f$E_{\rm reco}^{\rm min}\f$ and \f$E_{\rm reco}^{\rm max}\f$ are the  
1591 - * minimum and maximum reconstructed energy values for the same bin.  
1592 - ***************************************************************************/  
1593 -void GCTAEdisp2D::convert_dm2dlogE(void)  
1594 -{  
1595 - // Optional dump header  
1596 - #if defined(G_DEBUG_HESS_RENORMALIZATION)  
1597 - std::cout << "GCTAEdisp2D::hess_renormalization:" << std::endl;  
1598 - #endif  
1599 -  
1600 - // Get axes dimensions  
1601 - int etrue_size = m_edisp.axis_bins(m_inx_etrue);  
1602 - int migra_size = m_edisp.axis_bins(m_inx_migra);  
1603 - int theta_size = m_edisp.axis_bins(m_inx_theta);  
1604 -  
1605 - // Perform renormalization  
1606 - for (int i_etrue = 0; i_etrue < etrue_size; ++i_etrue) {  
1607 -  
1608 - // Get true energy in TeV  
1609 - double etrue = std::sqrt(m_edisp.axis_lo(m_inx_etrue, i_etrue) *  
1610 - m_edisp.axis_hi(m_inx_etrue, i_etrue));  
1611 -  
1612 - // Get offset angle  
1613 - for (int i_theta = 0; i_theta < theta_size; ++i_theta) {  
1614 -  
1615 - // Intialize control sum  
1616 - #if defined(G_DEBUG_HESS_RENORMALIZATION)  
1617 - double sum = 0.0;  
1618 - #endif  
1619 -  
1620 - // Loop over all migration bins  
1621 - int offset = table_index(i_etrue, 0, i_theta);  
1622 - int stride = table_stride(m_inx_migra);  
1623 - for (int i_migra = 0, i = offset; i_migra < migra_size; ++i_migra, i += stride) {  
1624 -  
1625 - // Get migration bin size  
1626 - double migra_bin = m_edisp.axis_hi(m_inx_migra, i_migra) -  
1627 - m_edisp.axis_lo(m_inx_migra, i_migra);  
1628 -  
1629 - // Get dlog10(E_reco) bin size  
1630 - double ereco_min = m_edisp.axis_lo(m_inx_migra, i_migra) * etrue;  
1631 - double ereco_max = m_edisp.axis_hi(m_inx_migra, i_migra) * etrue;  
1632 - double dlogE_bin = std::log10(ereco_max) - std::log10(ereco_min);  
1633 -  
1634 - // Compute normalization factor  
1635 - double norm = migra_bin / dlogE_bin;  
1636 -  
1637 - // Multiply matrix element  
1638 - m_edisp(m_inx_matrix, i) *= norm;  
1639 -  
1640 - // Update test sum  
1641 - #if defined(G_DEBUG_HESS_RENORMALIZATION)  
1642 - sum += m_edisp(m_inx_matrix, i) * dlogE_bin;  
1643 - #endif  
1644 -  
1645 - } // endfor: looped over migration bins  
1646 -  
1647 - // Optional test dump  
1648 - #if defined(G_DEBUG_HESS_RENORMALIZATION)  
1649 - std::cout << "etrue=" << etrue << " TeV ";  
1650 - std::cout << "i_theta=" << i_theta << ": ";  
1651 - std::cout << sum << std::endl;  
1652 - #endif  
1653 -  
1654 - } // endfor: looped over offset angle  
1655 -  
1656 - } // endfor: looped over true energies  
1657 -  
1658 - // Return  
1659 - return;  
1660 -}  
1661 -  
1662 -  
1663 -/***********************************************************************//**  
1664 * @brief Return index of response table element 1581 * @brief Return index of response table element
1665 * 1582 *
1666 * @param[in] ietrue True energy index. 1583 * @param[in] ietrue True energy index.
@@ -1811,6 +1728,9 @@ double GCTAEdisp2D::table_value(const int&amp; base_ll, @@ -1811,6 +1728,9 @@ double GCTAEdisp2D::table_value(const int&amp; base_ll,
1811 1728
1812 /***********************************************************************//** 1729 /***********************************************************************//**
1813 * @brief Smoothed energy dispersion table 1730 * @brief Smoothed energy dispersion table
  1731 + *
  1732 + * This method implements the kludge for CTA that reduces the statistical
  1733 + * noise in the energy dispersion matrix.
1814 ***************************************************************************/ 1734 ***************************************************************************/
1815 void GCTAEdisp2D::smooth_table(void) 1735 void GCTAEdisp2D::smooth_table(void)
1816 { 1736 {
@@ -2178,23 +2098,47 @@ GNdarray GCTAEdisp2D::gaussian_array(const double&amp; mean, @@ -2178,23 +2098,47 @@ GNdarray GCTAEdisp2D::gaussian_array(const double&amp; mean,
2178 2098
2179 2099
2180 /***********************************************************************//** 2100 /***********************************************************************//**
2181 - * @brief Integration kernel for edisp_kern() class 2101 + * @brief Integration kernel for GCTAEdisp2D::edisp_kern class
2182 * 2102 *
2183 - * @param[in] x Function value. 2103 + * @param[in] logEobs Base 10 logarithm of observation energy (\f$\log_{10}\f$ MeV).
  2104 + * @return Energy dispersion (\f$(\log_{10}\f$ MeV\f$)^{-1}\f$).
2184 * 2105 *
2185 - * This method implements the integration kernel needed for the edisp_kern()  
2186 - * class. 2106 + * This method implements the function
  2107 + *
  2108 + * \f[
  2109 + * E_{\rm disp}(E_{\rm true}, \log_{10} E_{\rm reco}, \theta) =
  2110 + * E_{\rm disp}(E_{\rm true}, m, \theta) \times
  2111 + * \frac{\log 10 \times E_{\rm reco}}{E_{\rm true}}
  2112 + * \f]
  2113 + *
  2114 + * which is the integration kernel needed for the GCTAEdisp2D::edisp_kern
  2115 + * class. The class is used by GCTAEdisp2D::normalize_table to integrate the
  2116 + * energy dispersion information using
  2117 + *
  2118 + * \f[
  2119 + * \int_{\log_{10} E_{\rm reco}^{\rm min}}^{\log_{10} E_{\rm reco}^{\rm max}}
  2120 + * E_{\rm disp}(E_{\rm true}, \log_{10} E_{\rm reco}, \theta) \,
  2121 + * d(\log_{10} E_{\rm reco})
  2122 + * \f]
2187 ***************************************************************************/ 2123 ***************************************************************************/
2188 -double GCTAEdisp2D::edisp_kern::eval(const double& x) 2124 +double GCTAEdisp2D::edisp_kern::eval(const double& logEobs)
2189 { 2125 {
2190 - // Get function value  
2191 - double value = m_parent->operator()(x, m_logEsrc, m_theta); 2126 + // Get function value. Note that logEobs is in log10 MeV, while we need
  2127 + // the value in log10 TeV.
  2128 + double value = m_parent->operator()(logEobs-6.0, m_logEsrc, m_theta);
  2129 +
  2130 + // Compute reconstructed energy in MeV. Note that logEobs is in
  2131 + // log10 MeV!
  2132 + double ereco = std::pow(10.0, logEobs);
  2133 +
  2134 + // Normalize energy dispersion so that the units are per log10 MeV
  2135 + value *= gammalib::ln10 * ereco;
2192 2136
2193 // Compile option: Check for NaN 2137 // Compile option: Check for NaN
2194 #if defined(G_NAN_CHECK) 2138 #if defined(G_NAN_CHECK)
2195 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) { 2139 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
2196 std::cout << "*** ERROR: GCTAEdisp2D::edisp_kern::eval"; 2140 std::cout << "*** ERROR: GCTAEdisp2D::edisp_kern::eval";
2197 - std::cout << "(x=" << x << "): "; 2141 + std::cout << "(logEobs=" << logEobs << "): ";
2198 std::cout << " NaN/Inf encountered"; 2142 std::cout << " NaN/Inf encountered";
2199 std::cout << " (value=" << value; 2143 std::cout << " (value=" << value;
2200 std::cout << ")" << std::endl; 2144 std::cout << ")" << std::endl;