/*************************************************************************** * GMatrixSparse.cpp - Sparse matrix class * * ----------------------------------------------------------------------- * * copyright (C) 2006-2017 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 GMatrixSparse.cpp * @brief Sparse matrix class implementation * @author Juergen Knoedlseder */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include #include "GException.hpp" #include "GTools.hpp" #include "GVector.hpp" #include "GMatrix.hpp" #include "GMatrixSparse.hpp" #include "GMatrixSymmetric.hpp" #include "GSparseSymbolic.hpp" #include "GSparseNumeric.hpp" /* __ Method name definitions ____________________________________________ */ #define G_CONSTRUCTOR "GMatrixSparse::GMatrixSparse(int&, int&, int&)" #define G_OP_MUL_VEC "GMatrixSparse::operator*(GVector&)" #define G_OP_ADD "GMatrixSparse::operator+=(GMatrixSparse&)" #define G_OP_SUB "GMatrixSparse::operator-=(GMatrixSparse&)" #define G_OP_MAT_MUL "GMatrixSparse::operator*=(GMatrixSparse&)" #define G_AT "GMatrixSparse::at(int&, int&)" #define G_EXTRACT_ROW "GMatrixSymmetric::row(int&)" #define G_SET_ROW "GMatrixSymmetric::row(int&, GVector&)" #define G_EXTRACT_COLUMN "GMatrixSymmetric::column(int&)" #define G_SET_COLUMN "GMatrixSymmetric::column(int&, GVector&)" #define G_SET_COLUMN2 "GMatrixSymmetric::column(int&, double*, int*, int)" #define G_ADD_TO_ROW "GMatrixSymmetric::add_to_row(int&, GVector&)" #define G_ADD_TO_COLUMN "GMatrixSymmetric::add_to_column(int&, GVector&)" #define G_ADD_TO_COLUMN2 "GMatrixSymmetric::add_to_column(int&, double*,"\ " int*, int)" #define G_CHOL_DECOMP "GMatrixSparse::cholesky_decompose(bool)" #define G_CHOL_SOLVE "GMatrixSparse::cholesky_solver(GVector&, bool)" #define G_STACK_INIT "GMatrixSparse::stack_init(int&, int&)" #define G_STACK_PUSH "GMatrixSparse::stack_push_column(double*, int*, int&,"\ " int&)" #define G_STACK_FLUSH "GMatrixSparse::stack_flush(void)" #define G_COPY_MEMBERS "GMatrixSparse::copy_members(GMatrixSparse&)" #define G_ALLOC_MEMBERS "GMatrixSparse::alloc_members(int&, int&, int&)" #define G_ALLOC "GMatrixSparse::alloc_elements(int&, int&)" #define G_FREE "GMatrixSparse::free_elements(int&, int&)" #define G_REMOVE_ZERO "GMatrixSparse::remove_zero_row_col(void)" #define G_SYMPERM "cs_symperm(GMatrixSparse*, int*, int&)" #define G_TRANSPOSE "cs_transpose(GMatrixSparse*, int)" /* __ Macros _____________________________________________________________ */ #define G_MIN(a,b) (((a) < (b)) ? (a) : (b)) #define G_MAX(a,b) (((a) > (b)) ? (a) : (b)) /* __ Coding definitions _________________________________________________ */ /* __ Debug definitions __________________________________________________ */ //#define G_DEBUG_SPARSE_PENDING // Analyse pending values //#define G_DEBUG_SPARSE_INSERTION // Analyse value insertion //#define G_DEBUG_SPARSE_ADDITION // Analyse value addition //#define G_DEBUG_SPARSE_COMPRESSION // Analyse zero row/column compression //#define G_DEBUG_SPARSE_MALLOC // Analyse memory management //#define G_DEBUG_SPARSE_STACK_PUSH // Analyse stack column pushing //#define G_DEBUG_SPARSE_STACK_FLUSH // Analyse stack column flushing /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void matrix constructor * * Constructs empty sparse matrix. The number of rows and columns of the * matrix will be zero. * * @todo Verify that the class is save against empty matrix objects. ***************************************************************************/ GMatrixSparse::GMatrixSparse(void) : GMatrixBase() { // Initialise class members init_members(); // Return return; } /***********************************************************************//** * @brief Matrix constructor * * @param[in] rows Number of rows [>0]. * @param[in] columns Number of columns [>0]. * @param[in] elements Number of allocated elements (defaults to 0). * * @exception GException::empty * Specified number of rows or columns is not valid. * * Constructs sparse matrix of dimension @p rows times @p columns. The * optional @p elements argument specifies the size of the physical * memory that should be allocated. By default, no memory will be allocated * and memory allocation will be performed on-the-fly. If the amount of * required memory is larger than the size specified by @p elements, * additional momeory will be allocated automatically on-the-fly. * * @todo Is there a real need to throw an empty exception? The class should * be able to operate on empty matrices. ***************************************************************************/ GMatrixSparse::GMatrixSparse(const int& rows, const int& columns, const int& elements) : GMatrixBase() { // Continue only if matrix is valid if (rows > 0 && columns > 0) { // Initialise private members for clean destruction init_members(); // Allocate matrix memory alloc_members(rows, columns, elements); } else { throw GException::empty(G_CONSTRUCTOR); } // Return return; } /***********************************************************************//** * @brief GMatrix to GMatrixSparse storage class convertor * * @param[in] matrix Generic matrix (GMatrix). * * Constructs a sparse matrix by using the number of rows and columns of * a generic matrix and by assigning the elements of the generic matrix * to the sparse matrix. ***************************************************************************/ GMatrixSparse::GMatrixSparse(const GMatrix& matrix) : GMatrixBase() { // Initialise class members for clean destruction init_members(); // Construct matrix alloc_members(matrix.rows(), matrix.columns()); // Fill matrix column by column for (int col = 0; col < m_cols; ++col) { GVector vector = matrix.column(col); this->column(col, vector); } // Return return; } /***********************************************************************//** * @brief GMatrixSymmetric to GMatrixSparse storage class convertor * * @param[in] matrix Symmetric matrix (GMatrixSymmetric). * * Constructs a sparse matrix by using the number of rows and columns of * a symmetric matrix and by assigning the elements of the symmetric matrix * to the sparse matrix. ***************************************************************************/ GMatrixSparse::GMatrixSparse(const GMatrixSymmetric& matrix) : GMatrixBase() { // Initialise class members for clean destruction init_members(); // Allocate matrix memory alloc_members(matrix.rows(), matrix.columns()); // Fill matrix column by column for (int col = 0; col < m_cols; ++col) { GVector vector = matrix.column(col); this->column(col, vector); } // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] matrix Matrix. * * Constructs matrix by copying an existing matrix. ***************************************************************************/ GMatrixSparse::GMatrixSparse(const GMatrixSparse& matrix) : GMatrixBase(matrix) { // Initialise private members for clean destruction init_members(); // Copy members. Note that the copy operation does not fill the // pending element. copy_members(matrix); // Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ GMatrixSparse::~GMatrixSparse(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Matrix assignment operator * * @param[in] matrix Matrix. * @return Matrix. * * Assigns the content of another matrix to the actual matrix instance. ***************************************************************************/ GMatrixSparse& GMatrixSparse::operator=(const GMatrixSparse& matrix) { // Execute only if object is not identical if (this != &matrix) { // Assign base class members. Note that this method will also perform // the allocation of the matrix memory and the copy of the matrix // attributes. this->GMatrixBase::operator=(matrix); // Free derived class members free_members(); // Initialise derived class members init_members(); // Copy derived class members copy_members(matrix); } // endif: object was not identical // Return return *this; } /***********************************************************************//** * @brief Value assignment operator * * @param[in] value Value. * @return Matrix. * * Assigns the specified @p value to all elements of the matrix. ***************************************************************************/ GMatrixSparse& GMatrixSparse::operator=(const double& value) { // Fill any pending element to have a non-pending state fill_pending(); // If value is 0 then simply reinitialize column start indices if (value == 0) { // Initialise column start indices to 0 for (int col = 0; col <= m_cols; ++col) { m_colstart[col] = 0; } } // ... otherwise fill column-wise else { // Set column vector GVector column(m_rows); column = value; // Column-wise setting for (int col = 0; col < m_cols; ++col) { this->column(col, column); } } // Return this object return *this; } /***********************************************************************//** * @brief Return reference to matrix element * * @param[in] row Matrix row [0,...,rows()-1]. * @param[in] column Matrix column [0,...,columns()-1]. * @return Reference to matrix element. * * Returns the reference to the matrix element at @p row and @p column. If * the matrix element does not yet exist, a reference to the pending element * with a value of 0.0 is returned. ***************************************************************************/ double& GMatrixSparse::operator()(const int& row, const int& column) { // Fill pending element. This will set the value of the pending element // to 0.0. fill_pending(); // Get element int inx = get_index(row, column); double* value; if (inx < 0) { value = &m_fill_val; m_fill_row = row; m_fill_col = column; } else { value = &(m_data[inx]); } // Return element return *value; } /***********************************************************************//** * @brief Return reference to matrix element (const version) * * @param[in] row Matrix row [0,...,rows()-1]. * @param[in] column Matrix column [0,...,columns()-1]. * @return Const reference to matrix element. * * Returns a const reference to the matrix element at @p row and @p column. * If the matrix element does not yet exist, a reference to the zero element * is returned. If the matrix element corresponds to the pending element, * a reference to the pending element is returned. Otherwise, a reference * to the matrix elements is returned. ***************************************************************************/ const double& GMatrixSparse::operator()(const int& row, const int& column) const { // Get element. We need here the zero element to return also a pointer // for 0.0 entry that is not stored. Since we have the const version we // don't have to care about modification of this zero value. int inx = get_index(row, column); double* value; if (inx < 0) { value = (double*)&m_zero; } else if (inx == m_elements) { value = (double*)&m_fill_val; } else { value = (double*)&(m_data[inx]); } // Return element return *value; } /***********************************************************************//** * @brief Vector multiplication * * @param[in] vector Vector. * * @exception GException::matrix_vector_mismatch * Vector length differs from number of columns in matrix. * * This method performs a vector multiplication of a matrix. The vector * multiplication will produce a vector. The matrix multiplication can only * be performed when the number of matrix columns is equal to the length of * the vector. * * The method includes any pending fill. ***************************************************************************/ GVector GMatrixSparse::operator*(const GVector& vector) const { // Raise an exception if the matrix and vector dimensions are not compatible if (m_cols != vector.size()) { throw GException::matrix_vector_mismatch(G_OP_MUL_VEC, vector.size(), m_rows, m_cols); } // Initialise result vector GVector result(m_rows); // Multiply only if there are elements in matrix if (m_elements > 0) { // Perform vector multiplication for (int col = 0; col < m_cols; ++col) { int i_start = m_colstart[col]; int i_stop = m_colstart[col+1]; double* ptr_data = m_data + i_start; int* ptr_rowinx = m_rowinx + i_start; for (int i = i_start; i < i_stop; ++i) { result[*ptr_rowinx++] += *ptr_data++ * vector[col]; } } // If fill is pending then add-in also this element into the product if (m_fill_val != 0.0) { result[m_fill_row] += m_fill_val * vector[m_fill_col]; #if defined(G_DEBUG_SPARSE_PENDING) std::cout << G_OP_MUL_VEC << ": pending value " << m_fill_val << " for location (" << m_fill_row << "," << m_fill_col << ") has been used." << std::endl; #endif } } // endif: there were elements in matrix // Return result return result; } /***********************************************************************//** * @brief Negate matrix elements * * @return Matrix with negated elements. ***************************************************************************/ GMatrixSparse GMatrixSparse::operator-(void) const { // Copy matrix GMatrixSparse matrix = *this; // Fill pending element matrix.fill_pending(); // Negate all matrix elements double* ptr = matrix.m_data; for (int i = 0; i < matrix.m_elements; ++i, ++ptr) { *ptr = -(*ptr); } // Return matrix return matrix; } /***********************************************************************//** * @brief Equalty operator * * @param[in] matrix Matrix. * * This operator checks if two matrices are identical. Two matrices are * considered identical if they have the same dimensions and identicial * elements. * * @todo Implement native sparse code ***************************************************************************/ bool GMatrixSparse::operator==(const GMatrixSparse &matrix) const { // Initalise the result to 'equal matrices' bool result = true; // Perform comparison (only if matrix dimensions are identical) if (m_rows == matrix.m_rows && m_cols == matrix.m_cols) { // Loop over all matrix elements for (int row = 0; row < m_rows; ++row) { for (int col = 0; col < m_cols; ++col) { if ((*this)(row,col) != matrix(row,col)) { result = false; break; } } if (!result) { break; } } } else { result = false; } // Return result return result; } /***********************************************************************//** * @brief Non-equality operator * * @param[in] matrix Matrix. * * This operator checks if two matrices are not identical. Two matrices are * considered not identical if they differ in their dimensions or if at * least one element differs. ***************************************************************************/ bool GMatrixSparse::operator!=(const GMatrixSparse &matrix) const { // Get negated result of equality operation bool result = !(this->operator==(matrix)); // Return result return result; } /***********************************************************************//** * @brief Unary matrix addition operator * * @param[in] matrix Matrix. * * @exception GException::matrix_mismatch * Incompatible matrix size. * * This method performs a matrix addition. The operation can only succeed * when the dimensions of both matrices are identical. * * @todo Implement native sparse code ***************************************************************************/ GMatrixSparse& GMatrixSparse::operator+=(const GMatrixSparse& matrix) { // Raise an exception if the matrix dimensions are not compatible if (m_rows != matrix.m_rows || m_cols != matrix.m_cols) { throw GException::matrix_mismatch(G_OP_ADD, m_rows, m_cols, matrix.m_rows, matrix.m_cols); } // Perform inplace matrix addition using vectors for (int col = 0; col < m_cols; ++col) { GVector v_result = column(col); GVector v_operand = matrix.column(col); v_result += v_operand; column(col, v_result); } // Return result return *this; } /***********************************************************************//** * @brief Unary matrix scalar addition operator * * @param[in] scalar Scalar. * * Adds a @p scalar to each matrix element. ***************************************************************************/ GMatrixSparse& GMatrixSparse::operator+=(const double& scalar) { // Perform inplace matrix addition using vectors for (int col = 0; col < m_cols; ++col) { GVector vector = column(col); vector += scalar; column(col, vector); } // Return result return *this; } /***********************************************************************//** * @brief Unary matrix subtraction operator * * @param[in] matrix Matrix. * * @exception GException::matrix_mismatch * Incompatible matrix size. * * This method performs a matrix addition. The operation can only succeed * when the dimensions of both matrices are identical. * * @todo Implement native sparse code ***************************************************************************/ GMatrixSparse& GMatrixSparse::operator-=(const GMatrixSparse& matrix) { // Raise an exception if the matrix dimensions are not compatible if (m_rows != matrix.m_rows || m_cols != matrix.m_cols) { throw GException::matrix_mismatch(G_OP_SUB, m_rows, m_cols, matrix.m_rows, matrix.m_cols); } // Perform inplace matrix subtraction for (int col = 0; col < m_cols; ++col) { GVector v_result = column(col); GVector v_operand = matrix.column(col); v_result -= v_operand; column(col, v_result); } // Return result return *this; } /***********************************************************************//** * @brief Unary matrix scalar subtraction operator * * @param[in] scalar Scalar. * * Subtracts a @p scalar from each matrix element. ***************************************************************************/ GMatrixSparse& GMatrixSparse::operator-=(const double& scalar) { // Perform inplace matrix subtraction for (int col = 0; col < m_cols; ++col) { GVector vector = column(col); vector -= scalar; column(col, vector); } // Return result return *this; } /***********************************************************************//** * @brief Unary matrix multiplication operator * * @param[in] matrix Matrix. * * @exception GException::matrix_mismatch * Incompatible matrix size. * * This method performs a matrix multiplication. The operation can only * succeed when the dimensions of both matrices are compatible. * * @todo Implement native sparse code ***************************************************************************/ GMatrixSparse& GMatrixSparse::operator*=(const GMatrixSparse& matrix) { // Raise an exception if the matrix dimensions are not compatible if (m_cols != matrix.m_rows) { throw GException::matrix_mismatch(G_OP_MAT_MUL, m_rows, m_cols, matrix.m_rows, matrix.m_cols); } // Allocate result matrix GMatrixSparse result(m_rows, matrix.m_cols); // Multiply only if there are elements in both matrices if (m_elements > 0 && matrix.m_elements > 0) { // Loop over all elements of result matrix for (int row = 0; row < m_rows; ++row) { for (int col = 0; col < matrix.m_cols; ++col) { double sum = 0.0; for (int i = 0; i < m_cols; ++i) { sum += (*this)(row,i) * matrix(i,col); } result(row,col) = sum; } } } // Assign result *this = result; // Return result return *this; } /*========================================================================== = = = Public methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Clear matrix ***************************************************************************/ void GMatrixSparse::clear(void) { // Free members free_members(); // Initialise private members init_members(); // Return return; } /***********************************************************************//** * @brief Clone matrix * * @return Pointer to deep copy of matrix. ***************************************************************************/ GMatrixSparse* GMatrixSparse::clone(void) const { // Clone matrix return new GMatrixSparse(*this); } /***********************************************************************//** * @brief Return reference to matrix element * * @param[in] row Matrix row [0,...,rows()-1]. * @param[in] column Matrix column [0,...,columns()-1]. * @return Reference to matrix element. * * @exception GException::out_of_range * Row or column index out of range. ***************************************************************************/ double& GMatrixSparse::at(const int& row, const int& column) { // Raise exception if row or column index is out of range if (row < 0 || row >= m_rows || column < 0 || column >= m_cols) { throw GException::out_of_range(G_AT, row, column, m_rows, m_cols); } // Return element return ((*this)(row, column)); } /***********************************************************************//** * @brief Return reference to matrix element (const version) * * @param[in] row Matrix row [0,...,rows()-1]. * @param[in] column Matrix column [0,...,columns()-1]. * @return Reference to matrix element. * * @exception GException::out_of_range * Row or column index out of range. ***************************************************************************/ const double& GMatrixSparse::at(const int& row, const int& column) const { // Raise exception if row or column index is out of range if (row < 0 || row >= m_rows || column < 0 || column >= m_cols) { throw GException::out_of_range(G_AT, row, column, m_rows, m_cols); } // Return element return ((*this)(row, column)); } /***********************************************************************//** * @brief Extract row as vector from matrix * * @param[in] row Row to be extracted (starting from 0). * * @exception GException::out_of_range * Invalid row index specified. * * This method extracts a matrix row into a vector. ***************************************************************************/ GVector GMatrixSparse::row(const int& row) const { // Raise an exception if the row index is invalid #if defined(G_RANGE_CHECK) if (row < 0 || row >= m_rows) { throw GException::out_of_range(G_EXTRACT_ROW, row, 0, m_rows-1); } #endif // Create result vector GVector result(m_cols); // Loop over all columns to extract data for (int col = 0; col < m_cols; ++col) { // Get the start and stop of the elements int i_start = m_colstart[col]; int i_stop = m_colstart[col+1]; // Search requested row in elements int i; for (i = i_start; i < i_stop; ++i) { if (m_rowinx[i] == row) { break; } } // Copy element if we found one if (i < i_stop) { result[col] = m_data[i]; } } // endfor: looped over all columns // If there is a pending element then put it in the vector if (m_fill_val != 0.0 && m_fill_row == row) { result[m_fill_col] = m_fill_val; } // Return vector return result; } /***********************************************************************//** * @brief Set row in matrix * * @todo To be implemented. ***************************************************************************/ void GMatrixSparse::row(const int& row, const GVector& vector) { // Raise an exception if the row index is invalid #if defined(G_RANGE_CHECK) if (row < 0 || row >= m_rows) { throw GException::out_of_range(G_SET_ROW, row, 0, m_rows-1); } #endif // Return return; } /***********************************************************************//** * @brief Extract column as vector from matrix * * @param[in] column Column index [0,...,columns()-1]. * * @exception GException::out_of_range * Invalid row index specified. * * This method extracts a matrix column into a vector. ***************************************************************************/ GVector GMatrixSparse::column(const int& column) const { // Raise an exception if the column index is invalid #if defined(G_RANGE_CHECK) if (column < 0 || column >= m_cols) { throw GException::out_of_range(G_EXTRACT_COLUMN, column, 0, m_cols-1); } #endif // Create result vector GVector result(m_rows); // Get the start and stop of the elements int i_start = m_colstart[column]; int i_stop = m_colstart[column+1]; // Extract elements into vector for (int i = i_start; i < i_stop; ++i) { result[m_rowinx[i]] = m_data[i]; } // If there is a pending element then put it in the vector if (m_fill_val != 0.0 && m_fill_col == column) { result[m_fill_row] = m_fill_val; } // Return vector return result; } /***********************************************************************//** * @brief Insert vector column into matrix * * @param[in] column Column index [0,...,columns()-1]. * @param[in] vector Vector. * * @exception GException::out_of_range * Invalid column index specified. * @exception GException::matrix_vector_mismatch * Matrix dimension mismatches the vector size. * * Inserts the content of a vector into a matrix column. Any previous * content in the matrix column will be overwritted. * * This is the main driver routine to insert data into a matrix. Note that * there is another instance of this function that takes a compressed array. ***************************************************************************/ void GMatrixSparse::column(const int& column, const GVector& vector) { // Debug header #if defined(G_DEBUG_SPARSE_INSERTION) std::cout << "GMatrixSparse::column("; std::cout << column << ", [" << v << "]):" << std::endl; std::cout << " In Data : "; for (int i = 0; i < m_elements; ++i) { std::cout << m_data[i] << " "; } std::cout << std::endl << " In Row .: "; for (int i = 0; i < m_elements; ++i) { std::cout << m_rowinx[i] << " "; } std::cout << std::endl << " In Col .: "; for (int i = 0; i < m_cols+1; ++i) { std::cout << m_colstart[i] << " "; } std::cout << std::endl; #endif // Raise an exception if the column index is invalid #if defined(G_RANGE_CHECK) if (column < 0 || column >= m_cols) { throw GException::out_of_range(G_SET_COLUMN, column, 0, m_cols-1); } #endif // Raise an exception if the matrix and vector dimensions are not // compatible if (m_rows != vector.size()) { throw GException::matrix_vector_mismatch(G_SET_COLUMN, vector.size(), m_rows, m_cols); } // If there is a pending element for this column then delete it since // the vector overwrites this element if (m_fill_val != 0.0 && m_fill_col == column) { #if defined(G_DEBUG_SPARSE_PENDING) std::cout << G_SET_COLUMN << ": pending value " << m_fill_val << " for location (" << m_fill_row << "," << m_fill_col << ") became obsolete" << std::endl; #endif m_fill_val = 0.0; m_fill_row = 0; m_fill_col = 0; } // Determine the number of non-zero elements in the vector int n_vector = 0; for (int row = 0; row < m_rows; ++row) { if (vector[row] != 0.0) { n_vector++; } } // Get the start and stop indices of the actual column and compute // the number of exisiting elements in the column int i_start = m_colstart[column]; int i_stop = m_colstart[column+1]; int n_exist = i_stop - i_start; // Compute the size difference for the new matrix. It is positive if // the number of non-zero entries in the vector is larger than the // number of non-zero entries in the matrix (in this case we have to // increase the matrix size). int n_diff = n_vector - n_exist; // If we need space then allocate it, if we have to much space then free it if (n_diff > 0) { alloc_elements(i_start, n_diff); #if defined(G_DEBUG_SPARSE_INSERTION) std::cout << " Insert .: " << n_diff << " elements at index " << i_start << std::endl; #endif } else if (n_diff < 0) { free_elements(i_start, -n_diff); #if defined(G_DEBUG_SPARSE_INSERTION) std::cout << " Remove .: " << -n_diff << " elements at index " << i_start << std::endl; #endif } // Insert the vector elements in the matrix if (n_vector > 0) { for (int row = 0, i = i_start; row < m_rows; ++row) { if (vector[row] != 0.0) { m_data[i] = vector[row]; m_rowinx[i] = row; i++; } } } // Update column start indices for (int i = column+1; i <= m_cols; ++i) { m_colstart[i] += n_diff; } // Debugging: show sparse matrix after insertion #if defined(G_DEBUG_SPARSE_INSERTION) std::cout << " Out Data: "; for (int i = 0; i < m_elements; ++i) { std::cout << m_data[i] << " "; } std::cout << std::endl << " Out Row : "; for (int i = 0; i < m_elements; ++i) { std::cout << m_rowinx[i] << " "; } std::cout << endl << " Out Col : "; for (int i = 0; i < m_cols+1; ++i) { std::cout << m_colstart[i] << " "; } std::cout << std::endl; #endif // Return return; } /***********************************************************************//** * @brief Insert compressed array into matrix column * * @param[in] column Column index [0,...,columns()-1]. * @param[in] values Compressed array. * @param[in] rows Row indices of array. * @param[in] number Number of elements in array. * * @exception GException::out_of_range * Invalid column index specified. * @exception GException::matrix_vector_mismatch * Matrix dimension mismatches the vector size. * * Inserts the content of a copressed array into a matrix column. Any * previous content in the matrix column will be overwritted. * * This is the main driver routine to insert data into a matrix. Note that * there is another instance of this function that takes a vector. ***************************************************************************/ void GMatrixSparse::column(const int& column, const double* values, const int* rows, int number) { // Debug header #if defined(G_DEBUG_SPARSE_INSERTION) std::cout << "GMatrixSparse::column("; std::cout << column << ", values, rows, " << number << "):" << std::endl; std::cout << " Matrix Data : "; for (int i = 0; i < m_elements; ++i) { std::cout << m_data[i] << " "; } std::cout << std::endl << " Matrix Row .: "; for (int i = 0; i < m_elements; ++i) { std::cout << m_rowinx[i] << " "; } std::cout << std::endl << " Matrix Col .: "; for (int i = 0; i < m_cols+1; ++i) { std::cout << m_colstart[i] << " "; } std::cout << std::endl; std::cout << " Array Data .: "; for (int i = 0; i < number; ++i) { std::cout << values[i] << " "; } std::cout << std::endl << " Array Row ..: "; for (int i = 0; i < number; ++i) { std::cout << rows[i] << " "; } std::cout << std::endl; #endif // Raise an exception if the column index is invalid #if defined(G_RANGE_CHECK) if (column < 0 || column >= m_cols) { throw GException::out_of_range(G_SET_COLUMN2, column, 0, m_cols-1); } #endif // Raise an exception if the index array seems incompatible with matrix // dimensions if (rows[number-1] >= m_rows) { throw GException::matrix_vector_mismatch(G_SET_COLUMN2, rows[number-1], m_rows, m_cols); } // If there is a pending element for this column then delete it since // the vector overwrites this element if (m_fill_val != 0.0 && m_fill_col == column) { #if defined(G_DEBUG_SPARSE_PENDING) std::cout << G_INSERT_COL2 << ": pending value " << m_fill_val << " for location (" << m_fill_row << "," << m_fill_col << ") became obsolete" << std::endl; #endif m_fill_val = 0.0; m_fill_row = 0; m_fill_col = 0; } // Get the start and stop indices of the actual column and compute // the number of exisiting elements in the column int i_start = m_colstart[column]; int i_stop = m_colstart[column+1]; int n_exist = i_stop - i_start; // If the array is empty then make sure that the number of elements is 0 // (we then just delete the existing column) if (!values || !rows) { number = 0; } // Compute the size difference for the new matrix. It is positive if // the number of non-zero entries in the array is larger than the // number of non-zero entries in the matrix (in this case we have to // increase the matrix size). int n_diff = number - n_exist; // If we need space then allocate it, if we have to much space then free it if (n_diff > 0) { alloc_elements(i_start, n_diff); #if defined(G_DEBUG_SPARSE_INSERTION) std::cout << " Insert .: " << n_diff << " elements at index " << i_start << std::endl; #endif } else if (n_diff < 0) { free_elements(i_start, -n_diff); #if defined(G_DEBUG_SPARSE_INSERTION) std::cout << " Remove .: " << -n_diff << " elements at index " << i_start << std::endl; #endif } // Insert the array elements into the matrix if (number > 0) { for (int row = 0, i = i_start; row < number; ++row, ++i) { m_data[i] = values[row]; m_rowinx[i] = rows[row]; } } // Update column start indices for (int i = column+1; i <= m_cols; ++i) { m_colstart[i] += n_diff; } // Debugging: show sparse matrix after insertion #if defined(G_DEBUG_SPARSE_INSERTION) std::cout << " Out Data: "; for (int i = 0; i < m_elements; ++i) { std::cout << m_data[i] << " "; } std::cout << std::endl << " Out Row : "; for (int i = 0; i < m_elements; ++i) { std::cout << m_rowinx[i] << " "; } std::cout << std::endl << " Out Col : "; for (int i = 0; i < m_cols+1; ++i) { std::cout << m_colstart[i] << " "; } std::cout << std::endl; #endif // Return return; } /***********************************************************************//** * @brief Add row to matrix elements * * @todo To be implemented. ***************************************************************************/ void GMatrixSparse::add_to_row(const int& row, const GVector& vector) { // Raise an exception if the row index is invalid #if defined(G_RANGE_CHECK) if (row < 0 || row >= m_rows) { throw GException::out_of_range(G_ADD_TO_ROW, row, 0, m_rows-1); } #endif // Return return; } /***********************************************************************//** * @brief Add vector column into matrix * * @param[in] column Column index [0,...,columns()-1]. * @param[in] vector Vector. * * @exception GException::out_of_range * Invalid column index specified. * @exception GException::matrix_vector_mismatch * Matrix dimension mismatches the vector size. * * Adds the contect of a vector to a matrix column. * * This is the main driver routine to add data to a matrix. It handles both * normal and stack-based filled. Note that there is another instance of this * method that takes a compressed array. ***************************************************************************/ void GMatrixSparse::add_to_column(const int& column, const GVector& vector) { // Debug header #if defined(G_DEBUG_SPARSE_ADDITION) std::cout << "GMatrixSparse::add_col("; std::cout << column << ", [" << v << "]):" << std::endl; std::cout << " In Data : "; for (int i = 0; i < m_elements; ++i) { std::cout << m_data[i] << " "; } std::cout << std::endl << " In Row .: "; for (int i = 0; i < m_elements; ++i) { std::cout << m_rowinx[i] << " "; } std::cout << std::endl << " In Col .: "; for (int i = 0; i < m_cols+1; ++i) { std::cout << m_colstart[i] << " "; } std::cout << std::endl; #endif // Initialise number of non-zero elements to 0 int non_zero = 0; // If we have a stack then try to push vector on stack first. Note that // stack_push_column does its own argument verifications, so to avoid // double checking we don't do anything before this call ... if (m_stack_data != NULL) { non_zero = stack_push_column(vector, column); if (non_zero == 0) { return; } } // ... otherwise check first the arguments and determine the number of // non-zero elements in vector else { // Raise an exception if the column index is invalid #if defined(G_RANGE_CHECK) if (column < 0 || column >= m_cols) { throw GException::out_of_range(G_ADD_TO_COLUMN, column, 0, m_cols-1); } #endif // Raise an exception if the matrix and vector dimensions are incompatible if (m_rows != vector.size()) { throw GException::matrix_vector_mismatch(G_ADD_TO_COLUMN, vector.size(), m_rows, m_cols); } // Determine number of non-zero elements in vector non_zero = vector.non_zeros(); } // Extract vector for column, add elements, and re-insert vector (only if // vector to insert has non-zeros) if (non_zero > 0) { // Copy input vector GVector v_column = vector; // Add elements to vector for (int i = m_colstart[column]; i < m_colstart[column+1]; ++i) { v_column[m_rowinx[i]] += m_data[i]; } // If there is a pending element then put it in the vector if (m_fill_val != 0.0 && m_fill_col == column) { v_column[m_fill_row] += m_fill_val; } // Set vector into matrix this->column(column, v_column); } // Debugging: show sparse matrix after addition #if defined(G_DEBUG_SPARSE_ADDITION) std::cout << " Out Data: "; for (int i = 0; i < m_elements; ++i) { std::cout << m_data[i] << " "; } std::cout << std::endl << " Out Row : "; for (int i = 0; i < m_elements; ++i) { std::cout << m_rowinx[i] << " "; } std::cout << std::endl << " Out Col : "; for (int i = 0; i < m_cols+1; ++i) { std::cout << m_colstart[i] << " "; } std::cout << std::endl; #endif // Return return; } /***********************************************************************//** * @brief Add compressed array into matrix column * * @param[in] column Column index [0,...,columns()-1]. * @param[in] values Compressed array. * @param[in] rows Row indices of array. * @param[in] number Number of elements in array. * * @exception GException::out_of_range * Invalid column index specified. * @exception GException::matrix_vector_mismatch * Matrix dimension mismatches the vector size. * * Adds the content of a compressed array into a matrix column. * * This is the main driver routine to add data to a matrix. It handles both * normal and stack-based filled. Note that there is another instance of this * method that takes a vector. ***************************************************************************/ void GMatrixSparse::add_to_column(const int& column, const double* values, const int* rows, int number) { // Debug header #if defined(G_DEBUG_SPARSE_ADDITION) std::cout << "GMatrixSparse::add_col("; std::cout << column << ", values, rows, " << number << "):" << std::endl; std::cout << " Matrix Data : "; for (int i = 0; i < m_elements; ++i) { std::cout << m_data[i] << " "; } std::cout << std::endl << " Matrix Row .: "; for (int i = 0; i < m_elements; ++i) { std::cout << m_rowinx[i] << " "; } std::cout << std::endl << " Matrix Col .: "; for (int i = 0; i < m_cols+1; ++i) { std::cout << m_colstart[i] << " "; } std::cout << std::endl; std::cout << " Array Data .: "; for (int i = 0; i < number; ++i) { std::cout << values[i] << " "; } std::cout << std::endl << " Array Row ..: "; for (int i = 0; i < number; ++i) { std::cout << rows[i] << " "; } std::cout << std::endl; #endif // If we have a stack then try to push elements on stack first. Note that // stack_push_column does its own argument verifications, so to avoid // double checking we don't do anything before this call ... if (m_stack_data != NULL) { number = stack_push_column(values, rows, number, column); if (number == 0) { return; } } // ... otherwise check the arguments else { // If the array is empty there is nothing to do if (!values || !rows || (number < 1)) { return; } // Raise an exception if the column index is invalid #if defined(G_RANGE_CHECK) if (column < 0 || column >= m_cols) { throw GException::out_of_range(G_ADD_TO_COLUMN2, column, 0, m_cols-1); } #endif // Raise an exception if the matrix and vector dimensions are incompatible if (rows[number-1] >= m_rows) { throw GException::matrix_vector_mismatch(G_ADD_TO_COLUMN2, rows[number-1], m_rows, m_cols); } } // endelse: there was no stack // Get indices of column in matrix int i_start = m_colstart[column]; int i_stop = m_colstart[column+1]; // Case A: the column exists in the matrix, so mix new elements with existing // data if (i_start < i_stop) { // Fill pending element before the merge (it will be lost otherwise) fill_pending(); // Allocate workspace to hold combined column int wrk_size = number + i_stop - i_start; double* wrk_double = new double[wrk_size]; int* wrk_int = new int[wrk_size]; // Mix matrix column with specified data int num_mix; mix_column(&(m_data[i_start]), &(m_rowinx[i_start]), i_stop-i_start, values, rows, number, wrk_double, wrk_int, &num_mix); // Insert mixed column this->column(column, wrk_double, wrk_int, num_mix); // Free workspace delete [] wrk_int; delete [] wrk_double; } // endif: Case A // Case B: the column does not yet exist in the matrix, so just insert it else { this->column(column, values, rows, number); } // Debugging: show sparse matrix after insertion #if defined(G_DEBUG_SPARSE_ADDITION) std::cout << " Out Data: "; for (int i = 0; i < m_elements; ++i) { std::cout << m_data[i] << " "; } std::cout << std::endl << " Out Row : "; for (int i = 0; i < m_elements; ++i) { std::cout << m_rowinx[i] << " "; } std::cout << std::endl << " Out Col : "; for (int i = 0; i < m_cols+1; ++i) { std::cout << m_colstart[i] << " "; } std::cout << std::endl; #endif // Return return; } /***********************************************************************//** * @brief Return transposed matrix * * @return Transposed matrix. * * Returns transposed matrix of the matrix. ***************************************************************************/ GMatrixSparse GMatrixSparse::transpose(void) const { // Copy matrix GMatrixSparse matrix = *this; // Fill pending element matrix.fill_pending(); // Compute the transpose matrix = cs_transpose(matrix, 1); // Return matrix return matrix; } /***********************************************************************//** * @brief Return inverted matrix * * @return Inverted matrix. * * Returns inverse of matrix. Inversion is done for the moment using Cholesky * decomposition. This does not work on any kind of matrix. * * @todo Specify in documentation for which kind of matrix the method works. ***************************************************************************/ GMatrixSparse GMatrixSparse::invert(void) const { // Copy matrix GMatrixSparse matrix = *this; // Fill pending element matrix.fill_pending(); // Invert matrix matrix = matrix.cholesky_invert(true); // Return matrix return matrix; } /***********************************************************************//** * @brief Solves linear matrix equation * * @param[in] vector Solution vector. * * Solves the linear equation * * \f[M \times {\tt solution} = {\tt vector} \f] * * where \f$M\f$ is the matrix, \f${\tt vector}\f$ is the result, and * \f${\tt solution}\f$ is the solution. Solving is done using Cholesky * decomposition. This does not work on any kind of matrix. * * @todo Specify in documentation for which kind of matrix the method works. ***************************************************************************/ GVector GMatrixSparse::solve(const GVector& vector) const { // Get Cholesky decomposition of matrix GMatrixSparse decomposition = cholesky_decompose(true); // Solve linear equation GVector result = decomposition.cholesky_solver(vector); // Return result return result; } /***********************************************************************//** * @brief Return absolute of matrix * * @return Absolute of matrix * * Returns matrix where all elements of the matrix have been replaced by * their absolute values. ***************************************************************************/ GMatrixSparse GMatrixSparse::abs(void) const { // Copy matrix GMatrixSparse matrix = *this; // Fill pending element matrix.fill_pending(); // Take the absolute value of all matrix elements double* ptr = matrix.m_data; for (int i = 0; i < matrix.m_elements; ++i, ++ptr) { *ptr = std::abs(*ptr); } // Return matrix return matrix; } /***********************************************************************//** * @brief Returns fill of matrix * * The fill of a matrix is defined as the number non-zero elements devided * by the number of total elements. By definition, the fill is comprised * in the interval [0,..,1]. The fill of an undefined matrix is defined to * be 0. ***************************************************************************/ double GMatrixSparse::fill(void) const { // Initialise result double result = 0.0; // Compute matrix size int size = m_rows*m_cols; // Continue only if matrix has elements if (size > 0) { // Determine number of elements in matrix int num = (m_fill_val == 0.0) ? m_elements : m_elements + 1; // Compute fill result = double(num) / double(size); } // endif: there were elements in matrix // Return fill return result; } /***********************************************************************//** * @brief Return minimum matrix element ***************************************************************************/ double GMatrixSparse::min(void) const { // Initialise minimum with fill value double result = m_fill_val; // Search all elements for the smallest one for (int i = 0; i < m_elements; ++i) { if (m_data[i] < result) { result = m_data[i]; } } // If minimum is > 0.0 and there are zero elements then set the minimum // to 0.0 if ((result > 0.0) && (m_elements < (m_rows*m_cols))) { result = 0.0; } // Return result return result; } /***********************************************************************//** * @brief Return maximum matrix element ***************************************************************************/ double GMatrixSparse::max(void) const { // Initialise maximum with fill value double result = m_fill_val; // Search all elements for the largest one for (int i = 0; i < m_elements; ++i) { if (m_data[i] > result) { result = m_data[i]; } } // If maximum is < 0.0 and there are zero elements then set the maximum // to 0.0 if ((result < 0.0) && (m_elements < (m_rows*m_cols))) { result = 0.0; } // Return result return result; } /***********************************************************************//** * @brief Sum matrix elements ***************************************************************************/ double GMatrixSparse::sum(void) const { // Initialise matrix sum with fill value double result = m_fill_val; // Add all elements for (int i = 0; i < m_elements; ++i) { result += m_data[i]; } // Return result return result; } /***********************************************************************//** * @brief Return Cholesky decomposition * * @param[in] compress Use zero-row/column compression (defaults to true). * @return Cholesky decomposition of matrix * * Returns the Cholesky decomposition of a sparse matrix. The decomposition * is stored within a GMatrixSparse object. ***************************************************************************/ GMatrixSparse GMatrixSparse::cholesky_decompose(const bool& compress) const { // Create copy of matrix GMatrixSparse matrix = *this; // Save original matrix size int matrix_rows = matrix.m_rows; int matrix_cols = matrix.m_cols; // Delete any existing symbolic and numeric analysis object and reset // pointers if (matrix.m_symbolic != NULL) delete matrix.m_symbolic; if (matrix.m_numeric != NULL) delete matrix.m_numeric; matrix.m_symbolic = NULL; matrix.m_numeric = NULL; // Allocate symbolic analysis object GSparseSymbolic* symbolic = new GSparseSymbolic(); // Declare numeric analysis object. We don't allocate one since we'll // throw it away at the end of the function (the L matrix will be copied // in this object) GSparseNumeric numeric; // Fill pending element into matrix matrix.fill_pending(); // Remove rows and columns containing only zeros if matrix compression // has been selected if (compress) { matrix.remove_zero_row_col(); } // Ordering an symbolic analysis of matrix. This sets up an array 'pinv' // which contains the fill-in reducing permutations symbolic->cholesky_symbolic_analysis(1, matrix); // Store symbolic pointer in sparse matrix object matrix.m_symbolic = symbolic; // Perform numeric Cholesky decomposition numeric.cholesky_numeric_analysis(matrix, *symbolic); // Copy L matrix into this object matrix.free_elements(0, matrix.m_elements); matrix.alloc_elements(0, numeric.m_L->m_elements); for (int i = 0; i < matrix.m_elements; ++i) { matrix.m_data[i] = numeric.m_L->m_data[i]; matrix.m_rowinx[i] = numeric.m_L->m_rowinx[i]; } for (int col = 0; col <= matrix.m_cols; ++col) { matrix.m_colstart[col] = numeric.m_L->m_colstart[col]; } // Insert zero rows and columns if they have been removed previously. if (compress) { matrix.insert_zero_row_col(matrix_rows, matrix_cols); } // Return matrix return matrix; } /***********************************************************************//** * @brief Cholesky solver * * @param[in] vector Solution vector. * @param[in] compress Request matrix compression. * * @exception GException::matrix_vector_mismatch * Matrix and vector do not match. * @exception GException::matrix_not_factorised * Matrix has not been factorised. * * Solves the linear equation A*x=b using a Cholesky decomposition of A. * This function is to be applied on a GMatrixSparse matrix for which a * Choleksy factorization has been produced using 'cholesky_decompose'. ***************************************************************************/ GVector GMatrixSparse::cholesky_solver(const GVector& vector, const bool& compress) const { // Dump header #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << "GMatrixSparse::cholesky_solver" << std::endl; std::cout << " Input vector .....: " << vector << std::endl; #endif // Raise an exception if the matrix and vector dimensions are incompatible if (m_rows != vector.size()) { throw GException::matrix_vector_mismatch(G_CHOL_SOLVE, vector.size(), m_rows, m_cols); } // Raise an exception if there is no symbolic pointer if (!m_symbolic) { throw GException::matrix_not_factorised(G_CHOL_SOLVE, "Cholesky decomposition"); } // Raise an exception if there is no permutation if (!m_symbolic->m_pinv) { throw GException::matrix_not_factorised(G_CHOL_SOLVE, "Cholesky decomposition"); } // Flag row and column compression int row_compressed = (m_rowsel != NULL && m_num_rowsel < m_rows); int col_compressed = (m_colsel != NULL && m_num_colsel < m_cols); // Decide if we need a compression algorithm or not int no_zero = !(compress && (row_compressed || col_compressed)); // Allocate vector for permutation and result vector GVector result(m_cols); // Setup pointers to L matrix and x vector int* Lp = m_colstart; int* Li = m_rowinx; double* Lx = m_data; // Case A: no zero-row/col compression needed if (no_zero) { // Declare working vector GVector x(m_rows); // Perform inverse vector permutation for (int i = 0; i < vector.size(); ++i) { x[m_symbolic->m_pinv[i]] = vector[i]; } // Inplace solve L\x=x for (int col = 0; col < m_cols; ++col) { // loop over columns x[col] /= Lx[Lp[col]]; // divide by diag. for (int p = Lp[col]+1; p < Lp[col+1]; p++) // loop over elements x[Li[p]] -= Lx[p] * x[col]; } // Inplace solve L'\x=x for (int col = m_cols-1; col >= 0; --col) { // loop over columns for (int p = Lp[col]+1; p < Lp[col+1]; p++) // loop over elements x[col] -= Lx[p] * x[Li[p]]; x[col] /= Lx[Lp[col]]; } // Perform vector permutation for (int i = 0; i < m_cols; ++i) { result[i] = x[m_symbolic->m_pinv[i]]; } } // endif: Case A // Case B: zero-row/column compression requested else { // Allocate row and column mapping arrays int* row_map = new int[m_rows]; int* col_map = new int[m_cols]; // Setup row mapping array that maps original matrix rows into compressed // matrix rows. An entry of -1 indicates that the row should be dropped. // If no selection exists then setup an identity map. if (row_compressed) { for (int row = 0; row < m_rows; ++row) { row_map[row] = -1; } for (int c_row = 0; c_row < m_num_rowsel; ++c_row) { row_map[m_rowsel[c_row]] = c_row; } } else { for (int row = 0; row < m_rows; ++row) { row_map[row] = row; } } #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " Row mapping ......:"; for (int row = 0; row < m_rows; ++row) { std::cout << " " << row_map[row]; } std::cout << std::endl; #endif // Setup column mapping array that maps original matrix column into compressed // matrix columns. An entry of -1 indicates that the column should be dropped // If no selection exists then setup an identity map. if (col_compressed) { for (int col = 0; col < m_cols; ++col) { col_map[col] = -1; } for (int c_col = 0; c_col < m_num_colsel; ++c_col) { col_map[m_colsel[c_col]] = c_col; } } else { for (int col = 0; col < m_cols; ++col) { col_map[col] = col; } } #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " Column mapping ...:"; for (int col = 0; col < m_cols; ++col) { std::cout << " " << col_map[col]; } std::cout << std::endl; #endif // Declare working vector GVector x(row_compressed ? m_num_rowsel : m_rows); // Compress input vector v -> c_v if required if (m_rowsel != NULL && m_num_rowsel < m_rows) { for (int c_row = 0; c_row < m_num_rowsel; ++c_row) { x[c_row] = vector[m_rowsel[c_row]]; } } else { x = vector; } #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " Compressed vector : " << x << std::endl; #endif // Perform inverse permutation x = iperm(x, m_symbolic->m_pinv); #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " Permutated vector : " << x << std::endl; #endif // Inplace solve L\x=x. The column and row maps are just use to see which // columns or rows should be skipped in the calculations. for (int col = 0; col < m_cols; ++col) { // loop over columns int c_col = col_map[col]; if (c_col >= 0) { // use only non-zero cols x[c_col] /= Lx[Lp[col]]; // divide by diag. for (int p = Lp[col]+1; p < Lp[col+1]; p++) { // loop over elements int c_row = row_map[Li[p]]; if (c_row >= 0) { // use only non-zero rows x[c_row] -= Lx[p] * x[c_col]; } } } } #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " Solve Lx=x .......: " << x << std::endl; #endif // Inplace solve L'\x=x. The column and row maps are just use to see which // columns or rows should be skipped in the calculations. for (int col = m_cols-1; col >= 0; --col) { // loop over columns int c_col = col_map[col]; if (c_col >= 0) { // use only non-zero cols for (int p = Lp[col]+1; p < Lp[col+1]; p++) { // loop over elements int c_row = row_map[Li[p]]; if (c_row >= 0) { // use only non-zero rows x[c_col] -= Lx[p] * x[c_row]; } } x[c_col] /= Lx[Lp[col]]; } } #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " Solve L'x=x ......: " << x << std::endl; #endif // Perform vector permutation x = perm(x, m_symbolic->m_pinv); #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " Permutated vector : " << x << std::endl; #endif // If column compression has been performed the expand the result vector // accordingly if (m_colsel != NULL && m_num_colsel < m_cols) { for (int c_col = 0; c_col < m_num_colsel; ++c_col) { result[m_colsel[c_col]] = x[c_col]; } } else { result = x; } #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " Restored vector ..: " << result << std::endl; #endif // Free mapping arrays delete [] row_map; delete [] col_map; } // endelse: Case B // Return result vector return result; } /***********************************************************************//** * @brief Invert matrix using a Cholesky decomposition * * @param[in] compress Use zero-row/column compression (defaults to true). * @return Inverted matrix. * * Inverts the matrix using a Cholesky decomposition. ***************************************************************************/ GMatrixSparse GMatrixSparse::cholesky_invert(const bool& compress) const { // Generate Cholesky decomposition of matrix GMatrixSparse decomposition = cholesky_decompose(compress); // Allocate result matrix and unit vector GMatrixSparse matrix(m_rows, m_cols); GVector unit(m_rows); // Column-wise solving of the problem for (int col = 0; col < m_cols; ++col) { // Set unit vector unit[col] = 1.0; // Solve for column GVector x = decomposition.cholesky_solver(unit, compress); // Insert column in matrix matrix.column(col, x); // Clear unit vector for next round unit[col] = 0.0; } // Return matrix return matrix; } /***********************************************************************//** * @brief Print matrix * * @param[in] chatter Chattiness (defaults to NORMAL). * @return String containing matrix information ***************************************************************************/ std::string GMatrixSparse::print(const GChatter& chatter) const { // Initialise result string std::string result; // Continue only if chatter is not silent if (chatter != SILENT) { // Determine number of elements int nonzero; int elements = m_elements; if (m_fill_val == 0.0) { nonzero = (m_colstart != NULL) ? m_colstart[m_cols] : 0; } else { nonzero = (m_colstart != NULL) ? m_colstart[m_cols]+1 : 0; elements++; } // Append header result.append("=== GMatrixSparse ==="); // Append information result.append("\n"+gammalib::parformat("Number of rows")); result.append(gammalib::str(m_rows)); if (m_rowsel != NULL) { result.append(" (compressed "+gammalib::str(m_num_rowsel)+")"); } result.append("\n"+gammalib::parformat("Number of columns")); result.append(gammalib::str(m_cols)); if (m_colsel != NULL) { result.append(" (compressed "+gammalib::str(m_num_colsel)+")"); } result.append("\n"+gammalib::parformat("Number of nonzero elements")); result.append(gammalib::str(nonzero)); if (m_fill_val != 0.0) { result.append("\n"+gammalib::parformat("Pending element")); result.append("("+gammalib::str(m_fill_row)+","+gammalib::str(m_fill_col)+")="); result.append(gammalib::str(m_fill_val)); } result.append("\n"+gammalib::parformat("Number of allocated cells")); result.append(gammalib::str(m_alloc)); result.append("\n"+gammalib::parformat("Memory block size")); result.append(gammalib::str(m_mem_block)); result.append("\n"+gammalib::parformat("Sparse matrix fill")); result.append(gammalib::str(fill())); result.append("\n"+gammalib::parformat("Pending element")); result.append(gammalib::str(m_fill_val)); result.append("\n"+gammalib::parformat("Fill stack size")); result.append(gammalib::str(m_stack_size)); if (m_stack_data == NULL) { result.append(" (none)"); } // Append elements and compression schemes result.append(print_elements(chatter)); result.append(print_row_compression(chatter)); result.append(print_col_compression(chatter)); // VERBOSE if (chatter >= VERBOSE) { // Append symbolic decomposition if available //if (m_symbolic != NULL) { // result.append(m_symbolic->print(chatter)); //} // Append numeric decomposition if available //if (m_numeric != NULL) { // result.append(m_numeric->print(chatter)); //} } // endif: chatter was verbose } // endif: chatter was not silent // Return result return result; } /***********************************************************************//** * @brief Initialises matrix filling stack * * @param[in] size Stack size. * @param[in] entries Maximum number of entries. * * The matrix filling stack is used to allow for a fast column-wise filling * of a sparse matrix. Columns are successively appended to a stack which is * regularily flushed when it is full. This reduces memory copies and * movements and increases filling speed. ***************************************************************************/ void GMatrixSparse::stack_init(const int& size, const int& entries) { // Free exisiting stack free_stack_members(); // Initialise stack members init_stack_members(); m_stack_max_entries = (entries > 0) ? entries : m_cols; m_stack_size = (size > 0) ? size : G_SPARSE_MATRIX_DEFAULT_STACK_SIZE; // Allocate stack memory. Raise an exception if allocation fails m_stack_colinx = new int[m_stack_max_entries]; m_stack_start = new int[m_stack_max_entries+1]; m_stack_data = new double[m_stack_size]; m_stack_rowinx = new int[m_stack_size]; m_stack_work = new int[m_cols]; m_stack_rows = new int[m_cols]; m_stack_values = new double[m_cols]; // Initialise next free stack location to the first stack element m_stack_start[0] = 0; // Return return; } /***********************************************************************//** * @brief Push a vector column on the matrix stack * * @param[in] vector Vector. * @param[in] col Column index (starting from 0). * * @exception GException::out_of_range * Invalid column index specified. * @exception GException::matrix_vector_mismatch * Matrix dimension mismatches the vector size. * * Adds the contect of a vector to the matrix stack. This method is * identical to the GMatrixSparse::stack_push_column method that uses a * compressed array, yet it takes a full vector. Internal working arrays * are used to convert the full column vector in a compressed array and to * hand it over to the compressed array version. ***************************************************************************/ int GMatrixSparse::stack_push_column(const GVector& vector, const int& col) { // Initialise number of non-zero elements int non_zero = 0; // Compress vector in the buffer and set-up row index array for (int i = 0; i < vector.size(); ++i) { if (vector[i] != 0.0) { m_stack_values[non_zero] = vector[i]; m_stack_rows[non_zero] = i; non_zero++; } } // Hand over to compressed array method int remaining = stack_push_column(m_stack_values, m_stack_rows, non_zero, col); // Return remaining number of elements return remaining; } /***********************************************************************//** * @brief Push a compressed array on the matrix stack * * @param[in] values Compressed array. * @param[in] rows Row indices of array. * @param[in] number Number of elements in array. * @param[in] col Column index (starting from 0). * * @exception GException::out_of_range * Invalid column index specified. * @exception GException::matrix_vector_mismatch * Matrix dimension mismatches the vector size. * * The method puts new data on the stack while assuring that each column * is present only once. If an already existing column is encountered * the data from the existing and new column are mixed and put into a new * entry one the stack; the old entry is signalled as obsolete. * * On return the method indicates the number of elements in the input * array that have not been put onto the stack (due to memory limits). * This value can be either '0' (i.e. all elements have been put on the * stack) or 'number' (i.e. none of the elements have been put on the * stack). Columns are not partially put onto the stack. ***************************************************************************/ int GMatrixSparse::stack_push_column(const double* values, const int* rows, const int& number, const int& col) { // Initialise return value int remaining = number; // Single loop for common exit point do { // If the array is empty there is nothing to do if (!values || !rows || (number < 1)) { continue; } // If there is no stack or the stack can not hold the requested // number of elements then report number of array elements to the // caller if (m_stack_data == NULL || number > m_stack_size) { continue; } // Raise an exception if the column index is invalid #if defined(G_RANGE_CHECK) if (col < 0 || col >= m_cols) { throw GException::out_of_range(G_STACK_PUSH, col, 0, m_cols-1); } #endif // Raise an exception if the matrix and vector dimensions are // incompatible if (rows[number-1] >= m_rows) { throw GException::matrix_vector_mismatch(G_STACK_PUSH, rows[number-1], m_rows, m_cols); } // Debug header #if defined(G_DEBUG_SPARSE_STACK_PUSH) std::cout << "GMatrixSparse::stack_push_column(v, i, n, col=" << col << ")"; std::cout << std::endl; std::cout << " Data to push on stack ...:"; for (int i = 0; i < number; ++i) { std::cout << " " << values[i]; } std::cout << std::endl; std::cout << " Row indices of data .....:"; for (int i = 0; i < number; ++i) { std::cout << " " << rows[i]; } std::cout << std::endl; #endif // If the stack is full then flush it before pushing the array onto it. // There are 2 conditions that may fill the stack: all entries are // occupied or there is not enough space to hold more elements if ((m_stack_entries >= m_stack_max_entries) || (number >= (m_stack_size - m_stack_start[m_stack_entries]))) { stack_flush(); } // If the specified column is already on the stack then mix new column // with old one and invalidate old column (by setting its column index // to -1). // Since we loop here over all stack entries we don't want to have too // many entries in the stack. So don't set the maximum number of stack // entries too large ... for (int entry = 0; entry < m_stack_entries; ++entry) { // Is the specified column already on the stack? if (col == m_stack_colinx[entry]) { // Get start and stop indices of existing column in stack int i_start = m_stack_start[entry]; int i_stop = m_stack_start[entry+1]; // Allocate variables to hold mixing results int num_1; // Number of elements that are only in stack column int num_2; // Number of elements that are only in new column int num_mix; // Number of elements that are in both columns // Determine how many elements are requested to hold the combined // column mix_column_prepare(&(m_stack_rowinx[i_start]), i_stop-i_start, rows, number, &num_1, &num_2, &num_mix); int num_request = num_1 + num_2 + num_mix; // If there is not enough space in the stack to hold the combined // column we flush the stack and exit the loop now. In this case // the new column is put as the first column in the empty (flushed // stack) if (num_request >= (m_stack_size - m_stack_start[m_stack_entries])) { stack_flush(); break; } // There is enough space, so combine both columns into a fresh one int inx = m_stack_start[m_stack_entries]; int num_new; mix_column(&(m_stack_data[i_start]), &(m_stack_rowinx[i_start]), i_stop-i_start, values, rows, number, &(m_stack_data[inx]), &(m_stack_rowinx[inx]), &num_new); // Store entry information and initialise start of next entry m_stack_colinx[m_stack_entries] = col; // Store column index for entry m_stack_entries++; // Increase the # of entries m_stack_start[m_stack_entries] = inx + num_new; // Set start pointer for next entry // Invalidate old column m_stack_colinx[entry] = -1; // Fall through to end remaining = 0; break; } // endif: we found an existing column } // endfor: looped over all columns // If column has already been inserted then fall through ... if (remaining == 0) { continue; } // Otherwise, push the array on the stack int inx = m_stack_start[m_stack_entries]; for (int i = 0; i < number; ++i) { m_stack_data[inx] = values[i]; m_stack_rowinx[inx] = rows[i]; inx++; } // Store entry information and initialise start of next entry m_stack_colinx[m_stack_entries] = col; // Store column index for entry m_stack_entries++; // Increase the # of entries m_stack_start[m_stack_entries] = inx; // Set start pointer for next entry // Signal success remaining = 0; } while (0); // End of main do-loop // Debug: show stack information #if defined(G_DEBUG_SPARSE_STACK_PUSH) std::cout << " Number of stack entries .: " << m_stack_entries << std::endl; std::cout << " Stack entry columns .....:"; for (int i = 0; i < m_stack_entries; ++i) { std::cout << " " << m_stack_colinx[i]; } std::cout << std::endl; std::cout << " Stack entry starts ......:"; for (int i = 0; i < m_stack_entries; ++i) { std::cout << " " << m_stack_start[i]; } std::cout << " (next at " << m_stack_start[m_stack_entries] << ")" << std::endl; std::cout << " Stack data ..............:"; for (int i = 0; i < m_stack_start[m_stack_entries]; ++i) { std::cout << " " << m_stack_data[i]; } std::cout << std::endl; std::cout << " Stack rows ..............:"; for (int i = 0; i < m_stack_start[m_stack_entries]; ++i) { std::cout << " " << m_stack_rowinx[i]; } std::cout << std::endl; #endif // Return remaining number of elements return remaining; } /***********************************************************************//** * @brief Flush matrix stack * * Adds the stack to the actual matrix. First, the total number of matrix * elements is determined. Then new memory is allocated to hold all * elements. Finally, all elements are filled into a working array that is * then compressed into the matrix. * * The method uses the internal working array m_stack_work. * * NOTE: The present algorithm assumes that each column occurs only once * in the stack! ***************************************************************************/ void GMatrixSparse::stack_flush(void) { // Do nothing if there is no stack if (m_stack_data == NULL) { return; } // Fill pending value fill_pending(); // Debug header #if defined(G_DEBUG_SPARSE_STACK_FLUSH) std::cout << "GMatrixSparse::stack_flush" << std::endl; std::cout << " Number of stack entries .: " << m_stack_entries << std::endl; std::cout << " Number of stack elements : " << m_stack_start[m_stack_entries] << std::endl; std::cout << " Number of matrix elements: " << m_elements << std::endl; #endif // Use stack working array to flag all columns that exist already in the // matrix by 1 and all non-existing columns by 0 for (int col = 0; col < m_cols; ++col) { if (m_colstart[col] < m_colstart[col+1]) { m_stack_work[col] = 1; } else { m_stack_work[col] = 0; } } // Initialise some element counters int new_elements = 0; #if defined(G_DEBUG_SPARSE_STACK_FLUSH) int num_matrix = 0; // Number of elements only in matrix int num_stack = 0; // Number of elements only on stack int num_both = 0; // Number of elements in matrix and on stack #endif // Loop over all stack entries and gather the number of elements that are // new with respect to the initial matrix. For each column set a flag with // the following meanings: // -(entry+2): Column exists and is also found in stack=entry // +(entry+2): Column exists in stack=entry only // 1: Column exists in matrix only // 0: Column does neither exist in matrix or stack for (int entry = 0; entry < m_stack_entries; ++entry) { // Get column for actual entry int col = m_stack_colinx[entry]; // Consider only valid entries if (col >= 0) { // If column exists already in matrix then determine total number // of additional elements if (m_stack_work[col] == 1) { // Flag that this column is a mixed column m_stack_work[col] = -(entry+2); // Setup index boundaries int i_start = m_colstart[col]; int i_stop = m_colstart[col+1]; int k_start = m_stack_start[entry]; int k_stop = m_stack_start[entry+1]; // Allocate output counters int num_1; int num_2; int num_mix; // Analyse column mixing mix_column_prepare(&(m_rowinx[i_start]), i_stop-i_start, &(m_stack_rowinx[k_start]), k_stop-k_start, &num_1, &num_2, &num_mix); // Update element counters new_elements += num_2; #if defined(G_DEBUG_SPARSE_STACK_FLUSH) num_matrix += num_1; num_stack += num_2; num_both += num_mix; #endif } // endif: column existed in the matrix // If column did not exists in the matrix then consider all // elements as new else { m_stack_work[col] = (entry+2); new_elements += (m_stack_start[entry+1] - m_stack_start[entry]); } } // endif: entry was valid } // endfor: looped over all entries int elements = m_elements + new_elements; // Dump number of elements in new matrix #if defined(G_DEBUG_SPARSE_STACK_FLUSH) std::cout << " New elements ............: " << new_elements << std::endl; #endif // Allocate memory for new matrix (always keep some elbow room) m_alloc = elements + m_mem_block; double* new_data = new double[m_alloc]; int* new_rowinx = new int[m_alloc]; // Fill new matrix. For this purpose we loop over all matrix columns // and perform the operation that was identified in the previous scan int index = 0; for (int col = 0; col < m_cols; ++col) { // If column does not exist then skip if (m_stack_work[col] == 0) { m_colstart[col] = index; continue; } // If column exists in the matrix but not in the stack we copy the // existing column into the new matrix if (m_stack_work[col] == 1) { for (int i = m_colstart[col]; i < m_colstart[col+1]; ++i) { new_data[index] = m_data[i]; new_rowinx[index] = m_rowinx[i]; index++; #if defined(G_DEBUG_SPARSE_STACK_FLUSH) num_matrix++; #endif } } // If column is new (i.e. it did not exist in the matrix before) // we copy the stack into the new matrix else if (m_stack_work[col] > 1) { int entry = m_stack_work[col] - 2; for (int i = m_stack_start[entry]; i < m_stack_start[entry+1]; ++i) { new_data[index] = m_stack_data[i]; new_rowinx[index] = m_stack_rowinx[i]; index++; #if defined(G_DEBUG_SPARSE_STACK_FLUSH) num_stack++; #endif } } // If column exists in the matrix and in the stack then we have to mix both else { // Get stack entry for column mix int entry = -(m_stack_work[col] + 2); // Setup index boundaries int i_start = m_colstart[col]; int i_stop = m_colstart[col+1]; int k_start = m_stack_start[entry]; int k_stop = m_stack_start[entry+1]; // Allocate output counter int num; // Perform column mixing mix_column(&(m_data[i_start]), &(m_rowinx[i_start]), i_stop-i_start, &(m_stack_data[k_start]), &(m_stack_rowinx[k_start]), k_stop-k_start, &(new_data[index]), &(new_rowinx[index]), &num); // Increment element index index += num; } // endelse: column mixing required // Store actual index in column start array m_colstart[col] = index; } // endfor: looped over all columns // Dump number of elements in new matrix after addition #if defined(G_DEBUG_SPARSE_STACK_FLUSH) std::cout << " Added elements ..........: " << index << " (should be " << elements << ")" << std::endl; std::cout << " - Matrix only ...........: " << num_matrix << std::endl; std::cout << " - Stack only ............: " << num_stack << std::endl; std::cout << " - Matrix & Stack ........: " << num_both << std::endl; #endif // Correct columns start array for (int col = m_cols; col > 0; --col) { m_colstart[col] = m_colstart[col-1]; } m_colstart[0] = 0; // Delete old matrix memory if (m_data != NULL) delete [] m_data; if (m_rowinx != NULL) delete [] m_rowinx; // Update pointers to new memory and update element counter m_data = new_data; m_rowinx = new_rowinx; m_elements = elements; // Stack is empty now, so reset stack counters m_stack_entries = 0; m_stack_start[0] = 0; // Return return; } /***********************************************************************//** * @brief Destroy matrix stack * * Flush and destroy matrix stack ***************************************************************************/ void GMatrixSparse::stack_destroy(void) { // Flush stack first stack_flush(); // Free stack members free_stack_members(); // Initialise stack (no entries) init_stack_members(); // Return return; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class mambers ***************************************************************************/ void GMatrixSparse::init_members(void) { // Initialise sparse matrix members m_rowinx = NULL; m_mem_block = G_SPARSE_MATRIX_DEFAULT_MEM_BLOCK; m_zero = 0.0; m_fill_val = 0.0; m_fill_row = 0; m_fill_col = 0; m_symbolic = NULL; m_numeric = NULL; // Initialise stack members init_stack_members(); // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] matrix Matrix to be copied. ***************************************************************************/ void GMatrixSparse::copy_members(const GMatrixSparse& matrix) { // Copy GMatrixSparse members m_mem_block = matrix.m_mem_block; m_zero = matrix.m_zero; m_fill_val = matrix.m_fill_val; m_fill_row = matrix.m_fill_row; m_fill_col = matrix.m_fill_col; // Copy row indices if they exist if (matrix.m_rowinx != NULL && matrix.m_alloc > 0) { m_rowinx = new int[matrix.m_alloc]; for (int i = 0; i < matrix.m_elements; ++i) { m_rowinx[i] = matrix.m_rowinx[i]; } } // Clone symbolic decomposition if it exists if (matrix.m_symbolic != NULL) { m_symbolic = new GSparseSymbolic(); *m_symbolic = *matrix.m_symbolic; } // Copy numeric decomposition if it exists if (matrix.m_numeric != NULL) { m_numeric = new GSparseNumeric(); *m_numeric = *matrix.m_numeric; } // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void GMatrixSparse::free_members(void) { // De-allocate only if memory has indeed been allocated if (m_numeric != NULL) delete m_numeric; if (m_symbolic != NULL) delete m_symbolic; if (m_rowinx != NULL) delete [] m_rowinx; // Properly mark members as free m_rowinx = NULL; m_symbolic = NULL; m_numeric = NULL; // Free stack members free_stack_members(); // Return return; } /***********************************************************************//** * @brief Allocate matrix * * @param[in] rows Number of rows. * @param[in] columns Number of columns. * @param[in] elements Number of matrix elements to be physically allocated. * * This is the main constructor code that allocates and initialises memory * for matrix elements. ***************************************************************************/ void GMatrixSparse::alloc_members(const int& rows, const int& columns, const int& elements) { // Continue only if rows and columns are valid if (rows > 0 && columns > 0) { // Free any existing memory if (m_colstart != NULL) delete [] m_colstart; // Allocate column start array. This is the only array that we can // allocate at this time. The other arrays can only be allocated // during filling of the matrix m_colstart = new int[columns+1]; // Store (logical) matrix size m_rows = rows; m_cols = columns; // Initialise column start indices to 0 for (int col = 0; col <= m_cols; ++col) { m_colstart[col] = 0; } // Optionally allocate memory for matrix elements if (elements > 0) { alloc_elements(0, elements); } } // endif: number of elements was positive // Return return; } /***********************************************************************//** * @brief Initialise fill stack * * The fill stack is used to fill the sparse matrix without any prior know- * ledge about the number of location of the non-zero matrix elements. * Values are first filled into the stack and flushed into the sparse * matrix once the stack is full. ***************************************************************************/ void GMatrixSparse::init_stack_members(void) { // Initialise stack members m_stack_max_entries = 0; m_stack_size = 0; m_stack_entries = 0; m_stack_colinx = NULL; m_stack_start = NULL; m_stack_data = NULL; m_stack_rowinx = NULL; m_stack_work = NULL; m_stack_rows = NULL; m_stack_values = NULL; // Return return; } /***********************************************************************//** * @brief Delete fill-stack class members * * Deletes all memory that has been allocated using the init_stack_members() * method. ***************************************************************************/ void GMatrixSparse::free_stack_members(void) { // Free stack members if (m_stack_colinx != NULL) delete [] m_stack_colinx; if (m_stack_start != NULL) delete [] m_stack_start; if (m_stack_data != NULL) delete [] m_stack_data; if (m_stack_rowinx != NULL) delete [] m_stack_rowinx; if (m_stack_work != NULL) delete [] m_stack_work; if (m_stack_rows != NULL) delete [] m_stack_rows; if (m_stack_values != NULL) delete [] m_stack_values; // Properly mark members as free m_stack_colinx = NULL; m_stack_start = NULL; m_stack_data = NULL; m_stack_rowinx = NULL; m_stack_work = NULL; m_stack_rows = NULL; m_stack_values = NULL; // Return return; } /***********************************************************************//** * @brief Determines element index for (row,column) * * @param[in] row Row index. * @param[in] column Column index. * * Returns the index in the compressed array for (row,col). The following * special results exist: * -1: Requested index does not exist in the matrix. * m_elements: Requested index is the pending element. ***************************************************************************/ int GMatrixSparse::get_index(const int& row, const int& column) const { // Initialise element to 'not found' int index = -1; // If we have a pending element then check if this element is requested if (m_fill_val != 0.0) { if (row == m_fill_row && column == m_fill_col) { return m_elements; } } // If requested element is not the pending element then check if it exists // in the matrix. Only if it is found its index is returned. Otherwise // the default index is -1, signalling that the element is absent. if (m_elements > 0) { int* ptr_colstart = m_colstart + column; int i_start = *ptr_colstart++; int i_stop = *ptr_colstart; int* ptr_rowinx = m_rowinx + i_start; for (int i = i_start; i < i_stop; ++i) { int row_test = *ptr_rowinx++; if (row_test == row) { index = i; break; } } } // Return index return index; } /***********************************************************************//** * @brief Fills pending matrix element * * If 'm_fill_val' is non-zero a pending matrix element exists that should * be filled into (row,col)=(m_fill_row,m_fill_col). This routine performs * the filling of the matrix with this element and resets 'm_fill_val' to * zero. This routine allows for element-by-element filling of a sparse * matrix. This is, of course, very time consuming and should in general * be avoided. However, it allows to design a sparse matrix class that * hides the matrix sparsity completely to the user. ***************************************************************************/ void GMatrixSparse::fill_pending(void) { // If we have a pending element then fill it into the matrix if (m_fill_val != 0.0) { // Debugging #if defined(G_DEBUG_SPARSE_PENDING) || defined(G_DEBUG_SPARSE_INSERTION) std::cout << "GMatrixSparse::fill_pending(): pending value " << m_fill_val << " will be filled in location (" << m_fill_row << "," << m_fill_col << ")" << std::endl; #endif // If there are so far no elements in the matrix then append element ... int inx_insert; if (m_elements == 0) { inx_insert = 0; } // ... otherwise search for index to insert else { int* ptr_colstart = m_colstart + m_fill_col; int i_start = *ptr_colstart++; int i_stop = *ptr_colstart; int* ptr_rowinx = m_rowinx + i_start; for (inx_insert = i_start; inx_insert < i_stop; ++inx_insert) { int row_test = *ptr_rowinx++; if (row_test > m_fill_row) { break; } } } // Allocate memory for new element alloc_elements(inx_insert, 1); // Insert element m_data[inx_insert] = m_fill_val; m_rowinx[inx_insert] = m_fill_row; // Update column start indices for (int col = m_fill_col+1; col <= m_cols; ++col) { m_colstart[col] += 1; } // Reset fill value m_fill_val = 0.0; m_fill_row = 0; m_fill_col = 0; // Debugging: show sparse matrix after filling #if defined(G_DEBUG_SPARSE_PENDING) || defined(G_DEBUG_SPARSE_INSERTION) std::cout << " Data: "; for (int i = 0; i < m_elements; ++i) { std::cout << m_data[i] << " "; } std::cout << std::endl << " Row.: "; for (int i = 0; i < m_elements; ++i) { std::cout << m_rowinx[i] << " "; } std::cout << std::endl << " Col.: "; for (int i = 0; i < m_cols+1; ++i) { std::cout << m_colstart[i] << " "; } std::cout << std::endl; #endif } // endif: a pending matrix element was found // Return return; } /***********************************************************************//** * @brief Allocate memory for new matrix elements * * @param[in] start Index of first allocated element. * @param[in] num Number of elements to be allocated. * * Inserts a memory allocation for 'num' elements at the index 'start'. * The new elements are filled with 0.0 and the corresponding row indices * are set to 0. * * NOTE: This method does not take care of updating 'm_colstart'. This has * to be done by the client. ***************************************************************************/ void GMatrixSparse::alloc_elements(int start, const int& num) { // Dump header #if defined(G_DEBUG_SPARSE_MALLOC) std::cout << "GMatrixSparse::alloc_elements(start=" << start << ", num=" << num << ")" << std::endl; std::cout << " Before allocation : " << m_elements << " " << m_alloc << std::endl; #endif // Continue only if we need memory if (num > 0) { // If start is after the end then append memory if (start > m_elements) { start = m_elements; } // Determine the requested new logical size of the matrix int new_size = m_elements + num; // Case A: the requested memory is already available, so just move the // data to make space for new elements and initialise the new cells if (new_size <= m_alloc) { // Move up all elements after index to insert for (int i = m_elements - 1; i >= start; --i) { m_data[i+num] = m_data[i]; m_rowinx[i+num] = m_rowinx[i]; } // Clear new elements (zero content for row 0) for (int i = start; i < start+num; ++i) { m_data[i] = 0.0; m_rowinx[i] = 0; } // Update element counter m_elements += num; } // endif: Case A: memory already existed // Case B: more memory is needed, so allocate it, copy the existing // content, and initialise new cells else { // Make sure that enough memory is allocated int new_propose = m_alloc + m_mem_block; m_alloc = (new_size > new_propose) ? new_size : new_propose; // Allocate memory for new elements double* new_data = new double[m_alloc]; int* new_rowinx = new int[m_alloc]; // Copy all elements before index to insert for (int i = 0; i < start; ++i) { new_data[i] = m_data[i]; new_rowinx[i] = m_rowinx[i]; } // Clear new elements (zero content for row 0) for (int i = start; i < start+num; ++i) { new_data[i] = 0.0; new_rowinx[i] = 0; } // Copy all elements after index to insert for (int i = start; i < m_elements; ++i) { new_data[i+num] = m_data[i]; new_rowinx[i+num] = m_rowinx[i]; } // Delete old memory if (m_data != NULL) delete [] m_data; if (m_rowinx != NULL) delete [] m_rowinx; // Update pointers to new memory and update element counter m_data = new_data; m_rowinx = new_rowinx; m_elements += num; } // endelse: Case B: more memory was needed } // endif: needed new memory // Dump new memory size #if defined(G_DEBUG_SPARSE_MALLOC) std::cout << " After allocation .: " << m_elements << " " << m_alloc << std::endl; #endif // Return return; } /***********************************************************************//** * @brief Free memory for obsolete matrix elements * * @param[in] start Index of first freed element. * @param[in] num Number of elements to be freed. * * Free memory for 'num' elements starting from index 'start'. * * NOTE: This method does not take care of updating 'm_colstart'. This has * to be done by the client. ***************************************************************************/ void GMatrixSparse::free_elements(const int& start, const int& num) { // Dump header #if defined(G_DEBUG_SPARSE_MALLOC) std::cout << "GMatrixSparse::free_elements(start=" << start << ", num=" << num << ")" << std::endl; #endif // Continue only if we need to free memory and if start is within the // range if (num > 0 && start < m_elements) { // Determine the requested new logical size of the matrix int new_size = m_elements - num; // If there are no elements then simply delete all matrix elements ... if (new_size < 1) { if (m_data != NULL) delete [] m_data; if (m_rowinx != NULL) delete [] m_rowinx; m_data = NULL; m_rowinx = NULL; m_elements = 0; m_alloc = 0; } // ... otherwise shrink the array else { // Case A: If at least one entire memory block has been liberated then // physically shrink the matrix array if (m_alloc - new_size > m_mem_block) { // Shrink, but leave a memory block for possible future filling m_alloc = new_size + m_mem_block; // Allocate new memory double* new_data = new double[m_alloc]; int* new_rowinx = new int[m_alloc]; // Copy all elements before the starting index for (int i = 0; i < start; ++i) { new_data[i] = m_data[i]; new_rowinx[i] = m_rowinx[i]; } // Copy all elements after the starting index for (int i = start; i < new_size; ++i) { new_data[i] = m_data[i+num]; new_rowinx[i] = m_rowinx[i+num]; } // Delete old memory if (m_data != NULL) delete [] m_data; if (m_rowinx != NULL) delete [] m_rowinx; // Update pointers to new memory and update element counter m_data = new_data; m_rowinx = new_rowinx; m_elements = new_size; } // endif: Case A: memory shrinkage performed // Case B: we keep the memory and just move the elements else { // Move all elements after the starting index for (int i = start; i < new_size; ++i) { m_data[i] = m_data[i+num]; m_rowinx[i] = m_rowinx[i+num]; } // Update element counter m_elements = new_size; } // endelse: Case B } // endif: array shrinkage needed } // endif: needed new memory // Return return; } /***********************************************************************//** * @brief Remove rows and columns with zeros * * @exception GException::matrix_zero * All matrix elements are zero. * * Remove all rows and columns that contain only zeros from matrix. This * function is needed for compressed matrix factorisation. The resulting * matrix has reduced size (number of rows and columns) and dimensions * (number of elements). Note that the physical memory is not reduced by * the method. ***************************************************************************/ void GMatrixSparse::remove_zero_row_col(void) { // Dump header #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << "GMatrixSparse::remove_zero_row_col" << std::endl; #endif // Fill pending value in matrix fill_pending(); // Select non-zero rows and columns of matrix select_non_zero(); // Stop if there is no compression if (m_rows == m_num_rowsel && m_cols == m_num_colsel) { return; } // Raise exception if all matrix elements are zero if (m_num_rowsel < 1 || m_num_colsel < 1) { throw GException::matrix_zero(G_REMOVE_ZERO); } // Allocate row mapping array int* row_map = new int[m_rows]; // Setup row mapping array that maps original matrix rows into compressed // matrix rows. An entry of -1 indicates that the row should be dropped for (int row = 0; row < m_rows; ++row) { row_map[row] = -1; } for (int c_row = 0; c_row < m_num_rowsel; ++c_row) { row_map[m_rowsel[c_row]] = c_row; } // Initialise pointers to compressed array double* d_data = m_data; int* d_rowinx = m_rowinx; // Initialise column start of first column to zero m_colstart[0] = 0; // Dump column start array #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " before compression (" << m_colstart[m_cols] << "):"; for (int col = 0; col <= m_cols; ++col) std::cout << " " << m_colstart[col]; std::cout << std::endl; #endif // Loop over all columns of compressed matrix for (int c_col = 0; c_col < m_num_colsel; ++c_col) { // Get index range for elements in original matrix int i_start = m_colstart[m_colsel[c_col]]; int i_stop = m_colstart[m_colsel[c_col]+1]; // Initialise number of element counter for the compressed column int num = 0; // Loop over all elements in original column int c_row; for (int i = i_start; i < i_stop; ++i) { if ((c_row = row_map[m_rowinx[i]]) >= 0) { *d_data++ = m_data[i]; *d_rowinx++ = c_row; num++; } } // Update column stop m_colstart[c_col+1] = m_colstart[c_col] + num; } // endfor: looped over all columns of compressed matrix // Dump column start array #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " after compression (" << m_colstart[m_num_colsel] << ") :"; for (int c_col = 0; c_col <= m_num_colsel; ++c_col) std::cout << " " << m_colstart[c_col]; std::cout << std::endl; #endif // Free row mapping array delete [] row_map; // Update matrix size m_rows = m_num_rowsel; m_cols = m_num_colsel; // Return return; } /***********************************************************************//** * @brief Insert zero rows and columns * * @param[in] rows Number of rows. * @param[in] cols Number of columns. * * Insert zero rows and columns into matrix. Since for a sparse matrix * this does not require any allocation of additional memory, the data are * not moved by this function, but the pointers are re-arranged. ***************************************************************************/ void GMatrixSparse::insert_zero_row_col(const int& rows, const int& cols) { // Dump header #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << "GMatrixSparse::insert_zero_row_col(" << rows << "," << cols << ")" << std::endl; #endif // Fill pending value in matrix fill_pending(); // Stop if there is no insertion if (m_rows == rows && m_cols == cols) { return; } // If row selection exists then restore row indices if (m_rowsel != NULL) { for (int i = 0; i < m_elements; ++i) { m_rowinx[i] = m_rowsel[m_rowinx[i]]; } } // Dump column start array #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " before restoration (" << m_colstart[m_num_colsel] << "):"; for (int c_col = 0; c_col <= m_num_colsel; ++c_col) std::cout << " " << m_colstart[c_col]; std::cout << std::endl; #endif // If column selection exists then restore column counters if (m_colsel != NULL) { // Start insertion from the last original column int col_stop = cols - 1; // Loop over all compressed columns for (int c_col = m_num_colsel-1; c_col > 0; --c_col) { int col_start = m_colsel[c_col-1] + 1; int col_value = m_colstart[c_col]; for (int col = col_start; col <= col_stop; ++col) { m_colstart[col] = col_value; } col_stop = col_start - 1; } // Set first columns if they are not yet set for (int col = 0; col <= col_stop; ++col) { m_colstart[col] = 0; } // Restore the number of elements m_colstart[cols] = m_elements; } // Dump column start array #if defined(G_DEBUG_SPARSE_COMPRESSION) std::cout << " after restoration (" << m_colstart[cols] << ") :"; for (int col = 0; col <= cols; ++col) { std::cout << " " << m_colstart[col]; } std::cout << std::endl; #endif // Update matrix size m_rows = rows; m_cols = cols; // Return return; } /***********************************************************************//** * @brief Prepare mix of sparse columns * * @param[in] src1_row Row index array [0...src1_num-1] of first column. * @param[in] src1_num Number of elements in first column. * @param[in] src2_row Row index array [0...src2_num-1] of second column. * @param[in] src2_num Number of elements in second column. * @param[out] num_1 Number of elements only found in column 1. * @param[out] num_2 Number of elements only found in column 2. * @param[out] num_mix Number of elements found in both columns. * * This method prepares the mix of two sparse matrix columns into a single * column. ***************************************************************************/ void GMatrixSparse::mix_column_prepare(const int* src1_row, int src1_num, const int* src2_row, int src2_num, int* num_1, int* num_2, int* num_mix) { // Initialise element counters *num_1 = 0; *num_2 = 0; *num_mix = 0; // Initialise indices and row indices of both columns int inx_1 = 0; // Column 1 element index int inx_2 = 0; // Column 2 element index int row_1 = src1_row[inx_1++]; // Column 1 first row index int row_2 = src2_row[inx_2++]; // Column 2 first row index // Mix elements of both columns while both contain still elements while (inx_1 < src1_num && inx_2 < src2_num) { // Case A: the element exist in both columns if (row_1 == row_2) { row_1 = src1_row[inx_1++]; row_2 = src2_row[inx_2++]; (*num_mix)++; } // Case B: the element exists only in first column else if (row_1 < row_2) { row_1 = src1_row[inx_1++]; (*num_1)++; } // Case C: the element exists only in second column else { row_2 = src2_row[inx_2++]; (*num_2)++; } } // endwhile: mixing // At this point either the first or the second column expired of elements // In the case that there are still elements remaining in the first column we // count them now ... if (inx_1 < src1_num) { *num_1 += (src1_num - inx_1); } // ... or in the case that there are still elements remaining in the second // column we count them now if (inx_2 < src2_num) { *num_2 += (src2_num - inx_2); } // We're done return; } /***********************************************************************//** * @brief Mix of sparse columns * * @param[in] src1_data Data array [0...src1_num-1] of first column. * @param[in] src1_row Row index array [0...src1_num-1] of first column. * @param[in] src1_num Number of elements in first column. * @param[in] src2_data Data array [0...src2_num-1] of second column. * @param[in] src2_row Row index array [0...src2_num-1] of second column. * @param[in] src2_num Number of elements in second column. * @param[in] dst_data Data array [0...dst_num-1] of result column. * @param[in] dst_row Row index array [0...dst_num-1] of result column. * @param[in] dst_num Number of elements in result column. * * This method mixes two sparse matrix columns into a single column. ***************************************************************************/ void GMatrixSparse::mix_column(const double* src1_data, const int* src1_row, int src1_num, const double* src2_data, const int* src2_row, int src2_num, double* dst_data, int* dst_row, int* dst_num) { // Initialise indices and row indices of both columns int inx_1 = 0; // Column 1 element index int inx_2 = 0; // Column 2 element index int inx = 0; // Result column element index int row_1 = src1_row[inx_1]; int row_2 = src2_row[inx_2]; // Mix elements of both columns while both contain still elements while (inx_1 < src1_num && inx_2 < src2_num) { // Case A: the element exists in both columns, so we add up the values if (row_1 == row_2) { dst_data[inx] = src1_data[inx_1++] + src2_data[inx_2++]; dst_row[inx] = row_1; if (inx_1 < src1_num) { row_1 = src1_row[inx_1]; } if (inx_2 < src2_num) { row_2 = src2_row[inx_2]; } } // Case B: the element exists only in first column, so we copy the element // from the first column else if (row_1 < row_2) { dst_data[inx] = src1_data[inx_1++]; dst_row[inx] = row_1; if (inx_1 < src1_num) { row_1 = src1_row[inx_1]; } } // Case C: the element exists only in second column, so we copy the element // from the second column else { dst_data[inx] = src2_data[inx_2++]; dst_row[inx] = row_2; if (inx_2 < src2_num) { row_2 = src2_row[inx_2]; } } // Update the destination index since we added a element inx++; } // endwhile: mixing // At this point either the first or the second column expired of elements // In the case that there are still elements remaining in the first column we // add them now ... for (int i = inx_1; i < src1_num; ++i, ++inx) { dst_data[inx] = src1_data[i]; dst_row[inx] = src1_row[i]; } // ... or in the case that there are still elements remaining in the second // column we add them now for (int i = inx_2; i < src2_num; ++i, ++inx) { dst_data[inx] = src2_data[i]; dst_row[inx] = src2_row[i]; } // Now store the number of columns in the second column *dst_num = inx; // We're done return; } /*========================================================================== = = = Friend functions = = = ==========================================================================*/ /***********************************************************************//** * @brief cs_symperm * * @param[in] matrix Matrix. * @param[in] pinv TBD. * * Returns matrix(p,p) where matrix and matrix(p,p) are symmetric the upper * part stored. ***************************************************************************/ GMatrixSparse cs_symperm(const GMatrixSparse& matrix, const int* pinv) { // Declare loop variables int i, j, p, q, i2, j2; // Assign matrix attributes int n = matrix.m_cols; int* Ap = matrix.m_colstart; int* Ai = matrix.m_rowinx; double* Ax = matrix.m_data; // Allocate result matrix GMatrixSparse C(n, n, Ap[n]); // Allocate and initialise workspace int wrk_size = n; int* wrk_int = new int[wrk_size]; for (i = 0; i < wrk_size; ++i) { wrk_int[i] = 0; } // Assign result matrix attributes int* Cp = C.m_colstart; int* Ci = C.m_rowinx; double* Cx = C.m_data; // Count entries in each column of C for (j = 0; j < n; j++) { // Column j of A is column j2 of C j2 = pinv ? pinv[j] : j; // Loop over entries in column j for (p = Ap[j]; p < Ap[j+1]; p++) { i = Ai [p]; if (i > j) continue; // skip lower triangular part of A i2 = pinv ? pinv[i] : i; // row i of A is row i2 of C wrk_int[G_MAX(i2, j2)]++; // column count of C } } // Compute column pointers of C cs_cumsum(Cp, wrk_int, n); // Loop over all columns of A for (j = 0 ; j < n ; j++) { // Column j of A is column j2 of C j2 = pinv ? pinv[j] : j; // Loop over entries in column j for (p = Ap[j]; p < Ap[j+1]; p++) { i = Ai [p] ; if (i > j) continue; // skip lower triangular part of A i2 = pinv ? pinv[i] : i; // row i of A is row i2 of C Ci[q = wrk_int[G_MAX(i2,j2)]++] = G_MIN(i2,j2); if (Cx) Cx[q] = Ax[p]; } } // Free workspace delete [] wrk_int; // Rectify the number of elements in matrix C C.free_elements(Cp[n], (C.m_elements-Cp[n])); // Return result return C; } /*************************************************************************** * @brief Compute transpose matrix * * @param[in] matrix Matrix. * @param[in] values Flag that signals if values should be copied. * * The flag 'values' allows to avoid copying the actual data values. This * allows to perform a logical matrix transposition, as needed by the * symbolic matrix analysis class. * * Note that this method does not support pending elements (they have to be * filled before). ***************************************************************************/ GMatrixSparse cs_transpose(const GMatrixSparse& matrix, int values) { // Declare and allocate result matrix GMatrixSparse result(matrix.m_cols, matrix.m_rows, matrix.m_elements); // Allocate and initialise workspace int wrk_size = matrix.m_rows; int* wrk_int = new int[wrk_size]; for (int i = 0; i < wrk_size; ++i) { wrk_int[i] = 0; } // Setup the number of non-zero elements in each row // for (p = 0 ; p < Ap [n] ; p++) w [Ai [p]]++ ; // Ap[n] = m.m_colstart[col] // n = m.m_cols // Ai[p] = m.m_rowinx[p] for (int p = 0; p < matrix.m_colstart[matrix.m_cols]; ++p) { wrk_int[matrix.m_rowinx[p]]++; } // Set row pointers. To use a GSparseSymbolic function we have to // allocate and object (but this does not take memory) cs_cumsum(result.m_colstart, wrk_int, matrix.m_rows); // Case A: Normal transponse, including assignment of values if (values) { for (int col = 0; col < matrix.m_cols; ++col) { for (int p = matrix.m_colstart[col]; p < matrix.m_colstart[col+1] ; ++p) { int i = wrk_int[matrix.m_rowinx[p]]++; result.m_rowinx[i] = col; result.m_data[i] = matrix.m_data[p] ; } } } // Case B: Logical transponse, no assignment of values is performed else { for (int col = 0; col < matrix.m_cols; ++col) { for (int p = matrix.m_colstart[col]; p < matrix.m_colstart[col+1] ; ++p) { result.m_rowinx[wrk_int[matrix.m_rowinx[p]]++] = col; } } } // Free workspace delete [] wrk_int; // Return transponse matrix return result; } /***********************************************************************//** * @brief cs_cumsum * * @param[out] p Integer array (n+1 elements). * @param[in] c Integer array (n elements). * @param[in] n Number of elements. * * Evaluate p[0..n] = cumulative sum of c[0..n-1] ***************************************************************************/ double cs_cumsum(int* p, int* c, int n) { // Signal error if one of the input pointers is NULL if (!p || !c) return (-1); // Initialise sums (integer and double) int nz = 0; double nz2 = 0.0; // Evaluate p[0..n] = cumulative sum of c[0..n-1] for (int i = 0; i < n; ++i) { p[i] = nz ; nz += c[i]; nz2 += c[i]; // also in double to avoid int overflow c[i] = p[i]; // also copy p[0..n-1] back into c[0..n-1] } p[n] = nz ; // Return cumulative sum of c[0..n-1] return nz2; }