/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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_PROBING_HPP_ #define _PAPILO_PRESOLVERS_PROBING_HPP_ #include "papilo/core/PresolveMethod.hpp" #include "papilo/core/ProbingView.hpp" #include "papilo/core/Problem.hpp" #include "papilo/core/ProblemUpdate.hpp" #include "papilo/core/SingleRow.hpp" #include "papilo/misc/Array.hpp" #include "papilo/misc/Hash.hpp" #include "papilo/misc/Vec.hpp" #include "papilo/misc/compress_vector.hpp" #include "papilo/misc/fmt.hpp" #ifdef PAPILO_TBB #include "papilo/misc/tbb.hpp" #endif #include #include namespace papilo { const static int DEFAULT_MAX_BADGE_SIZE = -1; template class Probing : public PresolveMethod { Vec nprobed; int maxinitialbadgesize = 1000; int minbadgesize = 10; int max_badge_size = DEFAULT_MAX_BADGE_SIZE; double mincontdomred = 0.3; public: Probing() : PresolveMethod() { this->setName( "probing" ); this->setTiming( PresolverTiming::kExhaustive ); this->setType( PresolverType::kIntegralCols ); } void compress( const Vec& rowmap, const Vec& colmap ) override { assert( colmap.size() == nprobed.size() ); compress_vector( colmap, nprobed ); Message::debug( this, "compress was called, compressed nprobed vector from " "size {} to size {}\n", colmap.size(), nprobed.size() ); } bool initialize( const Problem& problem, const PresolveOptions& presolveOptions ) override { nprobed.clear(); nprobed.resize( problem.getNCols(), 0 ); Message::debug( this, "initialized nprobed vector to size {}\n", nprobed.size() ); return true; } void addPresolverParams( ParameterSet& paramSet ) override { paramSet.addParameter( "probing.maxinitialbadgesize", "maximum number of probing candidates probed in " "the first badge of candidates", maxinitialbadgesize, 1 ); paramSet.addParameter( "probing.minbadgesize", "minimum number of probing candidates probed in " "a single badge of candidates", minbadgesize, 1 ); paramSet.addParameter( "probing.maxbadgesize", "maximal number of probing candidates probed in " "a single badge of candidates", max_badge_size, DEFAULT_MAX_BADGE_SIZE ); paramSet.addParameter( "probing.mincontdomred", "minimum fraction of domain that needs to be reduced for continuous " "variables to accept a bound change in probing", mincontdomred, 0.0, 1.0 ); } PresolveStatus execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) override; bool isBinaryVariable( REAL upper_bound, REAL lower_bound, int column_size, const Flags& colFlag ) const; void set_max_badge_size( int val); }; #ifdef PAPILO_USE_EXTERN_TEMPLATES extern template class Probing; extern template class Probing; extern template class Probing; #endif template PresolveStatus Probing::execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer) { if( problem.getNumIntegralCols() == 0 ) return PresolveStatus::kUnchanged; const auto& domains = problem.getVariableDomains(); const Vec& lower_bounds = domains.lower_bounds; const Vec& upper_bounds = domains.upper_bounds; const Vec& cflags = domains.flags; const auto& consMatrix = problem.getConstraintMatrix(); const auto& lhs = consMatrix.getLeftHandSides(); const auto& rhs = consMatrix.getRightHandSides(); const Vec& rowFlags = consMatrix.getRowFlags(); const auto& activities = problem.getRowActivities(); const int ncols = problem.getNCols(); const Vec& colsize = consMatrix.getColSizes(); const auto& colperm = problemUpdate.getRandomColPerm(); Vec probing_cands; probing_cands.reserve( ncols ); for( int i = 0; i != ncols; ++i ) { if( isBinaryVariable( upper_bounds[i], lower_bounds[i], colsize[i], cflags[i] ) ) probing_cands.push_back( i ); } if( probing_cands.empty() ) return PresolveStatus::kUnchanged; Array probing_scores( ncols ); for( int i = 0; i != ncols; ++i ) probing_scores[i].store( 0, std::memory_order_relaxed ); if( nprobed.empty() ) { nprobed.resize( size_t( ncols ), 0 ); assert( static_cast( nprobed.size() ) == ncols ); assert( std::all_of( nprobed.begin(), nprobed.end(), []( int n ) { return n == 0; } ) ); } #ifdef PAPILO_TBB tbb::parallel_for( tbb::blocked_range( 0, problem.getNRows() ), [&]( const tbb::blocked_range& r ) { Vec> binary_variables_in_row; for( int row = r.begin(); row != r.end(); ++row ) #else Vec> binary_variables_in_row; for( int row = 0; row != problem.getNRows(); ++row ) #endif { if( consMatrix.isRowRedundant( row ) ) continue; if( ( activities[row].ninfmin != 0 || rowFlags[row].test( RowFlag::kRhsInf ) ) && ( activities[row].ninfmax != 0 || rowFlags[row].test( RowFlag::kLhsInf ) ) ) continue; auto rowvec = consMatrix.getRowCoefficients( row ); const int* colinds = rowvec.getIndices(); const REAL* rowvals = rowvec.getValues(); const int rowlen = rowvec.getLength(); binary_variables_in_row.reserve( rowlen ); for( int i = 0; i != rowlen; ++i ) { if( isBinaryVariable( upper_bounds[i], lower_bounds[i], colsize[i], cflags[i] ) ) binary_variables_in_row.emplace_back( rowvals[i], colinds[i] ); } const int nbinvarsrow = static_cast( binary_variables_in_row.size() ); if( nbinvarsrow == 0 ) continue; pdqsort( binary_variables_in_row.begin(), binary_variables_in_row.end(), []( const std::pair& a, const std::pair& b ) { return abs( a.first ) > abs( b.first ); } ); for( int i = 0; i < nbinvarsrow; ++i ) { // TODO: wouldn't be simpler: calculate minimplcoef, if greater // equals than abs, then discard int col = binary_variables_in_row[i].second; REAL abscoef = abs( binary_variables_in_row[i].first ); REAL minimplcoef = abscoef; if( activities[row].ninfmin == 0 && !rowFlags[row].test( RowFlag::kRhsInf ) ) minimplcoef = std::min( minimplcoef, REAL( rhs[row] - activities[row].min - abscoef ) ); if( activities[row].ninfmax == 0 && !rowFlags[row].test( RowFlag::kLhsInf ) ) minimplcoef = std::min( minimplcoef, REAL( activities[row].max - abscoef - lhs[row] ) ); if( num.isFeasLE( abscoef, minimplcoef ) ) break; int nimplbins = 0; for( int j = i + 1; j != nbinvarsrow; ++j ) { if( num.isFeasGT( abs( binary_variables_in_row[j].first ), minimplcoef ) ) ++nimplbins; else break; } if( nimplbins != 0 ) probing_scores[col].fetch_add( nimplbins, std::memory_order_relaxed ); else break; } binary_variables_in_row.clear(); } #ifdef PAPILO_TBB }); #endif pdqsort( probing_cands.begin(), probing_cands.end(), [this, &probing_scores, &colsize, &colperm]( int col1, int col2 ) { std::pair s1; std::pair s2; if( nprobed[col2] == 0 && probing_scores[col2] > 0 ) s2.first = probing_scores[col2] / static_cast( colsize[col2] ); else s2.first = 0; if( nprobed[col1] == 0 && probing_scores[col1] > 0 ) s1.first = probing_scores[col1] / static_cast( colsize[col1] ); else s1.first = 0; s1.second = ( probing_scores[col1].load( std::memory_order_relaxed ) / static_cast( 1 + nprobed[col1] * colsize[col1] ) ); s2.second = ( probing_scores[col2].load( std::memory_order_relaxed ) / static_cast( 1 + nprobed[col2] * colsize[col2] ) ); return s1 > s2 || ( s1 == s2 && colperm[col1] < colperm[col2] ); } ); const Vec& rowsize = consMatrix.getRowSizes(); int current_badge_start = 0; int64_t working_limit = consMatrix.getNnz() * 2; int initial_badge_limit = 0.1 * working_limit; const int nprobingcands = static_cast( probing_cands.size() ); int badge_size = 0; for( int i : probing_cands ) { ++badge_size; if( badge_size == maxinitialbadgesize ) break; initial_badge_limit -= colsize[i]; if( initial_badge_limit <= 0 ) break; auto colvec = consMatrix.getColumnCoefficients( i ); const int* rowinds = colvec.getIndices(); for( int k = 0; k != colvec.getLength(); ++k ) { initial_badge_limit -= ( rowsize[rowinds[k]] - 1 ); if( initial_badge_limit <= 0 ) break; } if( initial_badge_limit <= 0 ) break; } badge_size = std::max( std::min( nprobingcands, minbadgesize ), badge_size ); int current_badge_end = current_badge_start + badge_size; int n_useless = 0; bool abort = false; HashMap, int, boost::hash>> substitutionsPos; Vec> substitutions; Vec boundPos( size_t( 2 * ncols ), 0 ); Vec> boundChanges; boundChanges.reserve( ncols ); std::atomic_bool infeasible{ false }; // use tbb combinable so that each thread will copy the activities and // bounds at most once #ifdef PAPILO_TBB tbb::combinable> probing_views( [this, &problem, &num]() { ProbingView probingView( problem, num ); probingView.setMinContDomRed( mincontdomred ); return probingView; } ); #else ProbingView probingView( problem, num ); probingView.setMinContDomRed( mincontdomred ); #endif do { Message::debug( this, "probing candidates {} to {}\n", current_badge_start, current_badge_end ); auto propagate_variables = [&]( int start, int end) { #ifdef PAPILO_TBB tbb::parallel_for( tbb::blocked_range( start, end ), [&]( const tbb::blocked_range& r ) { ProbingView& probingView = probing_views.local(); for( int i = r.begin(); i != r.end(); ++i ) #else for( int i = start; i < end; i++ ) #endif { if( PresolveMethod::is_time_exceeded( timer, problemUpdate.getPresolveOptions().tlim ) ) break; const int col = probing_cands[i]; assert( cflags[col].test( ColFlag::kIntegral ) && (lower_bounds[col] == 0 || upper_bounds[col] == 1 )); if( infeasible.load( std::memory_order_relaxed ) ) break; assert( !probingView.isInfeasible() ); probingView.setProbingColumn( col, true ); probingView.propagateDomains(); probingView.storeImplications(); probingView.reset(); if( infeasible.load( std::memory_order_relaxed ) ) break; assert( !probingView.isInfeasible() ); probingView.setProbingColumn( col, false ); probingView.propagateDomains(); bool globalInfeasible = probingView.analyzeImplications(); probingView.reset(); ++nprobed[col]; if( globalInfeasible ) { infeasible.store( true, std::memory_order_relaxed ); break; } } #ifdef PAPILO_TBB } ); #endif }; propagate_variables( current_badge_start, current_badge_end ); if( PresolveMethod::is_time_exceeded( timer, problemUpdate.getPresolveOptions().tlim ) ) return PresolveStatus::kUnchanged; if( infeasible.load( std::memory_order_relaxed ) ) return PresolveStatus::kInfeasible; int64_t amountofwork = 0; int nfixings = 0; int nboundchgs = 0; int nsubstitutions = -substitutions.size(); #ifdef PAPILO_TBB probing_views.combine_each( [&]( ProbingView& probingView ) { #endif const auto& probingBoundChgs = probingView.getProbingBoundChanges(); const auto& probingSubstitutions = probingView.getProbingSubstitutions(); amountofwork += probingView.getAmountOfWork(); for( const ProbingSubstitution& subst : probingSubstitutions ) { auto insres = substitutionsPos.emplace( std::make_pair( subst.col1, subst.col2 ), substitutions.size() ); if( insres.second ) substitutions.push_back( subst ); } for( const ProbingBoundChg& boundChg : probingBoundChgs ) { if( boundPos[2 * boundChg.col + boundChg.upper] == 0 ) { // found new bound change boundChanges.emplace_back( boundChg ); boundPos[2 * boundChg.col + boundChg.upper] = boundChanges.size(); // check if column is now fixed if( ( boundChg.upper && boundChg.bound == lower_bounds[boundChg.col] ) || ( !boundChg.upper && boundChg.bound == upper_bounds[boundChg.col] ) ) ++nfixings; else ++nboundchgs; } else { // already changed that bound ProbingBoundChg& otherBoundChg = boundChanges[boundPos[2 * boundChg.col + boundChg.upper] - 1]; if( boundChg.upper && boundChg.bound < otherBoundChg.bound ) { // new upper bound change is tighter otherBoundChg.bound = boundChg.bound; // check if column is now fixed if( boundChg.bound == lower_bounds[boundChg.col] ) ++nfixings; } else if( !boundChg.upper && boundChg.bound > otherBoundChg.bound ) { // new lower bound change is tighter otherBoundChg.bound = boundChg.bound; // check if column is now fixed if( boundChg.bound == upper_bounds[boundChg.col] ) ++nfixings; } // do only count fixings in this case for two reasons: // 1) the number of bound changes depends on the order and // would make probing non deterministic 2) the boundchange was // already counted in previous rounds and will only be added // once } } probingView.clearResults(); #ifdef PAPILO_TBB } ); #endif nsubstitutions += substitutions.size(); current_badge_start = current_badge_end; if( nfixings == 0 && nboundchgs == 0 && nsubstitutions == 0 ) n_useless += amountofwork; else n_useless = 0; Message::debug( this, "probing found: {} fixings, {} substitutions, {} bound changes\n", nfixings, nsubstitutions, nboundchgs ); int64_t extrawork = ( ( 0.1 * ( nfixings + nsubstitutions ) + 0.01 * nboundchgs ) * consMatrix.getNnz() ); working_limit -= amountofwork; working_limit += extrawork; badge_size = static_cast( ceil( badge_size * static_cast( working_limit + extrawork ) / (double) amountofwork ) ); badge_size = std::min( nprobingcands - current_badge_start, badge_size ); if( max_badge_size > 0 ) badge_size = std::min( max_badge_size, badge_size ); current_badge_end = current_badge_start + badge_size; abort = n_useless >= consMatrix.getNnz() * 2 || working_limit < 0 || current_badge_start == current_badge_end || PresolveMethod::is_time_exceeded(timer, problemUpdate.getPresolveOptions().tlim ); } while( !abort ); PresolveStatus result = PresolveStatus::kUnchanged; if( !boundChanges.empty() ) { pdqsort( boundChanges.begin(), boundChanges.end(), []( const ProbingBoundChg& a, const ProbingBoundChg& b ) { return ( a.col << 1 | a.upper ) < ( b.col << 1 | b.upper ); } ); for( const ProbingBoundChg& boundChg : boundChanges ) { if( boundChg.upper ) reductions.changeColUB( boundChg.col, boundChg.bound ); else reductions.changeColLB( boundChg.col, boundChg.bound ); } result = PresolveStatus::kReduced; } if( !substitutions.empty() ) { pdqsort( substitutions.begin(), substitutions.end(), []( const ProbingSubstitution& a, const ProbingSubstitution& b ) { return std::make_pair( a.col1, a.col2 ) > std::make_pair( b.col1, b.col2 ); } ); int lastsubstcol = -1; for( const ProbingSubstitution& subst : substitutions ) { if( subst.col1 == lastsubstcol ) continue; lastsubstcol = subst.col1; reductions.replaceCol( subst.col1, subst.col2, subst.col2scale, subst.col2const ); } result = PresolveStatus::kReduced; } return result; } template bool Probing::isBinaryVariable( REAL upper_bound, REAL lower_bound, int column_size, const Flags& colFlag ) const { return !colFlag.test( ColFlag::kUnbounded ) && colFlag.test( ColFlag::kIntegral ) && column_size > 0 && lower_bound == 0 && upper_bound == 1; } template void Probing::set_max_badge_size( int val) { max_badge_size = val; } } // namespace papilo #endif