Commit a3cb26682d5ca7232b50777357c6cd2b758e8545

Authored by Jürgen Knödlseder
1 parent 97940c94

Implement radius gradient for radial disk model (#3391)

doc/dev/inst/cta/gammalib_cta.tex
... ... @@ -151,7 +151,7 @@ and
151 151 \end{equation}
152 152  
153 153 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
154   -\subsubsection{Radial disk}
  154 +\paragraph{Radial disk}
155 155  
156 156 The radial disk function $f(\theta)$ is given by
157 157 \begin{equation}
... ... @@ -168,11 +168,16 @@ with the partial derivatives
168 168 \end{equation}
169 169 and
170 170 \begin{equation}
171   -\frac{\partial f}{\partial \theta_0} = ? .
  171 +\frac{\partial f}{\partial \theta_0} = \left \{
  172 + \begin{array}{l l}
  173 + \displaystyle -2 \pi f^2(\theta) \sin \theta_0 & \mbox{if $\theta \le \theta_0$} \\ \\
  174 + \displaystyle 0 & \mbox{if $\theta > \theta_0$}
  175 + \end{array}
  176 + \right .
172 177 \end{equation}
173 178  
174 179 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
175   -\subsubsection{Radial ring}
  180 +\paragraph{Radial ring}
176 181  
177 182 The radial ring function $f(\theta)$ is given by
178 183 \begin{equation}
... ... @@ -198,7 +203,7 @@ and
198 203 \end{equation}
199 204  
200 205 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
201   -\subsubsection{Radial Gaussian}
  206 +\paragraph{Radial Gaussian}
202 207  
203 208 The radial Gaussian function $f(\theta)$ is given by
204 209 \begin{equation}
... ... @@ -214,7 +219,7 @@ and
214 219 \end{equation}
215 220  
216 221 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
217   -\subsubsection{Radial shell}
  222 +\paragraph{Radial shell}
218 223  
219 224 The shell function on a sphere is given by
220 225 \begin{equation}
... ...
include/GModelSpatialRadialDisk.hpp
... ... @@ -86,16 +86,17 @@ protected:
86 86 void init_members(void);
87 87 void copy_members(const GModelSpatialRadialDisk& model);
88 88 void free_members(void);
89   - void update(void) const;
  89 + void update(const bool& gradients) const;
90 90 virtual void set_region(void) const;
91 91  
92 92 // Protected members
93 93 GModelPar m_radius; //!< Disk radius (degrees)
94 94  
95 95 // Cached members used for pre-computations
96   - mutable double m_last_radius; //!< Last disk radius
97   - mutable double m_radius_rad; //!< Radius in radians
98   - mutable double m_norm; //!< Normalization
  96 + mutable double m_last_radius; //!< Last disk radius
  97 + mutable double m_radius_rad; //!< Radius in radians
  98 + mutable double m_norm; //!< Normalization
  99 + mutable double m_factor_gradient; //!< Gradient factor
99 100 };
100 101  
101 102  
... ...
src/model/GModelSpatialRadialDisk.cpp
... ... @@ -51,6 +51,7 @@ const GModelSpatialRegistry g_radial_disk_legacy_registry(&amp;g_radial_disk_legac
51 51 /* __ Macros _____________________________________________________________ */
52 52  
53 53 /* __ Coding definitions _________________________________________________ */
  54 +//#define G_SMOOTH_RADIUS_GRADIENT //!< Use smooth radius gradient
54 55  
55 56 /* __ Debug definitions __________________________________________________ */
56 57  
... ... @@ -293,11 +294,36 @@ double GModelSpatialRadialDisk::eval(const double&amp; theta,
293 294 const bool& gradients) const
294 295 {
295 296 // Update precomputation cache
296   - update();
  297 + update(gradients);
297 298  
298 299 // Set value
299 300 double value = (theta <= m_radius_rad) ? m_norm : 0.0;
300 301  
  302 + // Optionally compute gradients
  303 + if (gradients) {
  304 +
  305 + // Alternative radius
  306 + #if defined(G_SMOOTH_RADIUS_GRADIENT)
  307 + double g_radius = 0.0;
  308 + if (m_radius.is_free()) {
  309 + const double k = 1000.0;
  310 + double f1 = std::exp(-k * (m_radius_rad - theta));
  311 + double f2 = 1.0 + f1;
  312 + g_radius = m_norm * k / (f2 * f2) * m_radius.scale() *
  313 + gammalib::deg2rad;
  314 + }
  315 +
  316 + // Evaluate radius gradient
  317 + #else
  318 + double g_radius = (m_radius.is_free() && theta <= m_radius_rad)
  319 + ? m_factor_gradient : 0.0;
  320 + #endif
  321 +
  322 + // Set radius gradient
  323 + m_radius.factor_gradient(g_radius);
  324 +
  325 + } // endif: gradient computation was requested
  326 +
301 327 // Compile option: Check for NaN/Inf
302 328 #if defined(G_NAN_CHECK)
303 329 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
... ... @@ -522,16 +548,21 @@ void GModelSpatialRadialDisk::init_members(void)
522 548 m_radius.free();
523 549 m_radius.scale(1.0);
524 550 m_radius.gradient(0.0);
525   - m_radius.has_grad(false); // Radial components never have gradients
  551 +
  552 + // Signal that this model provides analytical parameter gradients
  553 + //m_ra.has_grad(true); // Not yet implemented
  554 + //m_dec.has_grad(true); // Not yet implemented
  555 + m_radius.has_grad(true);
526 556  
527 557 // Set parameter pointer(s)
528 558 m_pars.push_back(&m_radius);
529 559  
530 560 // Initialise precomputation cache. Note that zero values flag
531 561 // uninitialised as a zero radius is not meaningful
532   - m_last_radius = 0.0;
533   - m_radius_rad = 0.0;
534   - m_norm = 0.0;
  562 + m_last_radius = 0.0;
  563 + m_radius_rad = 0.0;
  564 + m_norm = 0.0;
  565 + m_factor_gradient = 0.0;
535 566  
536 567 // Return
537 568 return;
... ... @@ -554,9 +585,10 @@ void GModelSpatialRadialDisk::copy_members(const GModelSpatialRadialDisk&amp; model)
554 585 m_radius = model.m_radius;
555 586  
556 587 // Copy precomputation cache
557   - m_last_radius = model.m_last_radius;
558   - m_radius_rad = model.m_radius_rad;
559   - m_norm = model.m_norm;
  588 + m_last_radius = model.m_last_radius;
  589 + m_radius_rad = model.m_radius_rad;
  590 + m_norm = model.m_norm;
  591 + m_factor_gradient = model.m_factor_gradient;
560 592  
561 593 // Return
562 594 return;
... ... @@ -576,6 +608,8 @@ void GModelSpatialRadialDisk::free_members(void)
576 608 /***********************************************************************//**
577 609 * @brief Update precomputation cache
578 610 *
  611 + * @param[in] gradients Request gradient computation?
  612 + *
579 613 * Computes the normalization
580 614 * \f[
581 615 * {\tt m\_norm} = \frac{1}{2 \pi (1 - \cos r)}
... ... @@ -586,7 +620,7 @@ void GModelSpatialRadialDisk::free_members(void)
586 620 * approximation you might have expected:
587 621 * \f[{\tt m\_norm} = \frac{1}{\pi r ^ 2}\f]
588 622 ***************************************************************************/
589   -void GModelSpatialRadialDisk::update() const
  623 +void GModelSpatialRadialDisk::update(const bool& gradients) const
590 624 {
591 625 // Update if radius has changed
592 626 if (m_last_radius != radius()) {
... ... @@ -597,10 +631,26 @@ void GModelSpatialRadialDisk::update() const
597 631 // Compute disk radius in radians
598 632 m_radius_rad = radius() * gammalib::deg2rad;
599 633  
600   - // Perform precomputations
601   - double denom = gammalib::twopi * (1 - std::cos(m_radius_rad));
  634 + // Compute sine and cosine of radius, benefitting of the sincos
  635 + // function
  636 + double sin_radius;
  637 + double cos_radius;
  638 + if (gradients) {
  639 + sin_radius = std::sin(m_radius_rad);
  640 + cos_radius = std::cos(m_radius_rad);
  641 + }
  642 + else {
  643 + cos_radius = std::cos(m_radius_rad);
  644 + }
  645 +
  646 + // Precompute radial disk function
  647 + double denom = gammalib::twopi * (1.0 - cos_radius);
602 648 m_norm = (denom > 0.0) ? 1.0 / denom : 0.0;
603 649  
  650 + // Precompute factor gradient
  651 + m_factor_gradient = -gammalib::twopi * m_norm * m_norm * sin_radius *
  652 + m_radius.scale() * gammalib::deg2rad;
  653 +
604 654 } // endif: update required
605 655  
606 656 // Return
... ...
test/dev/test_disk_approximation.py 0 → 100755
  1 +#! /usr/bin/env python
  2 +# ==========================================================================
  3 +# Approximate radial disk function
  4 +# --------------------------------------------------------------------------
  5 +#
  6 +# Copyright (C) 2020 Juergen Knoedlseder
  7 +#
  8 +# This program is free software: you can redistribute it and/or modify
  9 +# it under the terms of the GNU General Public License as published by
  10 +# the Free Software Foundation, either version 3 of the License, or
  11 +# (at your option) any later version.
  12 +#
  13 +# This program is distributed in the hope that it will be useful,
  14 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16 +# GNU General Public License for more details.
  17 +#
  18 +# You should have received a copy of the GNU General Public License
  19 +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  20 +#
  21 +# ==========================================================================
  22 +import math
  23 +import gammalib
  24 +import matplotlib.pyplot as plt
  25 +
  26 +
  27 +# ========================== #
  28 +# Compare radial model to MC #
  29 +# ========================== #
  30 +def show(theta0, k=1000.0, rad_max=1.0, nrad=1000):
  31 + """
  32 + Show smooth approximation of radial disk function.
  33 + """
  34 + # Get dummy energy and time
  35 + energy = gammalib.GEnergy()
  36 + time = gammalib.GTime()
  37 +
  38 + # Generate theta vector
  39 + theta_bin = rad_max/nrad
  40 + theta = [(i+0.5) * theta_bin for i in range(nrad)]
  41 +
  42 + # Generate model vector
  43 + model = [1.0 / (1.0 + math.exp(-k * (theta0 - t))) for t in theta]
  44 + disk = [1.0 if t < theta0 else 0.0 for t in theta]
  45 +
  46 + # Create figure
  47 + plt.figure(1)
  48 + plt.title('Smooth approximation of radial disk function')
  49 +
  50 + # Plot data
  51 + plt.plot(theta, model, 'r-')
  52 + plt.plot(theta, disk, 'g-')
  53 +
  54 + # Set axes
  55 + plt.xlabel('Offset (deg)')
  56 + plt.ylabel('Arbitrary units')
  57 +
  58 + # Notify
  59 + print('PLEASE CLOSE WINDOW TO CONTINUE ...')
  60 +
  61 + # Show plot
  62 + plt.show()
  63 +
  64 + # Return
  65 + return
  66 +
  67 +
  68 +# ======================== #
  69 +# Main routine entry point #
  70 +# ======================== #
  71 +if __name__ == '__main__':
  72 +
  73 + # Show radial model
  74 + show(0.5)
... ...