/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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_DOMINATED_COLS_HPP_ #define _PAPILO_PRESOLVERS_DOMINATED_COLS_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" #include "papilo/misc/Signature.hpp" #ifdef PAPILO_TBB #include "papilo/misc/tbb.hpp" #endif namespace papilo { template class DominatedCols : public PresolveMethod { public: DominatedCols() : PresolveMethod() { this->setName( "domcol" ); this->setTiming( PresolverTiming::kExhaustive ); } bool initialize( const Problem& problem, const PresolveOptions& presolveOptions ) override { if( presolveOptions.dualreds < 2 ) this->setEnabled( false ); return false; } /// stores implied bound information and signatures for a column struct ColInfo { Signature32 pos; Signature32 neg; int lbfree = 0; int ubfree = 0; Signature32 getNegSignature( int scale ) const { assert( scale == 1 || scale == -1 ); return scale == 1 ? neg : pos; } Signature32 getPosSignature( int scale ) const { assert( scale == 1 || scale == -1 ); return scale == 1 ? pos : neg; } bool allowsDomination( int scale, const ColInfo& other, int otherscale ) const { return getNegSignature( scale ).isSuperset( other.getNegSignature( otherscale ) ) && getPosSignature( scale ).isSubset( other.getPosSignature( otherscale ) ); } }; struct DomcolReduction { int col1; int col2; int implrowlock; BoundChange boundchg; }; 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 DominatedCols; extern template class DominatedCols; extern template class DominatedCols; #endif template PresolveStatus DominatedCols::execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) { const auto& obj = problem.getObjective().coefficients; const auto& consMatrix = problem.getConstraintMatrix(); const auto& lbValues = problem.getLowerBounds(); const auto& ubValues = problem.getUpperBounds(); const auto& lhsValues = consMatrix.getLeftHandSides(); const auto& rhsValues = consMatrix.getRightHandSides(); const auto& rflags = consMatrix.getRowFlags(); const auto& cflags = problem.getColFlags(); const auto& activities = problem.getRowActivities(); const auto& rowsize = consMatrix.getRowSizes(); const int ncols = problem.getNCols(); PresolveStatus result = PresolveStatus::kUnchanged; // do not call dominated column presolver too often, since it can be // expensive this->skipRounds( this->getNCalls() ); Vec colinfo( ncols ); #ifdef PAPILO_TBB tbb::concurrent_vector unboundedcols; #else Vec unboundedcols; #endif unboundedcols.reserve( ncols ); // compute signatures and implied bound information of all columns in // parallel #ifdef PAPILO_TBB tbb::parallel_for( tbb::blocked_range( 0, ncols ), [&]( const tbb::blocked_range& r ) { for( int col = r.begin(); col != r.end(); ++col ) #else for( int col = 0; col != ncols; ++col ) #endif { auto colvec = consMatrix.getColumnCoefficients( col ); int collen = colvec.getLength(); const int* colrows = colvec.getIndices(); const REAL* colvals = colvec.getValues(); if( cflags[col].test( ColFlag::kLbInf ) ) colinfo[col].lbfree = -1; if( cflags[col].test( ColFlag::kUbInf ) ) colinfo[col].ubfree = -1; for( int j = 0; j != collen; ++j ) { int row = colrows[j]; if( colinfo[col].ubfree == 0 && row_implies_UB( num, lhsValues[row], rhsValues[row], rflags[row], activities[row], colvals[j], lbValues[col], ubValues[col], cflags[col] ) ) colinfo[col].ubfree = j + 1; if( colinfo[col].lbfree == 0 && row_implies_LB( num, lhsValues[row], rhsValues[row], rflags[row], activities[row], colvals[j], lbValues[col], ubValues[col], cflags[col] ) ) colinfo[col].lbfree = j + 1; if( !rflags[row].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) ) { // ranged row or equality, add to positive and negative // signature colinfo[col].pos.add( row ); colinfo[col].neg.add( row ); } else if( rflags[row].test( RowFlag::kLhsInf ) ) { // <= constraint, add to positive signature for positive // coefficient and negative signature otherwise if( colvals[j] < 0 ) colinfo[col].neg.add( row ); else colinfo[col].pos.add( row ); } else { // >= constraint, add to positive signature for negative // coefficient and negative signature otherwise assert( rflags[row].test( RowFlag::kRhsInf ) ); if( colvals[j] < 0 ) colinfo[col].pos.add( row ); else colinfo[col].neg.add( row ); } } if( colinfo[col].lbfree != 0 || colinfo[col].ubfree != 0 ) unboundedcols.push_back( col ); } #ifdef PAPILO_TBB } ); #endif auto checkDominance = [&]( int col1, int col2, int scal1, int scal2 ) { assert( !cflags[col1].test( ColFlag::kIntegral ) || cflags[col2].test( ColFlag::kIntegral ) ); // first check if the signatures rule out domination if( !colinfo[col1].allowsDomination( scal1, colinfo[col2], scal2 ) ) return false; auto col1vec = consMatrix.getColumnCoefficients( col1 ); int col1len = col1vec.getLength(); const int* col1rows = col1vec.getIndices(); const REAL* col1vals = col1vec.getValues(); auto col2vec = consMatrix.getColumnCoefficients( col2 ); int col2len = col2vec.getLength(); const int* col2rows = col2vec.getIndices(); const REAL* col2vals = col2vec.getValues(); int i = 0; int j = 0; while( i != col1len && j != col2len ) { REAL val1; REAL val2; RowFlags rowf; if( col1rows[i] == col2rows[j] ) { val1 = col1vals[i] * scal1; val2 = col2vals[j] * scal2; rowf = rflags[col1rows[i]]; ++i; ++j; } else if( col1rows[i] < col2rows[j] ) { val1 = col1vals[i] * scal1; val2 = 0; rowf = rflags[col1rows[i]]; ++i; } else { assert( col1rows[i] > col2rows[j] ); val1 = 0; val2 = col2vals[j] * scal2; rowf = rflags[col2rows[j]]; ++j; } if( !rowf.test( RowFlag::kLhsInf, RowFlag::kRhsInf ) ) { // ranged row or equality, values must be equal if( !num.isEq( val1, val2 ) ) return false; } else if( rowf.test( RowFlag::kLhsInf ) ) { // <= constraint, col1 must have a smaller or equal coefficient if( num.isGT( val1, val2 ) ) return false; } else { // >= constraint, col1 must have a larger or equal coefficient assert( rowf.test( RowFlag::kRhsInf ) ); if( num.isLT( val1, val2 ) ) return false; } } while( i != col1len ) { REAL val1 = col1vals[i] * scal1; RowFlags rowf = rflags[col1rows[i]]; ++i; if( !rowf.test( RowFlag::kLhsInf, RowFlag::kRhsInf ) ) { // ranged row or equality, values must be equal return false; } else if( rowf.test( RowFlag::kLhsInf ) ) { // <= constraint, col1 must have a smaller or equal coefficient if( num.isGT( val1, 0 ) ) return false; } else { // >= constraint, col1 must have a larger or equal coefficient assert( rowf.test( RowFlag::kRhsInf ) ); if( num.isLT( val1, 0 ) ) return false; } } while( j != col2len ) { REAL val2 = col2vals[j] * scal2; RowFlags rowf = rflags[col2rows[j]]; ++j; if( !rowf.test( RowFlag::kLhsInf, RowFlag::kRhsInf ) ) { // ranged row or equality, values must be equal return false; } else if( rowf.test( RowFlag::kLhsInf ) ) { // <= constraint, col1 must have a smaller or equal coefficient if( num.isGT( 0, val2 ) ) return false; } else { // >= constraint, col1 must have a larger or equal coefficient assert( rowf.test( RowFlag::kRhsInf ) ); if( num.isLT( 0, val2 ) ) return false; } } return true; }; #ifdef PAPILO_TBB tbb::concurrent_vector domcolreductions; #else Vec domcolreductions; #endif #ifdef PAPILO_TBB // scan unbounded columns if they dominate other columns tbb::parallel_for( tbb::blocked_range( 0, (int) unboundedcols.size() ), [&]( const tbb::blocked_range& r ) { for( int k = r.begin(); k != r.end(); ++k ) #else for( int k = 0; k < (int) unboundedcols.size(); ++k ) #endif { int i = unboundedcols[k]; int lbfree = colinfo[i].lbfree; int ubfree = colinfo[i].ubfree; assert( lbfree != 0 || ubfree != 0 ); auto colvec = consMatrix.getColumnCoefficients( i ); int collen = colvec.getLength(); const int* colrows = colvec.getIndices(); const REAL* colvals = colvec.getValues(); int scale; int implrowlock; // determine the scale of the dominating column depending on // whether the upper or lower bound is free, and remember which // row needs to be locked to protect the implied bound (if any) if( ubfree != 0 ) { scale = 1; implrowlock = ubfree > 0 ? colrows[ubfree - 1] : -1; } else if( lbfree != 0 ) { scale = -1; implrowlock = lbfree > 0 ? colrows[lbfree - 1] : -1; } else continue; int bestrow = -1; int bestrowsize = std::numeric_limits::max(); for( int j = 0; j < collen; ++j ) { int row = colrows[j]; if( ( !rflags[row].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) || ( !rflags[row].test( RowFlag::kRhsInf ) && scale * colvals[j] > 0 ) || ( !rflags[row].test( RowFlag::kLhsInf ) && scale * colvals[j] < 0 ) ) && rowsize[row] < bestrowsize ) { bestrow = j; bestrowsize = rowsize[row]; } } if( bestrow == -1 || bestrowsize <= 1 ) continue; auto candrowvec = consMatrix.getRowCoefficients( colrows[bestrow] ); REAL colval = colvals[bestrow] * scale; REAL colobj = obj[i] * scale; bestrow = colrows[bestrow]; int rowlen = candrowvec.getLength(); const int* rowcols = candrowvec.getIndices(); const REAL* rowvals = candrowvec.getValues(); for( int j = 0; j != rowlen; ++j ) { int col = rowcols[j]; if( col == i || ( cflags[i].test( ColFlag::kIntegral ) && !cflags[col].test( ColFlag::kIntegral ) ) ) continue; bool fixtolb = false; bool fixtoub = false; if( !rflags[bestrow].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) ) { if( !cflags[col].test( ColFlag::kLbInf ) && num.isEq( colval, rowvals[j] ) && num.isLE( colobj, obj[col] ) && checkDominance( i, col, scale, 1 ) ) fixtolb = true; if( !cflags[col].test( ColFlag::kUbInf ) && num.isEq( colval, -rowvals[j] ) && num.isLE( colobj, -obj[col] ) && checkDominance( i, col, scale, -1 ) ) fixtoub = true; } else if( rflags[bestrow].test( RowFlag::kLhsInf ) ) { assert( colval > 0 && !rflags[bestrow].test( RowFlag::kRhsInf ) ); if( !cflags[col].test( ColFlag::kLbInf ) && num.isLE( colval, rowvals[j] ) && num.isLE( colobj, obj[col] ) && checkDominance( i, col, scale, 1 ) ) fixtolb = true; if( !cflags[col].test( ColFlag::kUbInf ) && num.isLE( colval, -rowvals[j] ) && num.isLE( colobj, -obj[col] ) && checkDominance( i, col, scale, -1 ) ) fixtoub = true; } else { assert( colval < 0 && rflags[bestrow].test( RowFlag::kRhsInf ) ); if( !cflags[col].test( ColFlag::kLbInf ) && num.isGE( colval, rowvals[j] ) && num.isLE( colobj, obj[col] ) && checkDominance( i, col, scale, 1 ) ) fixtolb = true; if( !cflags[col].test( ColFlag::kUbInf ) && num.isGE( colval, -rowvals[j] ) && num.isLE( colobj, -obj[col] ) && checkDominance( i, col, scale, -1 ) ) fixtoub = true; } if( fixtolb || fixtoub ) { domcolreductions.push_back( DomcolReduction{ i, col, implrowlock, fixtolb ? BoundChange::kUpper : BoundChange::kLower } ); } } } #ifdef PAPILO_TBB } ); #endif if( !domcolreductions.empty() ) { result = PresolveStatus::kReduced; // sort reductions by the smallest col in the domcol reduction, so that // parallel ones are adjacent pdqsort( domcolreductions.begin(), domcolreductions.end(), []( const DomcolReduction& a, const DomcolReduction& b ) { bool smaller_first_a = a.col1 < a.col2; bool smaller_first_b = b.col1 < b.col2; int smaller_row_a = smaller_first_a ? a.col1 : a.col2; int smaller_row_b = smaller_first_b ? b.col1 : b.col2; if( smaller_row_a != smaller_row_b ) return smaller_row_a < smaller_row_b; return ( ( !smaller_first_a ) ? a.col1 : a.col2 ) < ( ( !smaller_first_b ) ? b.col1 : b.col2 ); } ); for( int i = 0; i < (int) domcolreductions.size(); i++ ) { // check if consecutively reductions are equal const DomcolReduction dr = domcolreductions[i]; if( i < (int) domcolreductions.size() - 1 ) { const DomcolReduction dr2 = domcolreductions[i + 1]; if( dr2.col1 == dr.col2 && dr.col1 == dr2.col2 ) { if( dr.implrowlock > 0 ) continue; i++; } } TransactionGuard tg{ reductions }; reductions.lockCol( dr.col1 ); reductions.lockColBounds( dr.col1 ); reductions.lockCol( dr.col2 ); reductions.lockColBounds( dr.col2 ); //TODO: check if >=0 is correct instead of >0 if( dr.implrowlock >= 0 ) reductions.lockRow( dr.implrowlock ); if( dr.boundchg == BoundChange::kUpper ) reductions.fixCol( dr.col2, lbValues[dr.col2], dr.implrowlock ); else reductions.fixCol( dr.col2, ubValues[dr.col2], dr.implrowlock); } } return result; } } // namespace papilo #endif