Commit 6fd57dc6f241dc53a633e6b3ed8a86318f54ce2c

Authored by ldivenere
2 parents a54c4b50 bd372e66

Merge branch 'devel' of https://cta-gitlab.irap.omp.eu/gammalib/gammalib into devel

1 1 Rolf Buehler (rolf.buehler@desy.de)
2   -François Brun (brun@cenbg.in2p3.fr)
  2 +François Brun (francois.brun@cea.fr)
3 3 Joshua Cardenzana (jcardenzana@irap.omp.eu)
4 4 Jean-Baptiste Cayrou (cayrou@etud.insa-toulouse.fr)
5 5 Christoph Deil (Christoph.Deil@mpi-hd.mpg.de)
... ...
ChangeLog
1   -2018-11-27
  1 +2019-01-09
2 2  
3 3 * Version 1.6.0 released
4 4 ========================
5 5  
  6 + Improve OpenMP handling in GWcs::dir2pix (#2770)
6 7 Add support for Fermi/LAT P8V3 response functions
7 8 CTA background rates are now per ontime (#2758)
  9 + Add GPythonOptimizerFunction class (#2733)
8 10 Remove diffuse response cube caching (#2722)
9 11 Revise GCTAEdispCube::operator() interface and disable stacked response
10 12 cutting in true energy (#2721)
... ...
1 1 New Features and Important Changes in GammaLib 1.6.0
2 2  
3   -23 November 2018
  3 +9 January 2019
4 4  
5 5  
6 6 1. Introduction
... ... @@ -14,6 +14,7 @@ made since the last release of GammaLib.
14 14 The following classes have been added:
15 15 - GModelSpectralExponential
16 16 - GWcsARC
  17 +- GPythonOptimizerFunction
17 18 - GCTAResponseCache
18 19 - GCTAModelBackground
19 20 - GCTAModelSpatial
... ... @@ -247,6 +248,9 @@ None
247 248  
248 249 12. Sky module
249 250 --------------
  251 +An OpenMP locking mechanism was added to GWcs::dir2pix that improves the speed of
  252 +the code parallelization by avoiding mutual lockups of the threads (#2770).
  253 +
250 254 The class GWcsARC was added to handle ARC WCS projections (#2541). In addition
251 255 the private GWcs::prj_bchk() method was added to implement boundary checking
252 256 similar to the checking done in WCS.
... ...
doc/source/admin/release_history/1.6.rst
... ... @@ -49,7 +49,11 @@ Bug fixes
49 49 Improvements
50 50 ------------
51 51  
  52 +* [`2770 <https://cta-redmine.irap.omp.eu/issues/2770>`_] -
  53 + Improve OpenMP handling in ``GWcs::dir2pix``
52 54 * Add support for Fermi/LAT P8V3 response functions
  55 +* [`2733 <https://cta-redmine.irap.omp.eu/issues/2733>`_] -
  56 + Add ``GPythonOptimizerFunction`` class
53 57 * [`2721 <https://cta-redmine.irap.omp.eu/issues/2721>`_] -
54 58 Revise ``GCTAEdispCube::operator()`` interface and disable stacked response
55 59 cutting in true energy
... ...
include/GWcs.hpp
1 1 /***************************************************************************
2 2 * GWcs.hpp - Abstract world coordinate system base class *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2011-2018 by Juergen Knoedlseder *
  4 + * copyright (C) 2011-2019 by Juergen Knoedlseder *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ... @@ -30,6 +30,7 @@
30 30 /* __ Includes ___________________________________________________________ */
31 31 #include <vector>
32 32 #include <string>
  33 +#include <map>
33 34 #include <iostream>
34 35 #include "GSkyProjection.hpp"
35 36 #include "GLog.hpp"
... ... @@ -37,6 +38,10 @@
37 38 #include "GSkyDir.hpp"
38 39 #include "GSkyPixel.hpp"
39 40  
  41 +#ifdef _OPENMP
  42 +#include <omp.h>
  43 +#endif
  44 +
40 45  
41 46 /***********************************************************************//**
42 47 * @class GWcs
... ... @@ -220,6 +225,15 @@ protected:
220 225 mutable GSkyDir m_last_dir2pix_dir; //!< Last sky direction for dir2pix
221 226 mutable GSkyPixel m_last_pix2dir_pix; //!< Last pixel for pix2dir
222 227 mutable GSkyPixel m_last_dir2pix_pix; //!< Last pixel for dir2pix
  228 +
  229 + // Thread locking
  230 + void init_lock(const int& lock_id=0) const;
  231 + void set_lock(const int& lock_id=0) const;
  232 + void unset_lock(const int& lock_id=0) const;
  233 +
  234 + #ifdef _OPENMP
  235 + mutable std::map<int, omp_lock_t> m_locks;
  236 + #endif
223 237 };
224 238  
225 239  
... ... @@ -236,4 +250,49 @@ int GWcs::size(void) const
236 250 return 2;
237 251 }
238 252  
  253 +
  254 +/***********************************************************************//**
  255 + * @brief Initializes an OpenMP lock with a specific name
  256 + *
  257 + * @param[in] lock_id Identifier of the lock that should be initialized
  258 + ***************************************************************************/
  259 +inline
  260 +void GWcs::init_lock(const int& lock_id) const
  261 +{
  262 + #ifdef _OPENMP
  263 + if (m_locks.count(lock_id) == 0) {
  264 + m_locks[lock_id] = omp_lock_t();
  265 + omp_init_lock(&m_locks[lock_id]);
  266 + }
  267 + #endif
  268 +}
  269 +
  270 +
  271 +/***********************************************************************//**
  272 + * @brief Sets an OpenMP lock with a specific name
  273 + *
  274 + * @param[in] lock_id Identifier of the lock that should be set
  275 + ***************************************************************************/
  276 +inline
  277 +void GWcs::set_lock(const int& lock_id) const
  278 +{
  279 + #ifdef _OPENMP
  280 + omp_set_lock(&m_locks[lock_id]);
  281 + #endif
  282 +}
  283 +
  284 +
  285 +/***********************************************************************//**
  286 + * @brief Releases a previously set OpenMP lock
  287 + *
  288 + * @param[in] lock_id Identifier of the lock to be released
  289 + ***************************************************************************/
  290 +inline
  291 +void GWcs::unset_lock(const int& lock_id) const
  292 +{
  293 + #ifdef _OPENMP
  294 + omp_unset_lock(&m_locks[lock_id]);
  295 + #endif
  296 +}
  297 +
239 298 #endif /* GWCS_HPP */
... ...
inst/cta/src/GCTAOnOffObservation.cpp
... ... @@ -2675,8 +2675,11 @@ double GCTAOnOffObservation::likelihood_cstat(const GModels&amp; models,
2675 2675 double ngam = N_gamma(models, i, &sky_grad);
2676 2676 double nbgd = N_bgd(models, i, &bgd_grad);
2677 2677  
2678   - // Skip bin if model is too small (avoids -Inf or NaN gradients)
  2678 + // Compute and updated predicted number of events
2679 2679 double nonpred = ngam + alpha * nbgd;
  2680 + *npred += nonpred;
  2681 +
  2682 + // Skip bin if model is too small (avoids -Inf or NaN gradients)
2680 2683 if ((nbgd <= minmod) || (nonpred <= minmod)) {
2681 2684 #if defined(G_LIKELIHOOD_DEBUG)
2682 2685 n_small_model++;
... ... @@ -2685,10 +2688,8 @@ double GCTAOnOffObservation::likelihood_cstat(const GModels&amp; models,
2685 2688 }
2686 2689  
2687 2690 // Now we have all predicted gamma and background events for
2688   - // current energy bin. Update the log(likelihood) and predicted
2689   - // number of events
2690   - value += -non * log(nonpred) + nonpred - noff * log(nbgd) + nbgd;
2691   - *npred += nonpred;
  2691 + // current energy bin. Update the log(likelihood)
  2692 + value += -non * std::log(nonpred) + nonpred - noff * std::log(nbgd) + nbgd;
2692 2693  
2693 2694 // Update statistics
2694 2695 #if defined(G_LIKELIHOOD_DEBUG)
... ...
pyext/GPythonOptimizerFunction.i 0 → 100644
  1 +/***************************************************************************
  2 + * GPythonOptimizerFunction.i - Python optimizer function class *
  3 + * ----------------------------------------------------------------------- *
  4 + * copyright (C) 2018 by Juergen Knoedlseder *
  5 + * ----------------------------------------------------------------------- *
  6 + * *
  7 + * This program is free software: you can redistribute it and/or modify *
  8 + * it under the terms of the GNU General Public License as published by *
  9 + * the Free Software Foundation, either version 3 of the License, or *
  10 + * (at your option) any later version. *
  11 + * *
  12 + * This program is distributed in the hope that it will be useful, *
  13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  15 + * GNU General Public License for more details. *
  16 + * *
  17 + * You should have received a copy of the GNU General Public License *
  18 + * along with this program. If not, see <http://www.gnu.org/licenses/>. *
  19 + * *
  20 + ***************************************************************************/
  21 +/**
  22 + * @file GPythonOptimizerFunction.i
  23 + * @brief Python optimizer function class
  24 + * @author Juergen Knoedlseder
  25 + */
  26 +%{
  27 +/* Put headers and other declarations here that are needed for compilation */
  28 +#include "GOptimizerFunction.hpp"
  29 +#include "GTools.hpp"
  30 +#include "GVector.hpp"
  31 +#include "GMatrixSparse.hpp"
  32 +#include "GException.hpp"
  33 +
  34 +
  35 +/***********************************************************************//**
  36 + * @class GPythonException
  37 + *
  38 + * @brief Python exception
  39 + *
  40 + * This class implements a Python exception. It allows to catch any error
  41 + * message that occurs during function evaluation.
  42 + ***************************************************************************/
  43 +class GPythonException : public GExceptionHandler {
  44 +public:
  45 + class eval_error : public GExceptionHandler {
  46 + public:
  47 + eval_error(const std::string& origin) {
  48 +
  49 + // Set origin and default message
  50 + m_origin = origin;
  51 + m_message = "";
  52 +
  53 + // Set message
  54 + if (PyErr_Occurred()) {
  55 +
  56 + // Fetch error type, value and traceback
  57 + PyObject *type = 0;
  58 + PyObject *value = 0;
  59 + PyObject *traceback = 0;
  60 + PyErr_Fetch(&type, &value, &traceback);
  61 +
  62 + // Clear error message
  63 + PyErr_Clear();
  64 +
  65 + // Extract message strings
  66 + PyObject *py_type = PyObject_Str(type);
  67 + PyObject *py_value = PyObject_Str(value);
  68 + char *c_type = NULL;
  69 + char *c_value = NULL;
  70 + if (PyUnicode_Check(py_type)) {
  71 + PyObject* temp_bytes = PyUnicode_AsEncodedString(py_type,
  72 + "utf-8",
  73 + "Error ~");
  74 + if (temp_bytes != NULL) {
  75 + c_type = PyString_AsString(temp_bytes);
  76 + }
  77 + }
  78 + else if (py_type != NULL) {
  79 + c_type = PyString_AsString(py_type);
  80 + }
  81 + if (c_type != NULL) {
  82 + m_message += std::string(c_type);
  83 + m_message += "\n";
  84 + }
  85 + if (PyUnicode_Check(py_value)) {
  86 + PyObject* temp_bytes = PyUnicode_AsEncodedString(py_value,
  87 + "utf-8",
  88 + "Error ~");
  89 + if (temp_bytes != NULL) {
  90 + c_value = PyString_AsString(temp_bytes);
  91 + }
  92 + }
  93 + else if (py_value != NULL) {
  94 + c_value = PyString_AsString(py_value);
  95 + }
  96 + if (c_value != NULL) {
  97 + m_message += std::string(c_value);
  98 + }
  99 + Py_DECREF(py_type);
  100 + Py_DECREF(py_value);
  101 + }
  102 + }
  103 + };
  104 +};
  105 +
  106 +
  107 +/***********************************************************************//**
  108 + * @class GPythonOptimizerFunction
  109 + *
  110 + * @brief Python interface for optimizer function
  111 + *
  112 + * This class implements the call back for a Python optimizer function,
  113 + * allowing to use the GammaLib optimizer classes from within Python.
  114 + ***************************************************************************/
  115 +class GPythonOptimizerFunction : public GOptimizerFunction {
  116 +public:
  117 + // Constructor
  118 + GPythonOptimizerFunction(void) {
  119 + m_py_object = NULL;
  120 + m_value = 0.0;
  121 + m_gradient.clear();
  122 + m_curvature.clear();
  123 + m_pars.clear();
  124 + }
  125 +
  126 + // Destructor
  127 + virtual ~GPythonOptimizerFunction(void) {
  128 + if (m_py_object != NULL) {
  129 + Py_DECREF(m_py_object);
  130 + }
  131 + }
  132 +
  133 + // Evaluate Python function
  134 + void eval(const GOptimizerPars& pars) {
  135 +
  136 + // Store parameters
  137 + m_pars = pars;
  138 +
  139 + // Initialise value, gradient and curvature
  140 + int npars = pars.size();
  141 + if (npars < 1) {
  142 + npars = 1;
  143 + }
  144 +
  145 + // Initialise value, gradient vector and curvature matrix
  146 + m_value = 0.0;
  147 + m_gradient = GVector(npars);
  148 + m_curvature = GMatrixSparse(npars,npars);
  149 +
  150 + // Execute evaluation
  151 + PyObject* args = Py_BuildValue("()");
  152 + PyObject* func = static_cast<PyObject*>(m_py_object);
  153 + PyObject* res = PyEval_CallObject(func, args);
  154 + Py_DECREF(args);
  155 +
  156 + // Release result
  157 + if (res != NULL) {
  158 + Py_DECREF(res);
  159 + }
  160 + else {
  161 + throw GPythonException::eval_error("GPythonOptimizerFunction::eval");
  162 + }
  163 + }
  164 +
  165 + // Return value
  166 + double value(void) const {
  167 + return m_value;
  168 + }
  169 +
  170 + // Return gradient
  171 + GVector* gradient(void) {
  172 + return &m_gradient;
  173 + }
  174 +
  175 + // Return curvature
  176 + GMatrixSparse* curvature(void) {
  177 + return &m_curvature;
  178 + }
  179 +
  180 + // Return parameters
  181 + const GOptimizerPars& _pars(void) const {
  182 + return m_pars;
  183 + }
  184 +
  185 + // Set Python callback function
  186 + void _set_eval(PyObject* function) {
  187 + m_py_object = function;
  188 + Py_INCREF(function);
  189 + }
  190 +
  191 + // Set value
  192 + void _set_value(const double& value) {
  193 + m_value = value;
  194 + }
  195 +
  196 + // Callback information
  197 + PyObject* m_py_object; //!< Python callback function
  198 + double m_value; //!< Function value
  199 + GVector m_gradient; //!< Function gradient
  200 + GMatrixSparse m_curvature; //!< Function curvature
  201 + GOptimizerPars m_pars; //!< Current parameters
  202 +};
  203 +%}
  204 +
  205 +
  206 +/***********************************************************************//**
  207 + * @class GPythonOptimizerFunction
  208 + *
  209 + * @brief Optimizer class Python interface definition
  210 + ***************************************************************************/
  211 +class GPythonOptimizerFunction : public GOptimizerFunction {
  212 +public:
  213 + // Constructors and destructors
  214 + GPythonOptimizerFunction(void);
  215 + virtual ~GPythonOptimizerFunction(void);
  216 +
  217 + // Methods
  218 + void eval(const GOptimizerPars& pars);
  219 + double value(void) const;
  220 + GVector* gradient(void);
  221 + GMatrixSparse* curvature(void);
  222 +
  223 + // Protected methods
  224 + const GOptimizerPars& _pars(void) const;
  225 + void _set_eval(PyObject* function);
  226 + void _set_value(const double& value);
  227 +};
... ...
pyext/gammalib/opt.i
1 1 /***************************************************************************
2 2 * opt.i - Optimization module *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2011-2017 by Juergen Knoedlseder *
  4 + * copyright (C) 2011-2018 by Juergen Knoedlseder *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ... @@ -90,3 +90,4 @@
90 90 %include "GOptimizerPar.i"
91 91 %include "GOptimizerPars.i"
92 92 %include "GOptimizerFunction.i"
  93 +%include "GPythonOptimizerFunction.i"
... ...
src/sky/GSkyDir.cpp
... ... @@ -800,51 +800,52 @@ bool operator==(const GSkyDir &amp;a, const GSkyDir &amp;b)
800 800  
801 801 // Compute dependent on coordinate system availability. This speeds
802 802 // up things by avoiding unnecessary coordinate transformations.
803   - if (a.m_has_lb) {
804   - if (b.m_has_lb) {
805   - if (std::abs(a.m_b) == 90.0) {
806   - equal = (a.m_b == b.m_b);
807   - }
808   - else {
809   - equal = (a.m_b == b.m_b && a.m_l == b.m_l);
810   - }
  803 +
  804 + // Check if both have equatorial coordinates
  805 + if (a.m_has_radec && b.m_has_radec) {
  806 + if (std::abs(a.m_dec) == 90.0) {
  807 + equal = (a.m_dec == b.m_dec);
811 808 }
812 809 else {
  810 + equal = (a.m_dec == b.m_dec && a.m_ra == b.m_ra);
  811 + }
  812 + }
  813 + // ... check if both have Galactic coordinates
  814 + else if (a.m_has_lb && b.m_has_lb) {
  815 + if (std::abs(a.m_b) == 90.0) {
  816 + equal = (a.m_b == b.m_b);
  817 + }
  818 + else {
  819 + equal = (a.m_b == b.m_b && a.m_l == b.m_l);
  820 + }
  821 + }
  822 + // ... otherwise the coordinate systems are different
  823 + else {
  824 + if (a.m_has_lb) {
813 825 if (std::abs(a.m_b) == 90.0) {
814 826 equal = (a.m_b == b.b());
815 827 }
816 828 else {
817 829 equal = (a.m_b == b.b() && a.m_l == b.l());
818 830 }
819   - }
820   - }
821   - else if (a.m_has_radec) {
822   - if (b.m_has_radec) {
  831 + }
  832 + else if (a.m_has_radec) {
823 833 if (std::abs(a.m_dec) == 90.0) {
824   - equal = (a.m_dec == b.m_dec);
  834 + equal = (a.m_dec == b.dec());
825 835 }
826 836 else {
827   - equal = (a.m_dec == b.m_dec && a.m_ra == b.m_ra);
  837 + equal = (a.m_dec == b.dec() && a.m_ra == b.ra());
828 838 }
829 839 }
830 840 else {
831   - if (std::abs(a.m_dec) == 90.0) {
832   - equal = (a.m_dec == b.dec());
  841 + if (std::abs(b.dec()) == 90.0) {
  842 + equal = (b.dec() == a.dec());
833 843 }
834 844 else {
835   - equal = (a.m_dec == b.dec() && a.m_ra == b.ra());
  845 + equal = (b.dec() == a.dec() && b.ra() == a.ra());
836 846 }
837 847 }
838 848 }
839   - else {
840   - if (std::abs(b.dec()) == 90.0) {
841   - equal = (b.dec() == a.dec());
842   - }
843   - else {
844   - equal = (b.dec() == a.dec() && b.ra() == a.ra());
845   - }
846   - }
847   -
848 849 // Return equality
849 850 return equal;
850 851 }
... ...
src/sky/GWcs.cpp
1 1 /***************************************************************************
2 2 * GWcs.cpp - Abstract world coordinate system base class *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2011-2018 by Juergen Knoedlseder *
  4 + * copyright (C) 2011-2019 by Juergen Knoedlseder *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ... @@ -678,13 +678,16 @@ GSkyDir GWcs::pix2dir(const GSkyPixel&amp; pixel) const
678 678 // the one used for the cache, then get the cached sky direction. We need
679 679 // to put this in a OMP critical zone so that no other thread will change
680 680 // the cache in the meantime
681   - #pragma omp critical(GWcs_pix2dir_1)
  681 + bool update_cache = true;
  682 + set_lock(2);
682 683 if (m_has_pix2dir_cache && (m_last_pix2dir_pix == pixel)) {
683 684 dir = m_last_pix2dir_dir;
  685 + update_cache = false;
684 686 }
  687 + unset_lock(2);
685 688  
686 689 // ... otherwise the computation of the sky direction is needed
687   - else {
  690 + if (update_cache) {
688 691  
689 692 // Allocate memory for transformation
690 693 double pixcrd[2];
... ... @@ -712,12 +715,13 @@ GSkyDir GWcs::pix2dir(const GSkyPixel&amp; pixel) const
712 715  
713 716 // Store result in cache. We need to put this in a OMP critical zone
714 717 // to make sure that no other thread is fiddeling with the cache
715   - #pragma omp critical(GWcs_pix2dir_2)
  718 + set_lock(2);
716 719 {
717 720 m_has_pix2dir_cache = true;
718 721 m_last_pix2dir_pix = pixel;
719 722 m_last_pix2dir_dir = dir;
720 723 }
  724 + unset_lock(2);
721 725  
722 726 // Debug: Dump transformation steps
723 727 #if defined(G_XY2DIR_DEBUG)
... ... @@ -758,13 +762,16 @@ GSkyPixel GWcs::dir2pix(const GSkyDir&amp; dir) const
758 762 // to the one used for the cache, then get the cached sky pixel. We need
759 763 // to put this in a OMP critical zone so that no other thread will change
760 764 // the cache in the meantime
761   - #pragma omp critical(GWcs_dir2pix_1)
  765 + bool update_cache = true;
  766 + set_lock(1);
762 767 if (m_has_dir2pix_cache && (m_last_dir2pix_dir == dir)) {
763 768 pixel = m_last_dir2pix_pix;
  769 + update_cache = false;
764 770 }
  771 + unset_lock(1);
765 772  
766 773 // ... otherwise the computation of the sky pixel is needed
767   - else {
  774 + if (update_cache) {
768 775  
769 776 // Allocate memory for transformation
770 777 double pixcrd[2];
... ... @@ -793,12 +800,13 @@ GSkyPixel GWcs::dir2pix(const GSkyDir&amp; dir) const
793 800  
794 801 // Store result in cache. We need to put this in a OMP critical zone
795 802 // to make sure that no other thread is fiddeling with the cache
796   - #pragma omp critical(GWcs_dir2pix_2)
  803 + set_lock(1);
797 804 {
798 805 m_has_dir2pix_cache = true;
799 806 m_last_dir2pix_dir = dir;
800 807 m_last_dir2pix_pix = pixel;
801 808 }
  809 + unset_lock(1);
802 810  
803 811 // Debug: Dump transformation steps
804 812 #if defined(G_DIR2XY_DEBUG)
... ... @@ -944,6 +952,10 @@ void GWcs::init_members(void)
944 952 m_last_pix2dir_pix.clear();
945 953 m_last_dir2pix_pix.clear();
946 954  
  955 + // Initialize the locks
  956 + init_lock(1);
  957 + init_lock(2);
  958 +
947 959 // Return
948 960 return;
949 961 }
... ...