The code configuration is controlled via the config.h header file that is created during the configuration step of the GammaLib build. To make configuration options available the following code has to be added to the source code file:
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
Warning
The config.h file should never be included in header files. The header files are used by the outside world without any knowledge about config.h.
One of the most commonly used configuration options used throughout GammaLib is related to range checking. Range checks are usually performed when accessing array elements, assuring that no elements outside the valid range are accessed. Range checking, however, is time consuming, in particular if many elements are accessed subsequently. GammaLib therefore allows to disable range checks. This can be done by specifying ./configure --disable-range-check when configuring GammaLib for compilation. The --disable-range-check option undefines G_RANGE_CHECK, and optional range checking is thus achieved by adding for example
#if defined(G_RANGE_CHECK)
if (inx < 0 || inx >= m_num) {
throw GException::out_of_range("GVector::operator(int)", inx, m_num);
}
#endif
to the code.
The following table gives a list of important configuration options that are available in config.h and that can be used to tune the code:
Definition | Option | Usage |
---|---|---|
G_DEBUG | --enable-debug | Code debugging |
G_PROFILE | --enable-profiling | Code profiling |
G_RANGE_CHECK | --enable-range-check | Performs range checking |
G_NAN_CHECK | --enable-nan-check | Check for NaN and Inf values |
G_SMALL_MEMORY | --enable-small-memory | Optimizes for small memory |
HAVE_OPENMP | --enable-openmp | Has OpenMP multi-threading support |
HAVE_LIBREADLINE | --with-readline | Has readline library |
HAVE_LIBCFITSIO | --with-cfitsio | Has cfitsio library |
HAVE_PYTHON | --enable-python-binding | Has Python bindings |
PACKAGE | n.a. | gammalib |
PACKAGE_PREFIX | n.a. | Installation location (e.g. /usr/local/gamma) |
PACKAGE_STRING | n.a. | Full name and version of GammaLib |
PACKAGE_VERSION | n.a. | Version of GammaLib (format: x.y.z) |
Note that enable may be replaced by disable and with by without for switching off an option.
Class members should be either private or protected, the latter being generally used when a derived class should be able to access base class data.
Members should be prefixed by m_ and should be in lower case. For long member names, additional underscores may be added. Examples of valid member names are
m_num
m_response
m_grid_length
m_axis_dir_qual
Initialisation, copying and deleting of class members should be gathered in a single place to avoid memory leaks. For this purpose, each C++ class should have the following private or protected methods for memory management:
init_members()
initializes all member variables and pointers to well defined initial values. The class should be fully operational and consistent with these initial values. All pointers that will hold dynamically allocated memory should be initialised to NULL.
copy_members(const &A a)
copies all members from an instance a into the class.
free_members()
frees all memory that has been allocated by the class. Memory pointers should be set to NULL after the memory was deleted to signal that no valid memory is associated to the pointer. This allows for checking if memory has been allocated before it is accessed.
(in the above notation, A is the class name and a is an instance of the class).
An example for valid init_members(), copy_members(const &A a) and free_members() methods is:
void GEbounds::init_members(void)
{
m_num = 0;
m_min = NULL;
m_max = NULL;
return;
}
void GEbounds::copy_members(const GEbounds& ebds)
{
m_num = ebds.m_num;
if (m_num > 0) {
m_min = new GEnergy[m_num];
m_max = new GEnergy[m_num];
for (int i = 0; i < m_num; ++i) {
m_min[i] = ebds.m_min[i];
m_max[i] = ebds.m_max[i];
}
}
return;
}
void GEbounds::free_members(void)
{
if (m_min != NULL) delete [] m_min;
if (m_max != NULL) delete [] m_max;
m_min = NULL;
m_max = NULL;
return;
}
In this example, one may probably want to add a alloc_members() method for memory allocation:
void GEbounds::alloc_members(const int& num)
{
if (num > 0) {
m_min = new GEnergy[num];
m_max = new GEnergy[num];
for (int i = 0; i < num; ++i) {
m_min[i] = 0.0;
m_max[i] = 0.0;
}
m_num = num;
}
return;
}
This example illustrates several design conventions:
Each class should have at least a void constructor, a copy constructor, a destructor and an assignment operator. Additional constructors and operators can be implemented as required. The following example shows the basic implementation for these 4 methods. Due to the usage of the init_members(), copy_members(const &A a) and the free_members() methods, most classes will have exactly this kind of syntax:
GEbounds::GEbounds(void)
{
init_members();
return;
}
GEbounds::GEbounds(const GEbounds& ebds)
{
init_members();
copy_members(ebds);
return;
}
GEbounds::~GEbounds(void)
{
free_members();
return;
}
GEbounds& GEbounds::operator= (const GEbounds& ebds)
{
if (this != &ebds) {
free_members();
init_members();
copy_members(ebds);
}
return *this;
}
Class inheritance is central feature of the C++ language, and is largely used throughout GammaLib. Multiple inheritance is not used at the moment in GammaLib. Because of the added complexity of multiple inheritance in C++ and in Python there would have to be very good reasons to use it in GammaLib.
Although the inheritance philosophy may differ from class to class, the following guidelines should be respected as far as possible:
The base class and derived class destructors should be declared virtual.
Avoid overloading of base class methods by derived class methods. Preferentially, define base class methods as pure virtual.
All base class methods that should be implemented in the derived class should be declared virtual. Exceptions are the init_members(), the copy_members() and the free_members() methods that will be implemented in the base class and the derived class.
Base classes manage base class members, derived classes manage derived class members. By managing we mean here in particular memory allocation and de-allocation, but also proper initialization.
Derived class constructors should invoke base class constructors for proper base class initialization. A void constructor should look like
GEventList::GEventList(void) : GEvents()
{
init_members();
return;
}
and a copy constructor should look like
GEventList::GEventList(const GEventList& list) : GEvents(list)
{
init_members();
copy_members(list);
return;
}
Derived class operators should invoke base class operators, as illustrated by the following example:
GEventList& GEventList::operator=(const GEventList& list)
{
if (this != &list) {
this->GEvents::operator=(list);
free_members();
init_members();
copy_members(list);
}
return *this;
}
The clear() method of a derived class show invoke the free_members() method of the base class, as illustrated by the following example:
void GCTAEventList::clear(void)
{
free_members();
this->GEventList::free_members();
this->GEvents::free_members();
this->GEvents::init_members();
this->GEventList::init_members();
init_members();
return;
}
Avoid as far as possible methods that are only defined in the derived class.
Warning
For a derived class, init_members(), copy_members(const &A a) and free_members() should only act on derived class members but not on base class members. Any exception to this rule needs very careful documentation since it can easily be the source of memory leaks.
Uniform public method names should be provided throughout GammaLib for all classes. Unless the public method names are very long (which should be avoided), names should not comprise underscores as separators. Public method names are all lowercase.
Private or protected method names may differ from this since they are hidden within the class. Yet also here, all method names should be lowercase, and the use of underscores should be limited.
Methods that set or retrieve class attributes should be named after the attribute. Here an example for the attribute m_name:
public:
void name(const std::string& name);
std::string name(void) const;
protected:
m_name;
A method name that is used in multiple classes should always perform an equivalent action. Here is a list of method names that are widely used in GammaLib, together with their typical usage. The last column specifies where these methods are used. Note that the ``clear()``, ``clone()``, and ``print()`` methods should be implemented for all classes.
Method | Usage | Implementation |
---|---|---|
clear | Set object to initial empty state | all classes |
clone | Provides a deep copy of the class | all classes |
Print object into string | all classes (see Output) | |
is_empty | Checks for emptiness of object | Container classes |
append | Append element to list of elements | Container classes |
extend | Append container elements to list of elements | Container classes |
insert | Insert element to list of elements | Container classes |
remove | Remove element from list of elements | Container classes |
reserve | Reserve memory for a number of elements | Container classes |
load | Load data from file (open, read, close) | if applicable |
save | Save data into file (open, write, close) | if applicable |
open | Open file | if applicable |
read | Read data from open file | if applicable |
write | Write data into open file | if applicable |
close | Close file | if applicable |
name | Name of object | if applicable |
type | Type of object | if applicable |
size | Size of object | if applicable |
real | Returns double precision value | if applicable |
integer | Returns int value | if applicable |
string | Returns std::string value | if applicable |
Note the difference between load() and read() and between save() and write(). The load() and save() methods should take as arguments a file name, and they will open the file, read or write some data, and then close the file. In contrast, read() and write() will operate on files that are already open, and after the read or write operation the files will remain open. Typically, these methods take a GFits* or a GFitsHDU* pointer as argument.
Methods that perform checks should return a bool type and should start with the prefix is_ or has_, e.g. is_empty() or has_min().
All methods that do not alter accessible class members should be declared const. With accessible we mean here class members that can be read or written in some way by one of the methods. Non-accessible class members would be members that are only used internally, and for which no consistent state has to be preserved for the outside world. These could for example be members that hold pre-computed values.
Methods that do not alter accessible members, but that modify non-accessible members, should also be declared const. The non-accessible class members need then to be declared mutable to avoid compiler errors. Alternatively, the const_cast declaration can be used to allow member modifications within a const method.
As example we show here part of the definition of GModelSpectralPlaw2:
class GModelSpectralPlaw2 : public GModelSpectral {
public:
virtual double eval(const GEnergy& srcEng) const;
virtual void read(const GXmlElement& xml);
protected:
// Protected members
GModelPar m_integral; //!< Integral flux
GModelPar m_index; //!< Spectral index
GModelPar m_emin; //!< Lower energy limit (MeV)
GModelPar m_emax; //!< Upper energy limit (MeV)
// Cached members used for pre-computations
mutable double m_log_emin; //!< Log(emin)
mutable double m_log_emax; //!< Log(emax)
mutable double m_pow_emin; //!< emin^(index+1)
mutable double m_pow_emax; //!< emax^(index+1)
mutable double m_norm; //!< Power-law normalization (for pivot energy 1 MeV)
mutable double m_g_norm; //!< Power-law normalization gradient
mutable double m_power; //!< Power-law factor
mutable double m_last_integral; //!< Last integral flux
mutable double m_last_index; //!< Last spectral index (MeV)
mutable double m_last_emin; //!< Last lower energy limit (MeV)
mutable double m_last_emax; //!< Last upper energy limit (MeV)
mutable GEnergy m_last_energy; //!< Last source energy
mutable double m_last_value; //!< Last function value
mutable double m_last_g_integral; //!< Last integral flux gradient
mutable double m_last_g_index; //!< Last spectral index gradient
This class has an internal cache for precomputation, which is potentially updated when GModelSpectralPlaw2::eval() is called. Here the corresponding code:
double GModelSpectralPlaw2::eval(const GEnergy& srcEng) const
{
// Update precomputed values
update(srcEng);
// Compute function value
double value = integral() * m_norm * m_power;
// Return
return value;
}
As the pre-computation cache is not exposed to the external world but fully handled within the class, eval() is declared const as it does not modify any of the model parameters (which are m_integral, m_index, m_emin, and m_emax). It may however modified some of the cache members, that’s why these members are declared mutable. As there is however no way to access these cache values from the outside (no method exists to access them), the eval() method does not modify any observable property of the class, hence it is declared const.
If possible, method arguments should always be passed by reference. To protect references from changes by the method, arguments passed by reference should always be declared const. Pointers should only be used as arguments if NULL should be a possible argument value. Also pointers should always be declared const. Here an example based on the definition of GObservation:
class GObservation {
public:
void events(const GEvents* events);
void statistics(const std::string& statistics);
protected:
std::string m_statistics; //!< Optimizer statistics (default=poisson)
GEvents* m_events; //!< Pointer to event container
};
The statictics value is passed by reference because the class will hold the actual value, while events is passed as a pointer because the class will hold the pointer.
Numeric argument types should be either int or double. Unless absolutely necessary, avoid short int, long, and float.
If a method returns a class member, the return value should be passed by reference. Unless we explicitly want to modify a class member through the method call, return values passed by reference should be declared const.
If a method returns a base class object, a pointer should be returned. Do never return base class objects by reference, as this will lead to code slicing if the method is used for object assignment. Unless we explicitly want to modify a class member through the method call, the returned pointer should be declared const.
Here an example based on the definition of GObservation:
class GObservation {
public:
virtual double ontime(void) const = 0;
const GEvents* events(void) const;
const std::string& statistics(void) const { return m_statistics; }
protected:
std::string m_statistics; //!< Optimizer statistics (default=poisson)
GEvents* m_events; //!< Pointer to event container
};
The GObservation::ontime() method does return a double by value as the ontime property is not stored explicitly in the class (hence no reference can be returned to it). On the other hand, the statistics method returns by reference as the statistics property is stored as a data member (hence a reference can be returned). Although we could have returned a reference to the event container, this would lead to code slicing. Therefore, the events method returns a pointer. All returned references or pointers are declared const to prevent modification of class members.
Container classes are classes that contain list of elements. Two cases are distinguished here: containers holding objects, and containers holding pointers to objects.
Containers holding objects should have element access operators operator[] implemented that return container elements by reference. A non-const and a const version of the operator should exist. Eventually, at() methods could be added that always perform range checking. Here is a list of mandatory methods for container classes holding objects:
Method | Usage |
---|---|
operator[](const int&) | Element access operator |
const operator[](const int&) const | Element access operator (const version) |
void clear() | Delete all objects in container |
bool is_empty() | Checks whether container is empty |
int size() | Return number of elements in container |
void append(const e&) | Append an element to the container |
void insert(const int&, const e&) | Insert an element into the container |
void extend(const C&) | Append another container to the container |
void remove(const int&) | Removes an element from the container |
void reserve(const int&) | Reserve memory space in a container |
std::string print() | Print container (see Output) |
Containers holding pointers are different from those holding objects in that their operator[] operators return a pointer, and in that they implement a set() method for value setting. Here is a list of mandatory methods for container classes holding pointers:
Method | Usage |
---|---|
e* operator[](const int&) | Element access operator |
const e* operator[](const int&) const | Element access operator (const version) |
void set(const int&, const e&) | Set an element of the container |
void clear() | Delete all objects in container |
bool is_empty() | Checks whether container is empty |
int size() | Return number of elements in container |
void append(const e&) | Append an element to the container |
void insert(const int&, const e&) | Insert an element into the container |
void extend(const C&) | Append another container to the container |
void remove(const int&) | Removes an element from the container |
std::string print() | Print container (see Output) |
Output stream and logging operators should be implemented for every class as friend operators (see Header file structure). The usage of friend operators (instead of member operators) allows for correct handling of code such as
log << std::endl << "This is a text" << std::endl;
To support these friend operators (and to support also the Python interface), each class should have a print() method:
std::string print(const GChatter& chatter = NORMAL) const;
In case that the class derives from one of the standard interface classes GBase, GContainer and GRegistry, the output stream and logging operators are automatically implemented on the level of the base class. In all other cases, the developer needs to implement these operators on the class level. The operators will use the print() method to enable printing in the following form:
std::ostream& operator<< (std::ostream& os, const GFits& fits)
{
os << fits.print();
return os;
}
GLog& operator<< (GLog& log, const GFits& fits)
{
log << fits.print(log.chatter());
return log;
}
Exceptions are largely used in GammaLib to handle the occurrence of unexpected events. GammaLib exceptions are implemented by the GException class. For each new exception type, a new exception subclass is added.
Each exception returns the method name in which the exception occurs and an exception message. The exception message is generally built from values that are passed as arguments to the exception constructor.
Below a list of conventions for implementing and using exceptions:
The Python interface for container classes should implement the following class extensions:
Extension | Method | Usage |
---|---|---|
__str__ | Convert object to string | |
__getitem__ | operator[] | Element access (get) |
__setitem__ | operator[] | Element access (set) |
__len__ | size | Container size |
While __str__ and __len__ are implemented in the GContainer base class, __getitem__ and __setitem__ need to be implemented as class extensions in the SWIG file (see Python interface for C++ classes).