/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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_PRESOLVERS_SPARSIFY_HPP_ #define _PAPILO_PRESOLVERS_SPARSIFY_HPP_ #include "papilo/core/PresolveMethod.hpp" #include "papilo/core/Problem.hpp" #include "papilo/core/ProblemUpdate.hpp" #include "papilo/core/SingleRow.hpp" #include "papilo/misc/Hash.hpp" #ifdef PAPILO_TBB #include "papilo/misc/tbb.hpp" #endif namespace papilo { template class Sparsify : public PresolveMethod { double maxscale = 1000; using HitCount = uint16_t; struct SparsifyData { Vec candrowhits; Vec candrows; Vec> sparsify; Vec> reductionBuffer; explicit SparsifyData( int nrows ) : candrowhits( nrows ) { candrows.reserve( nrows ); } }; public: Sparsify() : PresolveMethod() { this->setName( "sparsify" ); this->setTiming( PresolverTiming::kExhaustive ); this->setDelayed( true ); } void addPresolverParams( ParameterSet& paramSet ) override { paramSet.addParameter( "sparsify.maxscale", "maximum absolute scale to use for cancelling nonzeros", this->maxscale, 1.0 ); } PresolveStatus execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) override; }; #ifdef PAPILO_USE_EXTERN_TEMPLATES extern template class Sparsify; extern template class Sparsify; extern template class Sparsify; #endif template PresolveStatus Sparsify::execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) { // go over the rows and get the equalities, extract the columns that // verify the conditions add them to a hash map, loop over the hash map // and compute the implied bounds and finally look for implied free // variables and add reductions const auto& domains = problem.getVariableDomains(); const auto& lower_bounds = domains.lower_bounds; const auto& upper_bounds = domains.upper_bounds; const auto& cflags = domains.flags; const ConstraintMatrix& consmatrix = problem.getConstraintMatrix(); const auto& rflags = consmatrix.getRowFlags(); const auto& rowsize = consmatrix.getRowSizes(); const auto& nrows = consmatrix.getNRows(); auto isBinaryCol = [&]( int col ) { return cflags[col].test( ColFlag::kIntegral ) && !cflags[col].test( ColFlag::kUnbounded ) && lower_bounds[col] == 0 && upper_bounds[col] == 1; }; PresolveStatus result = PresolveStatus::kUnchanged; // after each call skip more rounds to not call sparsify too often this->skipRounds( this->getNCalls() ); Vec equalities; equalities.reserve( nrows ); for( int i = 0; i < nrows; ++i ) { if( rflags[i].test( RowFlag::kRedundant ) || !rflags[i].test( RowFlag::kEquation ) || rowsize[i] <= 1 || rowsize[i] > std::numeric_limits::max() ) continue; assert( !rflags[i].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) && consmatrix.getLeftHandSides()[i] == consmatrix.getRightHandSides()[i] ); equalities.emplace_back( i ); } #ifdef PAPILO_TBB tbb::combinable sparsifyData( [nrows]() { return SparsifyData( nrows ); } ); tbb::parallel_for( tbb::blocked_range( 0, static_cast( equalities.size() ) ), [&]( const tbb::blocked_range& r ) { SparsifyData& localData = sparsifyData.local(); std::size_t sparsifyStart; auto& candrowhits = localData.candrowhits; auto& candrows = localData.candrows; auto& sparsify = localData.sparsify; auto& reductionBuffer = localData.reductionBuffer; for( int i = r.begin(); i < r.end(); ++i ) #else SparsifyData s = SparsifyData(nrows); auto& candrowhits = s.candrowhits; auto& candrows = s.candrows; auto& sparsify = s.sparsify; std::size_t sparsifyStart; auto& reductionBuffer = s.reductionBuffer; for( int i = 0; i < (int) equalities.size(); ++i ) #endif { int eqrow = equalities[i]; auto rowvec = consmatrix.getRowCoefficients( eqrow ); int eqlen = rowvec.getLength(); const int* eqcols = rowvec.getIndices(); bool cancelint = true; int minhits = eqlen - 1; int nint = 0; Message::debug( this, "trying sparsification with equality row {} of length {}\n", eqrow, eqlen ); if( problem.getNumIntegralCols() != 0 ) { int ncont = 0; int nbin = 0; for( int counter = 0; counter != eqlen; ++counter ) { int col = eqcols[counter]; if( !cflags[col].test( ColFlag::kIntegral ) ) ++ncont; else if( isBinaryCol( col ) ) ++nbin; else { ++nint; continue; } auto colvec = consmatrix.getColumnCoefficients( col ); const int* colrows = colvec.getIndices(); int collen = colvec.getLength(); for( int j = 0; j != collen; ++j ) { int row = colrows[j]; if( row == eqrow ) continue; if( candrowhits[row] == 0 ) { if( nbin + ncont > 2 ) continue; candrows.push_back( row ); } ++candrowhits[row]; } } if( nbin + nint == 0 ) { auto it = std::remove_if( candrows.begin(), candrows.end(), [&]( int _r ) { if( candrowhits[_r] < ncont - 1 ) { candrowhits[_r] = 0; return true; } return false; } ); cancelint = false; candrows.erase( it, candrows.end() ); } else { auto it = std::remove_if( candrows.begin(), candrows.end(), [&]( int _r ) { if( candrowhits[_r] < nbin + ncont - 1 ) { candrowhits[_r] = 0; return true; } if( cancelint ) candrowhits[_r] = nbin + ncont; return false; } ); candrows.erase( it, candrows.end() ); minhits = eqlen; } } if( problem.getNumIntegralCols() == 0 || nint != 0 ) { for( int counter = 0; counter != eqlen; ++counter ) { int col = eqcols[counter]; if( problem.getNumIntegralCols() != 0 && ( !cflags[col].test( ColFlag::kIntegral ) || isBinaryCol( col ) ) ) continue; auto colvec = consmatrix.getColumnCoefficients( col ); const int* colrows = colvec.getIndices(); int collen = colvec.getLength(); for( int j = 0; j != collen; ++j ) { int row = colrows[j]; if( row == eqrow ) continue; if( candrowhits[row] == 0 ) { if( counter > eqlen - minhits ) continue; candrows.push_back( row ); } ++candrowhits[row]; } } auto it = std::remove_if( candrows.begin(), candrows.end(), [&]( int _r ) { if( candrowhits[_r] < minhits ) { candrowhits[_r] = 0; return true; } return false; } ); candrows.erase( it, candrows.end() ); } if( !candrows.empty() ) { Vec scales( eqlen ); const REAL* eqvals = rowvec.getValues(); sparsifyStart = sparsify.size(); sparsify.reserve( sparsifyStart + candrows.size() ); for( int candrow : candrows ) { auto candrowvec = consmatrix.getRowCoefficients( candrow ); const int* candcols = candrowvec.getIndices(); const REAL* candvals = candrowvec.getValues(); int candlen = candrowvec.getLength(); if( !cancelint && candrowhits[candrow] != eqlen ) { bool has_integral = false; for( int j = 0; j != candlen; ++j ) { if( cflags[candcols[j]].test( ColFlag::kIntegral ) ) { has_integral = true; break; } } if( has_integral ) continue; } int h = 0; int j = 0; int currcancel = 0; while( h != eqlen && j != candlen ) { if( eqcols[h] == candcols[j] ) { scales[h] = -candvals[j] / eqvals[h]; ++h; ++j; } else if( eqcols[h] < candcols[j] ) { --currcancel; scales[h] = 0; ++h; } else { ++j; } } while( h != eqlen ) { --currcancel; scales[h] = 0; ++h; } pdqsort( scales.begin(), scales.end() ); int bestcancel = 0; REAL bestscale = 0; for( int k = 0; k != eqlen - 1; ++k ) { if( scales[k] == 0 || abs( scales[k] ) > maxscale ) continue; int ncancel = currcancel; for( int l = k + 1; l != eqlen; ++l ) { if( num.isEq( scales[k], scales[l] ) ) ++ncancel; else break; } if( ncancel > bestcancel ) { bestcancel = ncancel; bestscale = scales[k]; } } if( bestcancel > 0 ) { Message::debug( this, "equation row{} cancels {} nonzeros on row{} " "with scale {}\n", eqrow, bestcancel, candrow, bestscale ); sparsify.emplace_back( candrow, bestscale ); } } for( int candrow : candrows ) candrowhits[candrow] = 0; candrows.clear(); if( sparsify.size() != sparsifyStart ) reductionBuffer.emplace_back( eqrow, int( sparsifyStart ), int( sparsify.size() ) ); } } #ifdef PAPILO_TBB } ); #endif int nreductions = 0; #ifdef PAPILO_TBB sparsifyData.combine_each( [&]( const SparsifyData& localData ) { nreductions += localData.reductionBuffer.size(); } ); #else nreductions = s.reductionBuffer.size(); #endif if( nreductions != 0 ) { result = PresolveStatus::kReduced; Vec*>> reductionData; reductionData.reserve( nreductions ); #ifdef PAPILO_TBB sparsifyData.combine_each( [&]( SparsifyData& localData ) { for( const std::tuple& reductionTuple : localData.reductionBuffer ) { int eqrow = std::get<0>( reductionTuple ); int start = std::get<1>( reductionTuple ); int end = std::get<2>( reductionTuple ); reductionData.emplace_back( eqrow, end - start, &localData.sparsify[start] ); } } ); #else for( const std::tuple& reductionTuple : s.reductionBuffer ) { int eqrow = std::get<0>( reductionTuple ); int start = std::get<1>( reductionTuple ); int end = std::get<2>( reductionTuple ); reductionData.emplace_back( eqrow, end - start, &s.sparsify[start] ); } #endif const Vec& rowperm = problemUpdate.getRandomRowPerm(); pdqsort( reductionData.begin(), reductionData.end(), [&]( const std::tuple*>& a, const std::tuple*>& b ) { int eqrowA = std::get<0>( a ); int eqrowB = std::get<0>( b ); int numCandsA = std::get<1>( a ); int numCandsB = std::get<1>( b ); return std::make_tuple( rowsize[eqrowA], -numCandsA, rowperm[eqrowA] ) < std::make_tuple( rowsize[eqrowB], -numCandsB, rowperm[eqrowB] ); } ); for( const std::tuple*>& reductionTuple : reductionData ) { int equality_row = std::get<0>( reductionTuple ); int numeric = std::get<1>( reductionTuple ); const std::pair* sparsify = std::get<2>( reductionTuple ); TransactionGuard tg{ reductions }; reductions.lockRow( equality_row ); reductions.sparsify( equality_row, numeric, sparsify ); } } Message::debug( this, "sparsify finished\n" ); return result; } } // namespace papilo #endif