/*************************************************************************** * GNodeArray.cpp - Array of nodes class * * ----------------------------------------------------------------------- * * copyright (C) 2008-2014 by Juergen Knoedlseder * * ----------------------------------------------------------------------- * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ /** * @file GNodeArray.cpp * @brief Node array class implementation * @author Juergen Knoedlseder */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include #include "GException.hpp" #include "GNodeArray.hpp" #include "GTools.hpp" #include "GFits.hpp" #include "GFitsTable.hpp" #include "GFitsBinTable.hpp" #include "GFitsTableCol.hpp" #include "GFitsTableDoubleCol.hpp" /* __ Method name definitions ____________________________________________ */ #define G_AT "GNodeArray::at(int&)" #define G_INSERT "GNodeArray::insert(int&, double&)" #define G_REMOVE "GNodeArray::remove(int&)" #define G_INTERPOLATE "GNodeArray::interpolate(double&,"\ " std::vector&)" #define G_SET_VALUE "GNodeArray::set_value(double&)" /* __ Macros _____________________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /* __ Debug definitions __________________________________________________ */ /* __ Constants __________________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor ***************************************************************************/ GNodeArray::GNodeArray(void) { // Initialise class members for clean destruction init_members(); // Return return; } /***********************************************************************//** * @brief Element constructor * * @param[in] num Number of elements. * @param[in] array Array of elements. * * Constructs a node array from an @p array of double precision values of * length @p num. ***************************************************************************/ GNodeArray::GNodeArray(const int& num, const double* array) { // Initialise class members for clean destruction init_members(); // Use elements to initialise nodes nodes(num, array); // Return return; } /***********************************************************************//** * @brief GVector constructor * * @param[in] vector Vector. * * Constructs a node array from the elements in a @p vector, represented by * a GVector object. ***************************************************************************/ GNodeArray::GNodeArray(const GVector& vector) { // Initialise class members for clean destruction init_members(); // Use vector to initialise nodes nodes(vector); // Return return; } /***********************************************************************//** * @brief Vector constructor * * @param[in] vector Vector. * * Constructs a node array from the elements in a double precision @p vector. ***************************************************************************/ GNodeArray::GNodeArray(const std::vector& vector) { // Initialise class members for clean destruction init_members(); // Use vector to initialise nodes nodes(vector); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] array Node array. ***************************************************************************/ GNodeArray::GNodeArray(const GNodeArray& array) { // Initialise class members for clean destruction init_members(); // Copy members copy_members(array); // Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ GNodeArray::~GNodeArray(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] array Node array. * @return Node array. ***************************************************************************/ GNodeArray& GNodeArray::operator=(const GNodeArray& array) { // Execute only if object is not identical if (this != &array) { // Free members free_members(); // Initialise private members init_members(); // Copy members copy_members(array); } // endif: object was not identical // Return this object return *this; } /*========================================================================== = = = Public methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Clear node array ***************************************************************************/ void GNodeArray::clear(void) { // Free class members free_members(); // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Clone node array * * @return Pointer to deep copy of node array. ***************************************************************************/ GNodeArray* GNodeArray::clone(void) const { return new GNodeArray(*this); } /***********************************************************************//** * @brief Node access operator * * @param[in] index Node index [0,...,size()-1]. * @return Node value. * * @exception GException::out_of_range * Node index is out of range. * * Returns a reference to the node with the specified @p index. The @p index * is checked on its validity. ***************************************************************************/ double& GNodeArray::at(const int& index) { // Compile option: raise an exception if index is out of range #if defined(G_RANGE_CHECK) if (index < 0 || index >= size()) { throw GException::out_of_range(G_AT, "Node index", index, size()); } #endif // Signal that setup needs to be called m_need_setup = false; // Return node return m_node[index]; } /***********************************************************************//** * @brief Node access operator (const version) * * @param[in] index Node index [0,...,size()-1]. * @return Node value. * * @exception GException::out_of_range * Node index is out of range. * * Returns a reference to the node with the specified @p index. The @p index * is checked on its validity. ***************************************************************************/ const double& GNodeArray::at(const int& index) const { // Compile option: raise an exception if index is out of range #if defined(G_RANGE_CHECK) if (index < 0 || index >= size()) { throw GException::out_of_range(G_AT, "Node index", index, size()); } #endif // Signal that setup needs to be called m_need_setup = false; // Return node return m_node[index]; } /***********************************************************************//** * @brief Set node array * * @param[in] num Number of nodes * @param[in] array Node values \f$x_i\f$. * * Setup node array from an array of double precision values. ***************************************************************************/ void GNodeArray::nodes(const int& num, const double* array) { // Free any existing memory free_members(); // Initialise members init_members(); // Set node values for (int i = 0; i < num; ++i) { m_node.push_back(array[i]); } // Setup node distances and linear array handling setup(); // Return return; } /***********************************************************************//** * @brief Append one node to array. * * @param[in] node Node. ***************************************************************************/ void GNodeArray::append(const double& node) { // Add node m_node.push_back(node); // Setup node distances and linear array handling setup(); // Return return; } /***********************************************************************//** * @brief Insert one node into array. * * @param[in] index Node index [0,...,size()-1]. * @param[in] node Node. * * @exception GException::out_of_range * Node index is out of range. * * Inserts a @p node into the node array before the node with the specified * @p index. ***************************************************************************/ void GNodeArray::insert(const int& index, const double& node) { // Compile option: raise exception if index is out of range #if defined(G_RANGE_CHECK) if (is_empty()) { if (index > 0) { throw GException::out_of_range(G_INSERT, "Node index", index, size()); } } else { if (index < 0 || index >= size()) { throw GException::out_of_range(G_INSERT, "Node index", index, size()); } } #endif // Inserts node m_node.insert(m_node.begin()+index, node); // Setup node distances and linear array handling setup(); // Return return; } /***********************************************************************//** * @brief Remove one node into array. * * @param[in] index Node index [0,...,size()-1]. * * @exception GException::out_of_range * Node index is out of range. * * Remove node of specified @p index from node array. ***************************************************************************/ void GNodeArray::remove(const int& index) { // Compile option: raise exception if index is out of range #if defined(G_RANGE_CHECK) if (index < 0 || index >= size()) { throw GException::out_of_range(G_REMOVE, "Node index", index, size()); } #endif // Remove node m_node.erase(m_node.begin() + index); // Setup node distances and linear array handling setup(); // Return return; } /***********************************************************************//** * @brief Append node array * * @param[in] nodes Node array. * * Append a node array to the node array. ***************************************************************************/ void GNodeArray::extend(const GNodeArray& nodes) { // Do nothing if node array is empty if (!nodes.is_empty()) { // Get size. Note that we extract the size first to avoid an // endless loop that arises when a container is appended to // itself. int num = nodes.size(); // Reserve enough space reserve(size() + num); // Append all nodes for (int i = 0; i < num; ++i) { m_node.push_back(nodes[i]); } // Setup node distances and linear array handling setup(); } // endif: node array was not empty // Return return; } /***********************************************************************//** * @brief Set node array from vector * * @param[in] vector Vector from which node array will be built. * * Setup node array from a vector of values. ***************************************************************************/ void GNodeArray::nodes(const GVector& vector) { // Free any existing memory free_members(); // Initialise members init_members(); // Set node values for (int i = 0; i < vector.size(); ++i) { m_node.push_back(vector[i]); } // Setup node distances and linear array handling setup(); // Return return; } /***********************************************************************//** * @brief Set node array from vector * * @param[in] vector Vector from which node array will be built. * * Setup node array from a vector of double precision values. ***************************************************************************/ void GNodeArray::nodes(const std::vector& vector) { // Free any existing memory free_members(); // Initialise members init_members(); // Set node values m_node = vector; // Setup node distances and linear array handling setup(); // Return return; } /***********************************************************************//** * @brief Interpolate value. * * @param[in] value Value \f$x\f$ at which interpolation should be done. * @param[in] vector Vector \f$y_i\f$ that should be interpolated. * * @exception GException::not_enough_nodes * Not enough nodes for interpolation in node array. * @exception GException::vector_mismatch * Size of node vector does not match the size of vector argument. * * This method performs a linear interpolation of values \f$y_i\f$. The * corresponding values \f$x_i\f$ are stored in the node array. ***************************************************************************/ double GNodeArray::interpolate(const double& value, const std::vector& vector) const { // Throw exception if there are not enough nodes if (m_node.size() < 2) { throw GException::not_enough_nodes(G_INTERPOLATE, m_node.size()); } // Throw exception if vectors have not the same size if (m_node.size() != vector.size()) { throw GException::vector_mismatch(G_INTERPOLATE, m_node.size(), vector.size()); } // Set interpolation value (circumvent const correctness) set_value(value); // Interpolate double y = vector[inx_left()] * wgt_left() + vector[inx_right()] * wgt_right(); // Return return y; } /***********************************************************************//** * @brief Set indices and weighting factors for interpolation * * @param[in] value Value for which the interpolation should be done. * * @exception GException::invalid_value * No nodes are available for interpolation. * * Set the indices that bound the specified value and the corresponding * weighting factors for linear interpolation. If the array has a linear * form (i.e. the nodes are equidistant), an analytic formula is used to * determine the boundary indices. If the nodes are not equidistant the * boundary indices are searched by bisection. If there is only a single * node, no interpolation is done and the index of this node is returned. * * Note that this method needs to be called after changing the node array * to setup the corrected interpolation indices and weights. ***************************************************************************/ void GNodeArray::set_value(const double& value) const { // Get number of nodes int nodes = m_node.size(); /* // Throw an exception if less than 2 nodes are available if (nodes < 2) { throw GException::not_enough_nodes(G_SET_VALUE, nodes); } */ // Throw an exception if there are no nodes if (nodes < 1) { std::string msg = "Attempting to set interpolating value without " "having any nodes. Interpolation can only be " "done if nodes are available."; throw GException::invalid_value(G_SET_VALUE, msg); } // Initialize computation flag bool compute = true; // Update cache if required. If cache was updated, computation is // enforced. Otherwise we check if the value has changed. The value // check is only done when a last value has been recorded. if (m_need_setup) { setup(); } else { if (m_has_last_value && value == m_last_value) { compute = false; } } // Continue only if computation is required if (compute) { // Handle special case of a single node if (nodes == 1) { m_inx_left = 0; m_inx_right = 0; m_wgt_left = 1.0; m_wgt_right = 0.0; } // Handle all other cases else { // If array is linear then get left index from analytic formula if (m_is_linear) { // Set left index m_inx_left = int(m_linear_slope * value + m_linear_offset); // Keep index in valid range if (m_inx_left < 0) { m_inx_left = 0; } else if (m_inx_left >= nodes-1) { m_inx_left = nodes - 2; } } // endif: array is linear // ... otherwise search the relevant indices by bisection else { // Set left index if value is before first node if (value < m_node[0]) { m_inx_left = 0; } // Set left index if value is after last node else if (value > m_node[nodes-1]) { m_inx_left = nodes - 2; } // Set left index by bisection else { int low = 0; int high = nodes - 1; while ((high - low) > 1) { int mid = (low+high) / 2; if (m_node[mid] > value) { high = mid; } else { low = mid; } } m_inx_left = low; } // endelse: did bisection } // Set right index m_inx_right = m_inx_left + 1; // Set weighting factors m_wgt_right = (value - m_node[m_inx_left]) / m_step[m_inx_left]; m_wgt_left = 1.0 - m_wgt_right; } // endelse: more than one node was present } // endif: computation was required // Store last value and signal availability m_last_value = value; m_has_last_value = true; // Return return; } /***********************************************************************//** * @brief Load nodes from FITS file * * @param[in] filename FITS filename. * @param[in] extname Energies extension name (defaults to "NODES") * * Loads the nodes from FITS file. ***************************************************************************/ void GNodeArray::load(const std::string& filename, const std::string& extname) { // Allocate FITS file GFits file; // Open FITS file file.open(filename); // Get nodes table const GFitsTable& table = *file.table(extname); // Read nodes from table read(table); // Close FITS file file.close(); // Return return; } /***********************************************************************//** * @brief Save nodes to FITS file * * @param[in] filename FITS filename. * @param[in] clobber Overwrite any existing nodes extension? * @param[in] extname Nodes extension name (defaults to "NODES") * * Saves nodes into extension @p extname of a FITS file. If the file does * not exist it is created. If the file exists the nodes are appended as * extension. If another nodes extension exists already it is overwritten * if @p clobber=true. ***************************************************************************/ void GNodeArray::save(const std::string& filename, const bool& clobber, const std::string& extname) const { // Allocate FITS file GFits file; // Write nodes to FITS file write(file, extname); // Save to file file.saveto(filename, clobber); // Return return; } /***********************************************************************//** * @brief Read nodes from FITS table * * @param[in] table FITS table. * * Reads the nodes from a FITS table. ***************************************************************************/ void GNodeArray::read(const GFitsTable& table) { // Clear nodes clear(); // Extract number of nodes in FITS file int num = table.nrows(); // Continue only if there are nodes bins if (num > 0) { // Get the column with the name "Value" const GFitsTableCol* col_nodes = table["Value"]; // Set nodes for (int i = 0; i < num; ++i) { append(col_nodes->real(i)); } // Setup node distances and linear array handling setup(); } // endif: there were nodes // Return return; } /***********************************************************************//** * @brief Write nodes into FITS object * * @param[in] file FITS file. * @param[in] extname Nodes extension name (defaults to "NODES") * * Writes nodes into FITS object. ***************************************************************************/ void GNodeArray::write(GFits& file, const std::string& extname) const { // Set number of nodes int num = m_node.size(); // Create node column GFitsTableDoubleCol col_nodes = GFitsTableDoubleCol("Value", num); // Fill node column for (int i = 0; i < num; ++i) { col_nodes(i) = m_node[i]; } // Create nodes table GFitsBinTable* table = new GFitsBinTable(num); table->append(col_nodes); table->extname(extname); // Write to FITS file file.append(*table); // Free table delete table; // Return return; } /***********************************************************************//** * @brief Print nodes * * @param[in] chatter Chattiness (defaults to NORMAL). * @return String containing nodes information. ***************************************************************************/ std::string GNodeArray::print(const GChatter& chatter) const { // Initialise result string std::string result; // Continue only if chatter is not silent if (chatter != SILENT) { // Append header result.append("=== GNodeArray ==="); // Append array type result.append("\n"+gammalib::parformat("Number of nodes in array")); result.append(gammalib::str(size())); if (m_is_linear) { result.append("\n"+gammalib::parformat("Array type")+"linear"); result.append("\n"+gammalib::parformat("Linear slope")); result.append(gammalib::str(m_linear_slope)); result.append("\n"+gammalib::parformat("Linear offset")); result.append(gammalib::str(m_linear_offset)); } else { result.append("\n"+gammalib::parformat("Array type")+"nonlinear"); } // EXPLICIT: Append indices and weights if (chatter >= EXPLICIT) { result.append("\n"+gammalib::parformat("Indices and weights")); result.append("("+gammalib::str(m_inx_left)+","); result.append(gammalib::str(m_inx_right)+")="); result.append("("+gammalib::str(m_wgt_left)+","); result.append(gammalib::str(m_wgt_right)+")"); } // VERBOSE: Append nodes if (chatter >= VERBOSE) { for (int i = 0; i < size(); ++i) { result.append("\n"+gammalib::parformat("Node "+gammalib::str(i))); result.append(gammalib::str(m_node[i])); if (i < m_step.size()) { result.append(" (delta="+gammalib::str(m_step[i])+")"); } } } } // endif: chatter was not silent // Return result return result; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void GNodeArray::init_members(void) { // Initialise members m_node.clear(); m_step.clear(); m_is_linear = false; m_has_last_value = false; m_last_value = 0.0; m_linear_slope = 0.0; m_linear_offset = 0.0; m_inx_left = 0; m_inx_right = 0; m_wgt_left = 0.0; m_wgt_right = 0.0; m_need_setup = false; // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] array Node array. ***************************************************************************/ void GNodeArray::copy_members(const GNodeArray& array) { // Copy number of bins m_node = array.m_node; m_step = array.m_step; m_is_linear = array.m_is_linear; m_has_last_value = array.m_has_last_value; m_last_value = array.m_last_value; m_linear_slope = array.m_linear_slope; m_linear_offset = array.m_linear_offset; m_inx_left = array.m_inx_left; m_inx_right = array.m_inx_right; m_wgt_left = array.m_wgt_left; m_wgt_right = array.m_wgt_right; m_need_setup = array.m_need_setup; // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void GNodeArray::free_members(void) { // Return return; } /***********************************************************************//** * @brief Compute distance array and linear slope/offset * * Precomputes values for fast interpolation. The precomputation requires * at least 2 nodes to be present in the node array. If less than two * nodes are present, the distance vector m_step will be empty and no * computation is done. ***************************************************************************/ void GNodeArray::setup(void) const { // Reset distance vector m_step.clear(); // Get number of nodes int nodes = m_node.size(); // Continue only if there are at least 2 nodes if (nodes > 1) { // Setup distance array between subsequent nodes for (int i = 0; i < nodes-1; ++i) { m_step.push_back(m_node[i+1] - m_node[i]); } // Evaluate linear slope and offset m_linear_slope = double(nodes-1) / (m_node[nodes-1] - m_node[0]); m_linear_offset = -m_linear_slope * m_node[0]; // Check if nodes form a linear array m_is_linear = true; for (int i = 0; i < nodes-1; ++i) { double eps = m_linear_slope * m_node[i] + m_linear_offset - double(i); if (std::abs(eps) > 1.0e-6) { m_is_linear = false; break; } } } // endif: there were at least two nodes // Signal that setup has been called m_need_setup = false; // Return return; }