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 52 * @brief CTA 2D energy dispersion class
53 53 *
54 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 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 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 97 class GCTAEdisp2D : public GCTAEdisp {
65 98  
... ... @@ -135,7 +168,6 @@ private:
135 168 void set_table(void);
136 169 void set_boundaries(void);
137 170 void set_max_edisp(void) const;
138   - void convert_dm2dlogE(void);
139 171 void normalize_table(void);
140 172 int table_index(const int& ietrue,
141 173 const int& imigra,
... ... @@ -188,7 +220,7 @@ private:
188 220 m_parent(parent),
189 221 m_logEsrc(logEsrc),
190 222 m_theta(theta) { }
191   - double eval(const double& x);
  223 + double eval(const double& logEobs);
192 224 protected:
193 225 const GCTAEdisp2D* m_parent; //!< Pointer to parent class
194 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 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 183 * @param[in] logEobs Log10 of the measured energy (TeV).
184 184 * @param[in] logEsrc Log10 of the true photon energy (TeV).
... ... @@ -186,19 +186,20 @@ GCTAEdisp2D&amp; GCTAEdisp2D::operator=(const GCTAEdisp2D&amp; edisp)
186 186 * @param[in] phi Azimuth angle in camera system (rad). Not used in this method.
187 187 * @param[in] zenith Zenith angle in Earth system (rad). Not used in this method.
188 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 191 * Returns the energy dispersion
191 192 *
192 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 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 204 double GCTAEdisp2D::operator()(const double& logEobs,
204 205 const double& logEsrc,
... ... @@ -237,6 +238,19 @@ double GCTAEdisp2D::operator()(const double&amp; logEobs,
237 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 254 } // endif: migration was in valid range
241 255  
242 256 } // endif: true energy and offset angle was in valid range
... ... @@ -325,7 +339,7 @@ void GCTAEdisp2D::table(const GCTAResponseTable&amp; table)
325 339 * MATRIX - Migration matrix
326 340 *
327 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 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 419 * @brief Save energy dispersion table into FITS file
406 420 *
407 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 424 * Saves energy dispersion into a FITS file. If a file with the given
411 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 915 }
902 916  
903 917 // Compute integral
904   - prob += 0.5 * (f1 + f2) * std::log10(x2/x1);
  918 + prob += 0.5 * (f1 + f2) * (x2 - x1);
905 919  
906 920 // Set second node as first node
907 921 x1 = x2;
... ... @@ -923,7 +937,7 @@ double GCTAEdisp2D::prob_erecobin(const GEnergy&amp; ereco_min,
923 937 etrue_nodes.wgt_right(),
924 938 theta_nodes.wgt_left(),
925 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 943 } // endif: logEsrc, theta and migration range were valid
... ... @@ -1329,8 +1343,18 @@ void GCTAEdisp2D::compute_ebounds_src(const double&amp; theta,
1329 1343 * @brief Set table
1330 1344 *
1331 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 1359 * Sets the data members
1336 1360 * m_inx_etrue
... ... @@ -1367,9 +1391,6 @@ void GCTAEdisp2D::set_table(void)
1367 1391 // Set table boundaries
1368 1392 set_boundaries();
1369 1393  
1370   - // Convert table from dP/dm to dP/dlogE
1371   - convert_dm2dlogE();
1372   -
1373 1394 // Smooth energy dispersion table (kludge only for CTA)
1374 1395 #if defined(G_SMOOTH_EDISP_KLUDGE)
1375 1396 if (gammalib::strip_whitespace(m_edisp.telescope()) == "CTA") {
... ... @@ -1451,21 +1472,17 @@ void GCTAEdisp2D::set_max_edisp(void) const
1451 1472 * Normalize the energy dispersion table using
1452 1473 *
1453 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 1477 * \f]
1458 1478 *
1459 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 1487 * The normalisation is performed for each \f$E_{\rm true}\f$ and
1471 1488 * \f$\theta\f$ bin using a Romberg integration method. Since the
... ... @@ -1502,7 +1519,7 @@ void GCTAEdisp2D::normalize_table(void)
1502 1519 // Loop over true photon energy
1503 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 1523 double emin = std::log10(m_edisp.axis_lo(m_inx_etrue,i_etrue));
1507 1524 double emax = std::log10(m_edisp.axis_hi(m_inx_etrue,i_etrue));
1508 1525 double logEsrc = 0.5*(emin+emax);
... ... @@ -1516,9 +1533,9 @@ void GCTAEdisp2D::normalize_table(void)
1516 1533 // Loop over all energy intervals
1517 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 1540 // Setup integration function
1524 1541 edisp_kern integrand(this, logEsrc, theta);
... ... @@ -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 1581 * @brief Return index of response table element
1665 1582 *
1666 1583 * @param[in] ietrue True energy index.
... ... @@ -1811,6 +1728,9 @@ double GCTAEdisp2D::table_value(const int&amp; base_ll,
1811 1728  
1812 1729 /***********************************************************************//**
1813 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 1735 void GCTAEdisp2D::smooth_table(void)
1816 1736 {
... ... @@ -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 2137 // Compile option: Check for NaN
2194 2138 #if defined(G_NAN_CHECK)
2195 2139 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
2196 2140 std::cout << "*** ERROR: GCTAEdisp2D::edisp_kern::eval";
2197   - std::cout << "(x=" << x << "): ";
  2141 + std::cout << "(logEobs=" << logEobs << "): ";
2198 2142 std::cout << " NaN/Inf encountered";
2199 2143 std::cout << " (value=" << value;
2200 2144 std::cout << ")" << std::endl;
... ...