/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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_MISC_DEPENDENT_ROWS_HPP_ #define _PAPILO_MISC_DEPENDENT_ROWS_HPP_ #include "papilo/Config.hpp" #ifdef PAPILO_HAVE_LUSOL extern "C" { #include "papilo/external/lusol/clusol.h" } #endif #include "papilo/core/ConstraintMatrix.hpp" #include "papilo/core/SparseStorage.hpp" #include "papilo/misc/Vec.hpp" #include #include #include namespace papilo { template class DependentRows { public: #ifdef PAPILO_HAVE_LUSOL constexpr static bool Enabled = true; #else constexpr static bool Enabled = false; #endif DependentRows( int64_t nrows_, int64_t ncols_, int64_t maxnnz_ ) { this->nrows = nrows_; this->ncols = ncols_ + 1; mat.reserve( maxnnz_ ); } void reset( int64_t nrows_, int64_t ncols_, int64_t maxnnz ) { this->nrows = nrows_; this->ncols = ncols_ + 1; mat.clear(); mat.reserve( maxnnz ); } void addRow( int rowIndex, SparseVectorView rowValues, REAL side ) { int len = rowValues.getLength(); const int* inds = rowValues.getIndices(); const REAL* vals = rowValues.getValues(); mat.startBadge(); for( int i = 0; i != len; ++i ) mat.addBadgeEntry( rowIndex, inds[i], vals[i] ); if( side != 0 ) mat.addBadgeEntry( rowIndex, this->ncols - 1, side ); mat.finishBadge(); } struct PivotCandidate { int idx; int colsize; int rowsize; bool operator<( const PivotCandidate& other ) const { int trivial = colsize == 1 || rowsize == 1; int othertrivial = other.colsize == 1 || other.rowsize == 1; return std::make_tuple( trivial, colsize, rowsize ) > std::make_tuple( othertrivial, other.colsize, other.rowsize ); } }; struct LUSOL_Input { int64_t nrows; int64_t ncols; Vec A; Vec indc; Vec indr; void setSize( int64_t _nrows, int64_t _ncols, int64_t nnz ) { this->nrows = _nrows; this->ncols = _ncols; // preallocate enough storage for LUSOL to work int64_t alloc = std::max( { 8 * nnz, 32 * nrows, 32 * ncols, int64_t{ 10000 } } ); A.reserve( alloc ); indc.reserve( alloc ); indr.reserve( alloc ); } void addNnz( int64_t i, int64_t j, double val ) { A.push_back( double( val ) ); indc.push_back( i ); indr.push_back( j ); } void applyScaling() { // apply equilibrium scaling algorithm for remaining matrix Vec rowmax( nrows ); Vec rowmin( nrows ); Vec colmax( ncols ); Vec colmin( ncols ); for( int i = 0; i < (int) A.size(); ++i ) { double absai = abs( A[i] ); rowmax[indc[i] - 1] = std::max( rowmax[indc[i] - 1], absai ); colmax[indr[i] - 1] = std::max( colmax[indr[i] - 1], absai ); if( rowmin[indc[i] - 1] == 0 || absai < rowmin[indc[i] - 1] ) rowmin[indc[i] - 1] = absai; if( colmin[indr[i] - 1] == 0 || absai < colmin[indr[i] - 1] ) colmin[indr[i] - 1] = absai; } double maxrowratio = 1; for( int i = 0; i < (int) rowmax.size(); ++i ) maxrowratio = std::max( maxrowratio, rowmax[i] / rowmin[i] ); double maxcolratio = 1; for( int i = 0; i < (int) colmax.size(); ++i ) maxcolratio = std::max( maxcolratio, colmax[i] / colmin[i] ); if( maxrowratio < maxcolratio ) { for( int i = 0; i < (int) A.size(); ++i ) A[i] = ( A[i] / rowmax[indc[i] - 1] ); for( int i = 0; i < (int) colmax.size(); ++i ) colmax[i] = 0; for( int i = 0; i < (int) A.size(); ++i ) colmax[indr[i] - 1] = std::max( colmax[indr[i] - 1], abs( A[i] ) ); for( int i = 0; i < (int) A.size(); ++i ) A[i] = ( A[i] / colmax[indr[i] - 1] ); } else { for( int i = 0; i < (int) A.size(); ++i ) A[i] = ( A[i] / colmax[indr[i] - 1] ); for( int i = 0; i < (int) rowmax.size(); ++i ) rowmax[i] = 0; for( int i = 0; i < (int) A.size(); ++i ) rowmax[indc[i] - 1] = std::max( rowmax[indc[i] - 1], abs( A[i] ) ); for( int i = 0; i < (int) A.size(); ++i ) A[i] = ( A[i] / rowmax[indc[i] - 1] ); } } void computeDependentColumns( Vec& colmapping ) { #ifdef PAPILO_HAVE_LUSOL // is passed in : int64_t nelem; std::array luparm; std::array parmlu; Vec p( nrows ); // allocate for nrows (=m) Vec lenr( nrows ); // allocate for nrows Vec locr( nrows ); // allocate for nrows Vec iqloc( nrows ); // allocate for nrows Vec ipinv( nrows ); // allocate for nrows Vec q( ncols ); // allocate for ncols (=n) Vec lenc( ncols ); // allocate for ncols Vec locc( ncols ); // allocate for ncols Vec iploc( ncols ); // allocate for ncols Vec iqinv( ncols ); // allocate for ncols Vec w( ncols ); int64_t inform; double factol = 2.5; // Stability tolerance luparm[0] = 6; // File number for printed messages luparm[1] = -1; // Print level. >= 0 to get singularity info. // >=10 to get more LU statistics. // >=50 to get info on each pivot. luparm[2] = 5; // maxcol luparm[5] = 1; // Threshold Pivoting: 0 = TPP, 1 = TRP, 2 = TCP luparm[7] = 0; // keepLU parmlu[0] = factol; // Ltol1: max |Lij| during Factor parmlu[1] = factol; // Ltol2: max |Lij| during Update parmlu[2] = 3.0e-13; // small: drop tolerance parmlu[3] = 3.7e-11; // Utol1: absolute tol for small Uii parmlu[4] = 3.7e-11; // Utol2: relative tol for small Uii parmlu[5] = 3.0; // Uspace: parmlu[6] = 0.3; // dens1 parmlu[7] = 0.5; // dens2 int64_t nelem = A.size(); int64_t lena = A.capacity(); clu1fac( &nrows, &ncols, &nelem, &lena, luparm.data(), parmlu.data(), A.data(), indc.data(), indr.data(), p.data(), q.data(), lenc.data(), lenr.data(), locc.data(), locr.data(), iploc.data(), iqloc.data(), ipinv.data(), iqinv.data(), w.data(), &inform ); if( ( inform == 0 || inform == 1 ) && luparm[10] > 0 ) { for( int i = 0; i < ncols; ++i ) { if( w[i] > 0 ) colmapping[i] = -1; } colmapping.erase( std::remove( colmapping.begin(), colmapping.end(), -1 ), colmapping.end() ); assert( colmapping.size() == luparm[10] ); return; } #endif colmapping.clear(); } }; int64_t preprocessLUFac( const Message& msg, const Num& num, LUSOL_Input& lusolInput, Vec& rowmapping ) { SmallVec stack; SmallVec stack2; Vec rowsize( nrows ); Vec colsize( ncols ); for( int i = 1; i != (int) mat.entries.size(); ++i ) { assert( mat.entries[i].row < nrows ); assert( mat.entries[i].col < ncols ); assert( mat.entries[i].val != 0 ); ++rowsize[mat.entries[i].row]; ++colsize[mat.entries[i].col]; } #ifndef NDEBUG for( int i = 0; i != (int) colsize.size(); ++i ) { int tmpcolsize = 0; for( auto coliter = mat.template beginStart( stack, -1, i ); coliter->col == i; coliter = mat.template next( stack ) ) ++tmpcolsize; assert( tmpcolsize == colsize[i] ); } for( int i = 0; i != (int) rowsize.size(); ++i ) { int tmprowsize = 0; for( auto rowiter = mat.template beginStart( stack, i, -1 ); rowiter->row == i; rowiter = mat.template next( stack ) ) ++tmprowsize; assert( tmprowsize == rowsize[i] ); } #endif boost::heap::d_ary_heap, boost::heap::arity<4>> heap; heap.reserve( 2 * mat.getNnz() ); for( int i = 1; i != (int) mat.entries.size(); ++i ) { PivotCandidate p{ i, colsize[mat.entries[i].col], rowsize[mat.entries[i].row] }; heap.push( p ); } int nremoved = 0; REAL minpivot = num.getFeasTol() * 1e4; while( !heap.empty() ) { PivotCandidate pivot = heap.top(); heap.pop(); MatrixEntry* pivotentry = &mat.entries[pivot.idx]; // if pivot is zero we skip it if( pivotentry->val == 0 ) continue; bool trivialpivot = colsize[pivotentry->col] == 1 || rowsize[pivotentry->row] == 1; // always accept trivial pivots, but if the pivot is non-trivial do // some additional checks if( !trivialpivot ) { if( abs( pivotentry->val ) < minpivot ) continue; // if the pivot was altered while in the heap and is not as good as // it was when added, then we add it to the heap for later if( pivot.colsize < colsize[pivotentry->col] || pivot.rowsize < rowsize[pivotentry->row] ) { pivot.colsize = colsize[pivotentry->col]; pivot.rowsize = rowsize[pivotentry->row]; heap.push( pivot ); continue; } // only treat columns of at most size 2 if( pivot.colsize > 2 ) break; } assert( colsize[pivotentry->col] > 0 ); assert( rowsize[pivotentry->row] > 0 ); nremoved++; int col = pivotentry->col; int pivotrow = pivotentry->row; REAL pivotrowscale = -1.0 / pivotentry->val; if( !trivialpivot ) { for( const MatrixEntry* rowiter = mat.template beginStart( stack, pivotrow, -1 ); rowiter->row == pivotrow; rowiter = mat.template next( stack ) ) { if( rowiter->col == col || rowiter->val == 0 ) continue; REAL pivrowval = rowiter->val * pivotrowscale; for( const MatrixEntry* coliter = mat.template beginStart( stack2, -1, col ); coliter->col == col; coliter = mat.template next( stack2 ) ) { if( coliter->row == pivotrow || coliter->val == 0 ) continue; const MatrixEntry* rowentry = mat.template findEntry( coliter->row, rowiter->col ); REAL delta = pivrowval * coliter->val; if( rowentry != nullptr ) { REAL newval = rowentry->val + delta; if( rowentry->val == 0 ) { ++colsize[rowentry->col]; ++rowsize[rowentry->row]; } assert( rowiter->col != col || ( num.isZero( newval ) && rowentry->val != 0 ) ); if( num.isZero( newval ) ) { const_cast*>( rowentry )->val = 0; --colsize[rowentry->col]; --rowsize[rowentry->row]; } else { const_cast*>( rowentry )->val = newval; int rowentryidx = ( rowentry - &mat.entries[0] ); assert( rowentryidx > 0 && rowentryidx < (int) mat.entries.size() && rowentry == &mat.entries[rowentryidx] ); heap.push( PivotCandidate{ rowentryidx, colsize[rowentry->col], rowsize[rowentry->row] } ); } } else { ++colsize[rowiter->col]; ++rowsize[coliter->row]; int rowentryidx = int( mat.entries.size() ); heap.push( PivotCandidate{ rowentryidx, colsize[rowiter->col], rowsize[coliter->row] } ); int tmpcoliteridx = ( coliter - &mat.entries[0] ); int tmprowiteridx = ( rowiter - &mat.entries[0] ); mat.addEntry( coliter->row, rowiter->col, delta ); coliter = &mat.entries[tmpcoliteridx]; rowiter = &mat.entries[tmprowiteridx]; } } } } for( const MatrixEntry* coliter = mat.template beginStart( stack, -1, col ); coliter->col == col; coliter = mat.template next( stack ) ) { if( coliter->val == 0 ) continue; --rowsize[coliter->row]; --colsize[coliter->col]; const_cast*>( coliter )->val = 0; } for( const MatrixEntry* rowiter = mat.template beginStart( stack, pivotrow, -1 ); rowiter->row == pivotrow; rowiter = mat.template next( stack ) ) { if( rowiter->val == 0 ) continue; --rowsize[rowiter->row]; --colsize[rowiter->col]; const_cast*>( rowiter )->val = 0; } assert( rowsize[pivotrow] == 0 ); assert( colsize[col] == 0 ); rowsize[pivotrow] = -1; colsize[col] = -1; } int64_t remainingnnz = 0; for( int i = 0; i < (int) rowsize.size(); ++i ) { if( rowsize[i] != -1 ) { rowmapping.push_back( i ); remainingnnz += rowsize[i]; rowsize[i] = rowmapping.size(); } } msg.info( "preprocessed LU factor has {} nonzeros, preprocessing removed " "{} rows/cols\n", remainingnnz, nremoved ); if( remainingnnz == 0 ) return remainingnnz; nrows = rowmapping.size(); ncols = 0; for( int i = 0; i < (int) colsize.size(); ++i ) { if( colsize[i] != -1 ) { ++ncols; colsize[i] = ncols; } } // add data to lusol transposed lusolInput.setSize( ncols, nrows, remainingnnz ); for( int i = 1; i != (int) mat.entries.size(); ++i ) { if( mat.entries[i].val == 0 ) continue; lusolInput.addNnz( colsize[mat.entries[i].col], rowsize[mat.entries[i].row], double( mat.entries[i].val ) ); } assert( remainingnnz == (int) lusolInput.A.size() ); return remainingnnz; } Vec getDependentRows( const Message& msg, const Num& num ) { Vec rowmapping; LUSOL_Input lusolInput; if( !DependentRows::Enabled ) return rowmapping; int64_t nelem = preprocessLUFac( msg, num, lusolInput, rowmapping ); // no remaining nonzeros means all remaining rows are redundant if( nelem > 0 ) { lusolInput.applyScaling(); msg.info( "calling LUSOL on remaining factor\n" ); lusolInput.computeDependentColumns( rowmapping ); } return rowmapping; } private: int64_t nrows; int64_t ncols; MatrixBuffer mat; }; } // namespace papilo #endif