/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* This file is part of the program and library */ /* PaPILO --- Parallel Presolve for Integer and Linear Optimization */ /* */ /* Copyright (C) 2020-2022 Konrad-Zuse-Zentrum */ /* fuer Informationstechnik Berlin */ /* */ /* This program is free software: you can redistribute it and/or modify */ /* it under the terms of the GNU Lesser 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 Lesser General Public License for more details. */ /* */ /* You should have received a copy of the GNU Lesser General Public License */ /* along with this program. If not, see . */ /* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef _PAPILO_CORE_SPARSE_STORAGE_HPP_ #define _PAPILO_CORE_SPARSE_STORAGE_HPP_ #include "papilo/misc/MultiPrecision.hpp" #include "papilo/misc/Vec.hpp" #include "papilo/external/pdqsort/pdqsort.h" #include #include #include #include #include namespace papilo { /// type definition for a non-zero entry in triplet format template using Triplet = std::tuple; // forward declaration of constraint matrix to declare the constraint matrix a // friend class of SparseStorage template class ConstraintMatrix; struct IndexRange { int start; int end; IndexRange() : start( -1 ), end( -1 ){}; template void serialize( Archive& ar, const unsigned int version ) { ar& start; ar& end; } }; /// Sparse storage class to store a matrix in modified CSR format /// which includes a start and end for each row and allows for some free /// space between the rows which is useful if the matrix may be altered template class SparseStorage { friend class ConstraintMatrix; public: static constexpr double DEFAULT_SPARE_RATIO = 2.0; static constexpr int DEFAULT_MIN_INTER_ROW_SPACE = 4; SparseStorage() = default; SparseStorage( Vec> entries, int nRows_in, int nCols_in, bool sorted = false, double spareRatio = DEFAULT_SPARE_RATIO, int minInterRowSpace = DEFAULT_MIN_INTER_ROW_SPACE ); SparseStorage( REAL* values_in, int* rowstart_in, int* columns_in, int nRows_in, int nCols_in, int nnz_in, double spareRatio = DEFAULT_SPARE_RATIO, int minInterRowSpace = DEFAULT_MIN_INTER_ROW_SPACE ); SparseStorage( int nRows_in, int nCols_in, int nnz_in, double spareRatio, int minInterRowSpace ); SparseStorage getTranspose() const; Vec compress( const Vec& rowsize, const Vec& colsize, bool full = false ); int getNRows() const { return nRows; } bool shiftRows( const int* rowinds, int ninds, int maxshiftperrow, const Vec& requiredSpareSpace ); int getNCols() const { return nCols; } int getNnz() const { return nnz; } int& getNnz() { return nnz; } int getNAlloc() const { return nAlloc; } const REAL* getValues() const { return values.data(); } REAL* getValues() { return values.data(); } const Vec& getValuesVec() const { return values; } const IndexRange* getRowRanges() const { return rowranges.data(); } const Vec& getRowRangesVec() const { return rowranges; } IndexRange* getRowRanges() { return rowranges.data(); } const int* getColumns() const { return columns.data(); } int* getColumns() { return columns.data(); } const Vec& getColumnsVec() const { return columns; } Vec getRowStarts() const; // function to change existing coefficients in row. Must not be called with // coefficients that are currently not in the row. Changes must be given in // sorted order. template int changeRowInplace( int row, HasNext&& hasNext, GetNext&& getNext, CoeffChanged&& coeffChanged ) { int i = rowranges[row].start; int j = 0; while( hasNext() ) { int col; REAL newval; std::tie( col, newval ) = getNext(); while( col != columns[i] ) { if( j != 0 ) { columns[i - j] = columns[i]; values[i - j] = std::move( values[i] ); } ++i; } coeffChanged( row, col, values[i], newval ); if( newval == 0 ) { ++j; } else if( j != 0 ) { columns[i - j] = columns[i]; values[i - j] = std::move( newval ); } else { values[i] = std::move( newval ); } ++i; } if( j != 0 ) { while( i != rowranges[row].end ) { columns[i - j] = columns[i]; values[i - j] = std::move( values[i] ); ++i; } rowranges[row].end -= j; nnz -= j; } return rowranges[row].end - rowranges[row].start; } /// change coefficients inside the given row. The details of how to change /// the row are templatized to allow for a generalized use of this function. /// The iteration of the changes is controlled by the Iter template type and /// the GetCol and GetVal callbacks to retrieve the column and the value out /// of that iterator type. If a matching support has been found the MergeVals /// callback is called with the first argument being the rows current value. /// The value returned by MergeVals is used as the new coefficient. This /// allows to use the function for setting the coefficients to values, or to /// add values to the coefficients. If a coefficient was changed the caller /// is informed about the old and the new coefficient via the CoeffChanged /// callback. /// Returns the new size of the row. template int changeRow( int row, Iter it, Iter itend, GetCol&& getCol, GetVal&& getVal, MergeVals&& mergeVals, CoeffChanged&& coeffChanged, Vec& valbuffer, Vec& indbuffer ) { auto rowmaxlen = rowranges[row].end - rowranges[row].start + ( itend - it ); assert( valbuffer.empty() ); assert( indbuffer.empty() ); valbuffer.reserve( rowmaxlen ); indbuffer.reserve( rowmaxlen ); int i = rowranges[row].start; while( i != rowranges[row].end && it != itend ) { int col = getCol( it ); if( columns[i] == col ) { REAL newval = mergeVals( values[i], getVal( it ) ); coeffChanged( row, col, values[i], newval ); if( newval != 0.0 ) { indbuffer.push_back( col ); valbuffer.push_back( std::move( newval ) ); } ++i; ++it; } else if( columns[i] < col ) { indbuffer.push_back( columns[i] ); valbuffer.push_back( values[i] ); ++i; } else { REAL newval = getVal( it ); coeffChanged( row, col, 0.0, newval ); indbuffer.push_back( col ); valbuffer.push_back( std::move( newval ) ); ++it; } } if( i != rowranges[row].end ) { indbuffer.insert( indbuffer.end(), &columns[i], &columns[rowranges[row].end] ); valbuffer.insert( valbuffer.end(), &values[i], &values[rowranges[row].end] ); } else { while( it != itend ) { int col = getCol( it ); REAL newval = getVal( it ); coeffChanged( row, col, 0.0, newval ); indbuffer.push_back( col ); valbuffer.push_back( std::move( newval ) ); ++it; } } // copy over values from buffer assert( valbuffer.size() == indbuffer.size() ); assert( std::is_sorted( indbuffer.begin(), indbuffer.end() ) ); int newsize = static_cast( indbuffer.size() ); assert( newsize <= rowranges[row + 1].start - rowranges[row].start ); nnz = nnz - rowranges[row].end + rowranges[row].start + newsize; // copy over values from buffer std::copy_n( valbuffer.data(), newsize, &values[rowranges[row].start] ); std::memcpy( &columns[rowranges[row].start], indbuffer.data(), sizeof( int ) * newsize ); rowranges[row].end = rowranges[row].start + newsize; valbuffer.clear(); indbuffer.clear(); return newsize; } template void serialize( Archive& ar, const unsigned int version ) { ar& nRows; ar& nCols; ar& nnz; ar& nAlloc; ar& spareRatio; ar& minInterRowSpace; if( Archive::is_loading::value ) { assert( values.empty() ); assert( rowranges.empty() ); assert( columns.empty() ); rowranges.resize( nRows + 1 ); values.resize( nAlloc ); columns.resize( nAlloc ); } for( int i = 0; i != nRows + 1; ++i ) ar& rowranges[i]; for( int i = 0; i != nRows; ++i ) { for( int j = rowranges[i].start; j != rowranges[i].end; ++j ) { ar& values[j]; ar& columns[j]; } } } int computeRowAlloc( int rowsize ) const { return static_cast( rowsize * spareRatio ) + minInterRowSpace; } private: int computeNAlloc() const { return static_cast( nnz * spareRatio ) + nRows * minInterRowSpace; } Vec values; Vec rowranges; Vec columns; int nRows = -1; int nCols = -1; int nnz = -1; int nAlloc = -1; double spareRatio = 0.0; int minInterRowSpace = 0; }; #ifdef PAPILO_USE_EXTERN_TEMPLATES extern template class SparseStorage; extern template class SparseStorage; extern template class SparseStorage; #endif template SparseStorage::SparseStorage( Vec> entries, int nRows_in, int nCols_in, bool sorted, double spareRatio_, int minInterRowSpace_ ) : nRows( nRows_in ), nCols( nCols_in ), spareRatio( spareRatio_ ), minInterRowSpace( minInterRowSpace_ ) { assert( spareRatio_ >= 0.0 ); assert( !sorted || std::is_sorted( entries.begin(), entries.end() ) ); if( !sorted ) pdqsort( entries.begin(), entries.end() ); nnz = entries.size(); nAlloc = computeNAlloc(); rowranges.resize( nRows + 1 ); values.resize( nAlloc ); columns.resize( nAlloc ); rowranges[0].start = 0; int idx = 0; int current_row = 0; for( auto entry : entries ) { int row; int col; REAL value; std::tie( row, col, value ) = entry; assert( row >= 0 && row < nRows ); assert( col >= 0 && col < nCols ); if( row != current_row ) { assert( row > current_row ); rowranges[current_row].end = idx; idx = rowranges[current_row].start + computeRowAlloc( rowranges[current_row].end - rowranges[current_row].start ); assert( idx > rowranges[current_row].end ); rowranges[current_row + 1].start = idx; // there might be empty rows for( int r = current_row + 1; r < row; r++ ) { rowranges[r].end = idx; rowranges[r + 1].start = idx; } current_row = row; } if( value != 0 ) { assert( idx < nAlloc ); values[idx] = value; columns[idx++] = col; } else --nnz; } rowranges[current_row].end = idx; idx = rowranges[current_row].start + computeRowAlloc( rowranges[current_row].end - rowranges[current_row].start ); assert( idx > rowranges[current_row].end ); assert( idx <= nAlloc ); rowranges[current_row + 1].start = idx; // there might be empty rows at the end for( int r = current_row + 1; r < nRows; r++ ) { rowranges[r].end = idx; rowranges[r + 1].start = idx; } rowranges[nRows].end = idx; } template SparseStorage::SparseStorage( int nRows_in, int nCols_in, int nnz_in, double spareRatio_, int minInterRowSpace_ ) : nRows( nRows_in ), nCols( nCols_in ), nnz( nnz_in ), spareRatio( spareRatio_ ), minInterRowSpace( minInterRowSpace_ ) { nAlloc = computeNAlloc(); assert( spareRatio_ >= 1.0 ); rowranges.resize( nRows + 1 ); values.resize( nAlloc ); columns.resize( nAlloc ); rowranges[nRows].start = nAlloc; rowranges[nRows].end = nAlloc; } template SparseStorage::SparseStorage( REAL* values_in, int* rowstart_in, int* columns_in, int nRows_in, int nCols_in, int nnz_in, double spareRatio_in, int minInterRowSpace_in ) : nRows( nRows_in ), nCols( nCols_in ), nnz( nnz_in ), spareRatio( spareRatio_in ), minInterRowSpace( minInterRowSpace_in ) { assert( nRows_in >= 0 && nnz_in >= 0 && spareRatio >= 1.0 ); assert( rowstart_in ); // compute length of new storage nAlloc = computeNAlloc(); columns.resize( nAlloc ); values.resize( nAlloc ); rowranges.resize( nRows + 1 ); // build storage int shift = 0; for( int r = 0; r < nRows; r++ ) { rowranges[r].start = rowstart_in[r] + shift; for( int j = rowstart_in[r]; j < rowstart_in[r + 1]; j++ ) { if( values_in[j] != REAL{ 0.0 } ) { assert( j + shift >= 0 ); values[j + shift] = values_in[j]; columns[j + shift] = columns_in[j]; } else { shift--; } } rowranges[r].end = rowstart_in[r + 1] + shift; const int rowsize = rowranges[r].end - rowranges[r].start; const int rowalloc = computeRowAlloc( rowsize ); shift += rowalloc - rowsize; } assert( nRows == 0 ); rowranges[nRows].start = rowstart_in[nRows] + shift; rowranges[nRows].end = rowranges[nRows].start; } template SparseStorage SparseStorage::getTranspose() const { // if( nCols <= 0 ) // return SparseStorage{}; // compute nnz of each row of At (column of A) Vec w( size_t( nCols ), 0 ); for( int r = 0; r < nRows; r++ ) { const int start = rowranges[r].start; const int end = rowranges[r].end; for( int j = start; j < end; j++ ) { assert( values[j] != REAL{ 0.0 } ); w[columns[j]]++; } } assert( spareRatio >= 1.0 ); SparseStorage transpose{ nCols, nRows, nnz, spareRatio, minInterRowSpace }; // set row ranges of transpose transpose.rowranges[0].start = 0; for( int i = 1; i <= nCols; i++ ) { const int oldstart = transpose.rowranges[i - 1].start; const int oldend = oldstart + w[i - 1]; assert( oldend >= oldstart ); transpose.rowranges[i - 1].end = oldend; transpose.rowranges[i].start = oldstart + transpose.computeRowAlloc( w[i - 1] ); w[i - 1] = oldstart; } transpose.rowranges[nCols].start = transpose.nAlloc; transpose.rowranges[nCols].end = transpose.nAlloc; // fill values and columns arrays of transpose for( int r = 0; r < nRows; r++ ) { const int start = rowranges[r].start; const int end = rowranges[r].end; for( int j = start; j < end; j++ ) { const int idx = w[columns[j]]; assert( idx < transpose.nAlloc ); transpose.values[idx] = values[j]; transpose.columns[idx] = r; w[columns[j]] = idx + 1; } } return transpose; } template Vec SparseStorage::compress( const Vec& rowsize, const Vec& colsize, bool full ) { if( full ) { spareRatio = 1.0; minInterRowSpace = 0; } // now create and fill storage Vec colsmap( static_cast( nCols ) ); if( nCols > 0 ) { int colcount = 0; for( int i = 0; i < nCols; i++ ) { if( colsize[i] >= 0 ) colsmap[i] = colcount++; else colsmap[i] = -1; } nCols = colcount; } if( nRows > 0 ) { int offset = 0; int rowcount = 0; for( int r = 0; r < nRows; r++ ) { const int start = rowranges[r].start; const int end = rowranges[r].end; const int rowalloc = rowranges[r + 1].start - start; // empty row? if( rowsize[r] == -1 ) offset += rowalloc; else { rowranges[rowcount].start = start; rowranges[rowcount].end = end; if( offset > 0 ) { // move values and columns assert( start >= offset ); std::move( &values[start], &values[end], &values[start - offset] ); std::move( &columns[start], &columns[end], &columns[start - offset] ); rowranges[rowcount].start -= offset; rowranges[rowcount].end -= offset; } offset = std::max( offset + rowalloc - computeRowAlloc( end - start ), 0 ); ++rowcount; } assert( offset <= nAlloc ); } rowranges[rowcount].start = rowranges[nRows].start - offset; rowranges[rowcount].end = rowranges[nRows].end - offset; nRows = rowcount; nAlloc = nAlloc - offset; assert( nAlloc >= 0 ); rowranges.resize( nRows + 1 ); values.resize( nAlloc ); columns.resize( nAlloc ); if( full ) { rowranges.shrink_to_fit(); values.shrink_to_fit(); columns.shrink_to_fit(); } for( int r = 0; r < nRows; r++ ) { const int start = rowranges[r].start; const int end = rowranges[r].end; for( int j = start; j < end; j++ ) { assert( columns[j] >= 0 ); assert( columns[j] < static_cast( colsmap.size() ) ); columns[j] = colsmap[columns[j]]; assert( columns[j] >= 0 ); assert( columns[j] < nCols ); } } } return colsmap; } template bool SparseStorage::shiftRows( const int* rowinds, int ninds, int maxshiftperrow, const Vec& requiredSpareSpace ) { assert( ninds > 0 ); assert( rowinds != nullptr ); assert( (int) requiredSpareSpace.size() == ninds ); assert( std::is_sorted( rowinds, rowinds + ninds ) ); for( int i = 0; i != ninds; ++i ) { const int row = rowinds[i]; int missingspace = requiredSpareSpace[i] - ( rowranges[row + 1].start - rowranges[row].end ); if( missingspace > 0 ) { int leftbound = i == 0 ? 0 : rowinds[i - 1] + 1; int rightbound = i == ninds - 1 ? nRows : rowinds[i + 1]; int l = row; int r = row + 1; int lastshiftleft = 0; int lastshiftright = 0; int maxshift = maxshiftperrow; while( missingspace > 0 ) { if( l > leftbound && r < rightbound ) { int nspaceleft = std::min( missingspace, rowranges[l].start - rowranges[l - 1].end ); int nspaceright = std::min( missingspace, rowranges[r + 1].start - rowranges[r].end ); int nshiftleft = rowranges[l].end - rowranges[l].start; int nshiftright = rowranges[r].end - rowranges[r].start; bool goleft; if( nshiftleft == 0 ) goleft = true; else if( nshiftright == 0 ) goleft = false; else if( nshiftleft <= maxshift && nspaceleft / (double)nshiftleft >= nspaceright / (double)nshiftright ) goleft = true; else if( nshiftright <= maxshift ) goleft = false; else return false; // take direction that gives the most space per shifted // nonzero if( goleft ) { maxshift -= nshiftleft; if( nspaceleft != 0 ) { lastshiftleft = nspaceleft; missingspace -= lastshiftleft; } --l; } else { maxshift -= nshiftright; if( nspaceright != 0 ) { lastshiftright = nspaceright; missingspace -= lastshiftright; } ++r; } } else if( l > leftbound && rowranges[l].end - rowranges[l].start <= maxshift ) { maxshift -= rowranges[l].end - rowranges[l].start; lastshiftleft = std::min( missingspace, rowranges[l].start - rowranges[l - 1].end ); missingspace -= lastshiftleft; --l; } else if( r < rightbound && rowranges[r].end - rowranges[r].start <= maxshift ) { maxshift -= rowranges[r].end - rowranges[r].start; lastshiftright = std::min( missingspace, rowranges[r + 1].start - rowranges[r].end ); missingspace -= lastshiftright; ++r; } else return false; } assert( missingspace == 0 && ( lastshiftleft > 0 || lastshiftright > 0 ) ); if( lastshiftleft > 0 ) { do { ++l; // skip possibly empty rows that did not increase the available // space } while( rowranges[l].start == rowranges[l - 1].end ); REAL* valsout = &values[rowranges[l].start - lastshiftleft]; int* colsout = &columns[rowranges[l].start - lastshiftleft]; assert( rowranges[l - 1].end <= rowranges[l].start - lastshiftleft ); while( l <= row ) { int shift = &values[rowranges[l].start] - valsout; #ifndef NDEBUG Vec tmpvals; Vec tmpinds; tmpvals.insert( tmpvals.end(), &values[rowranges[l].start], &values[rowranges[l].end] ); tmpinds.insert( tmpinds.end(), &columns[rowranges[l].start], &columns[rowranges[l].end] ); #endif if( rowranges[l].start != rowranges[l].end ) { valsout = std::move( &values[rowranges[l].start], &values[rowranges[l].end], valsout ); colsout = std::move( &columns[rowranges[l].start], &columns[rowranges[l].end], colsout ); } rowranges[l].start -= shift; rowranges[l].end -= shift; assert( &columns[rowranges[l].end] == colsout ); assert( &values[rowranges[l].end] == valsout ); assert( rowranges[l - 1].end <= rowranges[l].start ); assert( rowranges[l].end - rowranges[l].start == (int) tmpvals.size() ); assert( std::equal( tmpvals.begin(), tmpvals.end(), &values[rowranges[l].start] ) ); assert( std::equal( tmpinds.begin(), tmpinds.end(), &columns[rowranges[l].start] ) ); ++l; } } if( lastshiftright > 0 ) { do { --r; // skip possibly empty rows that did not increase the available // space } while( rowranges[r].end == rowranges[r + 1].start ); REAL* valsout = &values[rowranges[r].end + lastshiftright]; int* colsout = &columns[rowranges[r].end + lastshiftright]; assert( rowranges[r + 1].start >= rowranges[r].end + lastshiftright ); while( r > row ) { int shift = valsout - &values[rowranges[r].end]; #ifndef NDEBUG Vec tmpvals; Vec tmpinds; tmpvals.insert( tmpvals.end(), &values[rowranges[r].start], &values[rowranges[r].end] ); tmpinds.insert( tmpinds.end(), &columns[rowranges[r].start], &columns[rowranges[r].end] ); #endif if( rowranges[r].start != rowranges[r].end ) { valsout = std::move_backward( &values[rowranges[r].start], &values[rowranges[r].end], valsout ); colsout = std::move_backward( &columns[rowranges[r].start], &columns[rowranges[r].end], colsout ); } rowranges[r].start += shift; rowranges[r].end += shift; assert( &columns[rowranges[r].start] == colsout ); assert( &values[rowranges[r].start] == valsout ); assert( rowranges[r + 1].start >= rowranges[r].end ); assert( rowranges[r].end - rowranges[r].start == (int) tmpvals.size() ); assert( std::equal( tmpvals.begin(), tmpvals.end(), &values[rowranges[r].start] ) ); assert( std::equal( tmpinds.begin(), tmpinds.end(), &columns[rowranges[r].start] ) ); --r; } } assert( rowranges[row + 1].start - rowranges[row].end >= requiredSpareSpace[i] ); } // space should suffice now, either because it already did, or because it // was modified assert( requiredSpareSpace[i] <= ( rowranges[row + 1].start - rowranges[row].end ) ); } return true; } template Vec SparseStorage::getRowStarts() const { int size = getNRows() + 1; Vec colStart( size ); unsigned int i; for( i = 0; i < colStart.size() - 1; ++i ) { colStart[i] = rowranges[i].start; assert( rowranges[i].end == rowranges[i + 1].start ); } colStart[i] = rowranges[i].end; return colStart; } } // namespace papilo #endif