/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* */
/* 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_PRESOLVE_HPP_
#define _PAPILO_CORE_PRESOLVE_HPP_
#include
#include
#include
#include
#include
#include
#include
#include
#include "papilo/core/PresolveMethod.hpp"
#include "papilo/core/PresolveOptions.hpp"
#include "papilo/core/Problem.hpp"
#include "papilo/core/ProblemUpdate.hpp"
#include "papilo/core/Statistics.hpp"
#include "papilo/core/postsolve/Postsolve.hpp"
#include "papilo/core/postsolve/PostsolveStorage.hpp"
#include "papilo/interfaces/SolverInterface.hpp"
#include "papilo/io/Message.hpp"
#include "papilo/misc/DependentRows.hpp"
#include "papilo/misc/ParameterSet.hpp"
#include "papilo/misc/Timer.hpp"
#include "papilo/misc/Vec.hpp"
#ifdef PAPILO_TBB
#include "papilo/misc/tbb.hpp"
#endif
#include "papilo/presolvers/CoefficientStrengthening.hpp"
#include "papilo/presolvers/ConstraintPropagation.hpp"
#include "papilo/presolvers/DominatedCols.hpp"
#include "papilo/presolvers/DualFix.hpp"
#include "papilo/presolvers/DualInfer.hpp"
#include "papilo/presolvers/FixContinuous.hpp"
#include "papilo/presolvers/FreeVarSubstitution.hpp"
#include "papilo/presolvers/ImplIntDetection.hpp"
#include "papilo/presolvers/ParallelColDetection.hpp"
#include "papilo/presolvers/ParallelRowDetection.hpp"
#include "papilo/presolvers/Probing.hpp"
#include "papilo/presolvers/SimpleProbing.hpp"
#include "papilo/presolvers/SimpleSubstitution.hpp"
#include "papilo/presolvers/SimplifyInequalities.hpp"
#include "papilo/presolvers/SingletonCols.hpp"
#include "papilo/presolvers/SingletonStuffing.hpp"
#include "papilo/presolvers/Sparsify.hpp"
namespace papilo
{
template
struct PresolveResult
{
PostsolveStorage postsolve;
PresolveStatus status;
};
enum class Delegator
{
kAbort,
kFast,
kMedium,
kExhaustive,
kExceeded
};
template
class Presolve
{
public:
void
addDefaultPresolvers()
{
using uptr = std::unique_ptr>;
// fast presolvers
addPresolveMethod( uptr( new SingletonCols() ) );
addPresolveMethod( uptr( new CoefficientStrengthening() ) );
addPresolveMethod( uptr( new ConstraintPropagation() ) );
//medium presolvers
addPresolveMethod( uptr( new SimpleProbing() ) );
addPresolveMethod( uptr( new ParallelRowDetection() ) );
addPresolveMethod( uptr( new ParallelColDetection() ) );
addPresolveMethod( uptr( new SingletonStuffing() ) );
addPresolveMethod( uptr( new DualFix() ) );
addPresolveMethod( uptr( new FixContinuous() ) );
addPresolveMethod( uptr( new SimplifyInequalities() ) );
addPresolveMethod( uptr( new SimpleSubstitution() ) );
//exhaustive presolvers
addPresolveMethod( uptr( new ImplIntDetection() ) );
addPresolveMethod( uptr( new DominatedCols() ) );
addPresolveMethod( uptr( new DualInfer ) );
addPresolveMethod( uptr( new Probing() ) );
addPresolveMethod( uptr( new Substitution() ) );
addPresolveMethod( uptr( new Sparsify() ) );
}
ParameterSet
getParameters()
{
ParameterSet paramSet;
msg.addParameters( paramSet );
presolveOptions.addParameters( paramSet );
for( const std::unique_ptr>& presolver : presolvers )
presolver->addParameters( paramSet );
return paramSet;
}
/***
* presolves the problem and applies the reductions found by the presolvers
* immediately to it.
* The functions returns the PresolveStatus (Reduced, Unchanged, ) and the
* postsolve information
*
* @tparam REAL: computational accuracy template
* @param problem: the problem to be presolved
* @return: presolved problem and PresolveResult contains postsolve
* information
*/
PresolveResult
apply( Problem& problem, bool store_dual_postsolve = true );
/// add presolve method to presolving
void
addPresolveMethod( std::unique_ptr> presolveMethod )
{
presolvers.emplace_back( std::move( presolveMethod ) );
}
void
setLPSolverFactory( std::unique_ptr> value )
{
this->lpSolverFactory = std::move( value );
}
void
setMIPSolverFactory( std::unique_ptr> value )
{
this->mipSolverFactory = std::move( value );
}
const std::unique_ptr>&
getLPSolverFactory() const
{
return this->lpSolverFactory;
}
const std::unique_ptr>&
getMIPSolverFactory() const
{
return this->mipSolverFactory;
}
void
setPresolverOptions( const PresolveOptions& value )
{
this->presolveOptions = value;
}
const PresolveOptions&
getPresolveOptions() const
{
return this->presolveOptions;
}
PresolveOptions&
getPresolveOptions()
{
return this->presolveOptions;
}
/// get epsilon value for numerical comparisons
const REAL&
getEpsilon() const
{
return num.getEpsilon();
}
/// get feasibility tolerance value
const REAL&
getFeasTol() const
{
return num.getFeasTol();
}
/// set the verbosity level
void
setVerbosityLevel( VerbosityLevel verbosity )
{
msg.setVerbosityLevel( verbosity );
}
/// get the verbosity level
VerbosityLevel
getVerbosityLevel() const
{
return msg.getVerbosityLevel();
}
const Message&
message() const
{
return msg;
}
Message&
message()
{
return msg;
}
/// access statistics of presolving
const Statistics&
getStatistics() const
{
return stats;
}
std::pair
applyReductions( int p, const Reductions& reductions_,
ProblemUpdate& probUpdate );
private:
// data to perform presolving
Vec results;
Vec>> presolvers;
Vec> reductions;
Delegator round_to_evaluate;
Vec*, const Reduction*>>
postponedReductions;
Vec postponedReductionToPresolver;
// settings for presolve behavior
Num num;
Message msg;
PresolveOptions presolveOptions;
// statistics
Statistics stats;
std::unique_ptr> lpSolverFactory;
std::unique_ptr> mipSolverFactory;
Vec> presolverStats;
bool lastRoundReduced;
int nunsuccessful;
bool rundelayed;
/// evaluate result array of each presolver, return the largest result value
PresolveStatus
evaluateResults();
void
finishRound( ProblemUpdate& probUpdate );
void
applyPostponed( ProblemUpdate& probUpdate );
Delegator
determine_next_round( Problem& problem,
ProblemUpdate& probUpdate,
const Statistics& roundStats,
const Timer& presolvetimer, bool unchanged = false );
PresolveStatus
apply_all_presolver_reductions( ProblemUpdate& probUpdate );
void
printRoundStats( bool unchanged, std::string rndtype );
void
printPresolversStats();
private:
void
logStatus( const Problem& problem,
const PostsolveStorage& postsolveStorage ) const;
bool
is_time_exceeded( const Timer& presolvetimer ) const;
bool
is_only_slighlty_changes( const Problem& problem,
const ProblemUpdate& probUpdate,
const Statistics& roundStats ) const;
Delegator
increase_delegator( Delegator delegator );
std::string
get_round_type( Delegator delegator );
Delegator
increase_round_if_last_run_was_not_successfull(
const Problem& problem, const ProblemUpdate& probUpdate,
const Statistics& roundStats, bool unchanged );
Delegator
handle_case_exceeded( Delegator& next_round );
PresolveStatus
evaluate_and_apply( const Timer& timer, Problem& problem,
PresolveResult& result,
ProblemUpdate& probUpdate,
const Statistics& oldstats, bool run_sequential );
void
apply_reduction_of_solver( ProblemUpdate& probUpdate,
size_t index_presolver );
void
apply_result_sequential( int index_presolver,
ProblemUpdate& probUpdate,
bool& run_sequential );
void
run_presolvers( const Problem& problem,
const std::pair& presolver_2_run,
ProblemUpdate& probUpdate, bool& run_sequential, const Timer& timer );
bool
is_status_infeasible_or_unbounded( const PresolveStatus& status ) const;
bool
are_only_dual_postsolve_presolvers_enabled();
};
#ifdef PAPILO_USE_EXTERN_TEMPLATES
extern template class Presolve;
extern template class Presolve;
extern template class Presolve;
#endif
/***
* presolves the problem and applies the reductions found by the presolvers
* immediately to it.
* The functions returns the PresolveStatus (Reduced, Unchanged, ) and the
* postsolve information
*
* @tparam REAL: computational accuracy template
* @param problem: the problem to be presolved
* @param store_dual_postsolve: should dual postsolve reductions stored in the postsolve stack
* @return: presolved problem and PresolveResult contains postsolve information
*/
template
PresolveResult
Presolve::apply( Problem& problem, bool store_dual_postsolve )
{
#ifdef PAPILO_TBB
tbb::task_arena arena( presolveOptions.threads == 0
? tbb::task_arena::automatic
: presolveOptions.threads );
#endif
#ifdef PAPILO_TBB
return arena.execute( [this, &problem, store_dual_postsolve]() {
#endif
stats = Statistics();
num.setFeasTol( REAL{ presolveOptions.feastol } );
num.setEpsilon( REAL{ presolveOptions.epsilon } );
num.setHugeVal( REAL{ presolveOptions.hugeval } );
Timer timer( stats.presolvetime );
ConstraintMatrix& constraintMatrix = problem.getConstraintMatrix();
Vec& rhsVals = constraintMatrix.getRightHandSides();
Vec& rflags = constraintMatrix.getRowFlags();
const Vec& rowsize = constraintMatrix.getRowSizes();
msg.info( "\nstarting presolve of problem {}:\n", problem.getName() );
msg.info( " rows: {}\n", problem.getNRows() );
msg.info( " columns: {}\n", problem.getNCols() );
msg.info( " int. columns: {}\n", problem.getNumIntegralCols() );
msg.info( " cont. columns: {}\n", problem.getNumContinuousCols() );
msg.info( " nonzeros: {}\n\n", problem.getConstraintMatrix().getNnz() );
PresolveResult result;
result.postsolve =
PostsolveStorage( problem, num, presolveOptions );
#ifndef PAPILO_TBB
if( presolveOptions.threads != 1 )
msg.warn( "PaPILO without TBB can only use one thread. Number of "
"threads is set to 1\n" );
presolveOptions.threads = 1;
#endif
if( store_dual_postsolve && problem.getNumIntegralCols() == 0 )
{
if( presolveOptions.componentsmaxint == -1 && presolveOptions.detectlindep == 0 &&
are_only_dual_postsolve_presolvers_enabled())
result.postsolve.postsolveType = PostsolveType::kFull;
else
{
msg.error(
"Please turn off the presolvers substitution and sparsify and "
"componentsdetection to use dual postsolving\n" );
return result;
}
}
result.status = PresolveStatus::kUnchanged;
std::stable_sort( presolvers.begin(), presolvers.end(),
[]( const std::unique_ptr>& a,
const std::unique_ptr>& b ) {
return static_cast( a->getTiming() ) <
static_cast( b->getTiming() );
} );
std::pair fastPresolvers;
std::pair mediumPresolvers;
std::pair exhaustivePresolvers;
int npresolvers = static_cast( presolvers.size() );
fastPresolvers.first = fastPresolvers.second = 0;
while( fastPresolvers.second < npresolvers &&
presolvers[fastPresolvers.second]->getTiming() ==
PresolverTiming::kFast )
++fastPresolvers.second;
mediumPresolvers.first = mediumPresolvers.second = fastPresolvers.second;
while( mediumPresolvers.second < npresolvers &&
presolvers[mediumPresolvers.second]->getTiming() ==
PresolverTiming::kMedium )
++mediumPresolvers.second;
exhaustivePresolvers.first = exhaustivePresolvers.second =
mediumPresolvers.second;
while( exhaustivePresolvers.second < npresolvers &&
presolvers[exhaustivePresolvers.second]->getTiming() ==
PresolverTiming::kExhaustive )
++exhaustivePresolvers.second;
reductions.resize( presolvers.size() );
results.resize( presolvers.size() );
round_to_evaluate = Delegator::kFast;
presolverStats.resize( presolvers.size(), std::pair( 0, 0 ) );
ProblemUpdate probUpdate( problem, result.postsolve, stats,
presolveOptions, num, msg );
for( int i = 0; i != npresolvers; ++i )
{
if( presolvers[i]->isEnabled() )
{
if( presolvers[i]->initialize( problem, presolveOptions ) )
probUpdate.observeCompress( presolvers[i].get() );
}
}
result.status = probUpdate.trivialPresolve();
if( result.status == PresolveStatus::kInfeasible ||
result.status == PresolveStatus::kUnbndOrInfeas ||
result.status == PresolveStatus::kUnbounded )
return result;
printRoundStats( false, "Trivial" );
round_to_evaluate = Delegator::kFast;
finishRound( probUpdate );
++stats.nrounds;
nunsuccessful = 0;
rundelayed = true;
for( int i = 0; i < npresolvers; ++i )
{
if( presolvers[i]->isEnabled() && presolvers[i]->isDelayed() )
{
rundelayed = false;
break;
}
}
Statistics last_rounds_stats = stats;
do
{
bool was_executed_sequential = false;
// if problem is trivial abort here
if( probUpdate.getNActiveCols() == 0 ||
probUpdate.getNActiveRows() == 0 )
break;
switch( round_to_evaluate )
{
case Delegator::kFast:
run_presolvers( problem, fastPresolvers, probUpdate,
was_executed_sequential, timer );
break;
case Delegator::kMedium:
run_presolvers( problem, mediumPresolvers, probUpdate,
was_executed_sequential, timer );
break;
case Delegator::kExhaustive:
run_presolvers( problem, exhaustivePresolvers, probUpdate,
was_executed_sequential, timer );
break;
default:
assert( false );
}
result.status =
evaluate_and_apply( timer, problem, result, probUpdate,
last_rounds_stats, was_executed_sequential );
if( is_status_infeasible_or_unbounded( result.status ) )
return result;
last_rounds_stats = stats;
} while( round_to_evaluate != Delegator::kAbort );
if( stats.ntsxapplied > 0 || stats.nboundchgs > 0 ||
stats.ncoefchgs > 0 || stats.ndeletedcols > 0 ||
stats.ndeletedrows > 0 || stats.nsidechgs > 0 )
{
result.status = probUpdate.trivialPresolve();
if( result.status == PresolveStatus::kInfeasible ||
result.status == PresolveStatus::kUnbndOrInfeas ||
result.status == PresolveStatus::kUnbounded )
return result;
probUpdate.clearStates();
probUpdate.check_and_compress();
}
printPresolversStats();
if( DependentRows::Enabled &&
( presolveOptions.detectlindep == 2 ||
( problem.getNumIntegralCols() == 0 &&
presolveOptions.detectlindep == 1 ) ) )
{
ConstraintMatrix& consMatrix = problem.getConstraintMatrix();
Vec equations;
equations.reserve( problem.getNRows() );
size_t eqnnz = 0;
for( int i = 0; i != problem.getNRows(); ++i )
{
if( rflags[i].test( RowFlag::kRedundant ) ||
!rflags[i].test( RowFlag::kEquation ) )
continue;
equations.push_back( i );
eqnnz += rowsize[i] + 1;
}
if( !equations.empty() )
{
DependentRows depRows( equations.size(), problem.getNCols(),
eqnnz );
for( size_t i = 0; i != equations.size(); ++i )
depRows.addRow( i, consMatrix.getRowCoefficients( equations[i] ),
REAL( rhsVals[equations[i]] ) );
Vec dependentEqs;
double factorTime = 0.0;
msg.info( "found {} equations, checking for linear dependency\n",
equations.size() );
{
Timer t{ factorTime };
dependentEqs = depRows.getDependentRows( msg, num );
}
msg.info( "{} equations are redundant, factorization took {} "
"seconds\n",
dependentEqs.size(), factorTime );
if( !dependentEqs.empty() )
{
for( int dependentEq : dependentEqs )
{
probUpdate.markRowRedundant( equations[dependentEq] );
}
probUpdate.flush( true );
}
}
if( presolveOptions.dualreds == 2 )
{
Vec freeCols;
freeCols.reserve( problem.getNCols() );
size_t freeColNnz = 0;
const Vec& cflags = problem.getColFlags();
const Vec& colsize = problem.getColSizes();
const Vec& obj = problem.getObjective().coefficients;
for( int col = 0; col != problem.getNCols(); ++col )
{
if( cflags[col].test( ColFlag::kInactive, ColFlag::kIntegral ) ||
!cflags[col].test( ColFlag::kLbInf ) ||
!cflags[col].test( ColFlag::kUbInf ) )
continue;
freeCols.push_back( col );
freeColNnz += colsize[col] + 1;
}
if( !freeCols.empty() )
{
DependentRows depRows( freeCols.size(), problem.getNRows(),
freeColNnz );
for( size_t i = 0; i != freeCols.size(); ++i )
depRows.addRow(
i, consMatrix.getColumnCoefficients( freeCols[i] ),
obj[freeCols[i]] );
Vec dependentFreeCols;
double factorTime = 0.0;
msg.info(
"found {} free columns, checking for linear dependency\n",
freeCols.size(), freeColNnz );
{
Timer t{ factorTime };
dependentFreeCols = depRows.getDependentRows( msg, num );
}
msg.info( "{} free columns are redundant, factorization took {} "
"seconds\n",
dependentFreeCols.size(), factorTime );
if( !dependentFreeCols.empty() )
{
for( int dependentFreeCol : dependentFreeCols )
probUpdate.fixCol( freeCols[dependentFreeCol], 0 );
probUpdate.flush( true );
}
}
}
}
// finally compress problem fully and release excess storage even if
// problem was not reduced
probUpdate.compress( true );
// check whether problem was reduced
if( stats.ntsxapplied > 0 || stats.nboundchgs > 0 ||
stats.ncoefchgs > 0 || stats.ndeletedcols > 0 ||
stats.ndeletedrows > 0 || stats.nsidechgs > 0 )
{
if( presolveOptions.boundrelax && problem.getNumIntegralCols() == 0 )
{
int nremoved;
int nnewfreevars;
std::tie( nremoved, nnewfreevars ) =
probUpdate.removeRedundantBounds();
if( nremoved != 0 )
msg.info( "removed {} redundant column bounds, got {} new free "
"variables\n",
nremoved, nnewfreevars );
}
bool detectComponents = presolveOptions.componentsmaxint != -1;
if( !lpSolverFactory && problem.getNumContinuousCols() != 0 )
detectComponents = false;
if( !mipSolverFactory && problem.getNumIntegralCols() != 0 )
detectComponents = false;
if( problem.getNCols() == 0 )
detectComponents = false;
if( detectComponents && probUpdate.getNActiveCols() > 0 )
{
assert( problem.getNCols() != 0 && problem.getNRows() != 0 );
Components components;
int ncomponents = components.detectComponents( problem );
if( ncomponents > 1 )
{
const Vec& compInfo =
components.getComponentInfo();
msg.info( "found {} disconnected components\n", ncomponents );
msg.info(
"largest component has {} cols ({} int., {} cont.) and "
"{} nonzeros\n",
compInfo[ncomponents - 1].nintegral +
compInfo[ncomponents - 1].ncontinuous,
compInfo[ncomponents - 1].nintegral,
compInfo[ncomponents - 1].ncontinuous,
compInfo[ncomponents - 1].nnonz );
Solution solution;
solution.primal.resize( problem.getNCols() );
Vec componentSolved( ncomponents );
if( result.postsolve.postsolveType == PostsolveType::kFull )
{
solution.type = SolutionType::kPrimalDual;
solution.reducedCosts.resize( problem.getNCols() );
solution.dual.resize( problem.getNRows() );
solution.varBasisStatus.resize( problem.getNCols() );
}
#ifdef PAPILO_TBB
tbb::parallel_for(
tbb::blocked_range( 0, ncomponents - 1 ),
[this, &components, &solution, &problem, &result, &compInfo,
&componentSolved,
&timer]( const tbb::blocked_range& r ) {
for( int i = r.begin(); i != r.end(); ++i )
#else
for( int i = 0; i < ncomponents - 1; ++i )
#endif
{
if( compInfo[i].nintegral == 0 )
{
std::unique_ptr> solver =
lpSolverFactory->newSolver(
VerbosityLevel::kQuiet );
solver->setUp( problem,
result.postsolve.origrow_mapping,
result.postsolve.origcol_mapping,
components, compInfo[i] );
if( presolveOptions.tlim !=
std::numeric_limits::max() )
{
double tlim =
presolveOptions.tlim - timer.getTime();
if( tlim <= 0 )
break;
solver->setTimeLimit( tlim );
}
solver->solve();
SolverStatus status = solver->getStatus();
if( status == SolverStatus::kOptimal )
{
if( solver->getSolution( components,
compInfo[i].componentid,
solution ) )
componentSolved[compInfo[i].componentid] =
true;
}
}
else if( compInfo[i].nintegral <=
presolveOptions.componentsmaxint )
{
std::unique_ptr> solver =
mipSolverFactory->newSolver(
VerbosityLevel::kQuiet );
solver->setGapLimit( 0 );
solver->setNodeLimit(
problem.getConstraintMatrix().getNnz() /
std::max( compInfo[i].nnonz, 1 ) );
solver->setUp( problem,
result.postsolve.origrow_mapping,
result.postsolve.origcol_mapping,
components, compInfo[i] );
if( presolveOptions.tlim !=
std::numeric_limits::max() )
{
double tlim =
presolveOptions.tlim - timer.getTime();
if( tlim <= 0 )
break;
solver->setTimeLimit( tlim );
}
solver->solve();
SolverStatus status = solver->getStatus();
if( status == SolverStatus::kOptimal )
{
if( solver->getSolution( components,
compInfo[i].componentid,
solution ) )
componentSolved[compInfo[i].componentid] =
true;
}
}
}
#ifdef PAPILO_TBB
}
,tbb::simple_partitioner() );
#endif
int nsolved = 0;
int oldndelcols = stats.ndeletedcols;
int oldndelrows = stats.ndeletedrows;
auto& lbs = problem.getLowerBounds();
auto& ubs = problem.getUpperBounds();
for( int i = 0; i != ncomponents; ++i )
{
if( componentSolved[i] )
{
++nsolved;
const int* compcols = components.getComponentsCols( i );
int numcompcols = components.getComponentsNumCols( i );
for( int j = 0; j != numcompcols; ++j )
{
const int col = compcols[j];
lbs[compcols[j]] = solution.primal[col];
ubs[compcols[j]] = solution.primal[col];
probUpdate.markColFixed( col );
if( result.postsolve.postsolveType ==
PostsolveType::kFull )
result.postsolve.storeDualValue(
true, col, solution.reducedCosts[col] );
}
const int* comprows = components.getComponentsRows( i );
int numcomprows = components.getComponentsNumRows( i );
for( int j = 0; j != numcomprows; ++j )
{
probUpdate.markRowRedundant( comprows[j] );
}
}
}
if( nsolved != 0 )
{
if( probUpdate.flush( true ) == PresolveStatus::kInfeasible )
assert( false );
probUpdate.compress();
msg.info( "solved {} components: {} cols fixed, {} rows "
"deleted\n",
nsolved, stats.ndeletedcols - oldndelcols,
stats.ndeletedrows - oldndelrows );
}
}
}
logStatus( problem, result.postsolve );
result.status = PresolveStatus::kReduced;
if( result.postsolve.postsolveType == PostsolveType::kFull )
{
auto& coefficients = problem.getObjective().coefficients;
auto& col_lower = problem.getLowerBounds();
auto& col_upper = problem.getUpperBounds();
auto& row_lhs = problem.getConstraintMatrix().getLeftHandSides();
auto& row_rhs = problem.getConstraintMatrix().getRightHandSides();
auto& row_flags = problem.getRowFlags();
auto& col_flags = problem.getColFlags();
result.postsolve.storeReducedBoundsAndCost(
col_lower, col_upper, row_lhs, row_rhs, coefficients, row_flags,
col_flags );
}
return result;
}
logStatus( problem, result.postsolve );
// problem was not changed
result.status = PresolveStatus::kUnchanged;
return result;
#ifdef PAPILO_TBB
} );
#endif
}
template
void
Presolve::run_presolvers( const Problem& problem,
const std::pair& presolver_2_run,
ProblemUpdate& probUpdate,
bool& run_sequential, const Timer& timer )
{
#ifndef PAPILO_TBB
assert(presolveOptions.runs_sequential() == true);
#endif
if( presolveOptions.runs_sequential() &&
presolveOptions.apply_results_immediately_if_run_sequentially )
{
probUpdate.setPostponeSubstitutions( false );
for( int i = presolver_2_run.first; i != presolver_2_run.second; ++i )
{
results[i] =
presolvers[i]->run( problem, probUpdate, num, reductions[i], timer );
apply_result_sequential( i, probUpdate, run_sequential );
if( results[i] == PresolveStatus::kInfeasible )
return;
if( problem.getNRows() == 0 || problem.getNCols() == 0 )
return;
}
PresolveStatus status = probUpdate.trivialPresolve();
if( is_status_infeasible_or_unbounded( status ) )
{
results[presolver_2_run.first] = status;
return;
}
probUpdate.clearStates();
probUpdate.check_and_compress();
}
#ifdef PAPILO_TBB
else
{
tbb::parallel_for(
tbb::blocked_range( presolver_2_run.first,
presolver_2_run.second ),
[&]( const tbb::blocked_range& r ) {
for( int i = r.begin(); i != r.end(); ++i )
{
results[i] = presolvers[i]->run( problem, probUpdate, num,
reductions[i], timer );
}
},
tbb::simple_partitioner() );
}
#endif
}
template
void
Presolve::apply_result_sequential( int index_presolver,
ProblemUpdate& probUpdate,
bool& run_sequential )
{
run_sequential = true;
apply_reduction_of_solver( probUpdate, index_presolver );
probUpdate.flushChangedCoeffs();
if( probUpdate.flush( false ) == PresolveStatus::kInfeasible )
{
results[index_presolver] = PresolveStatus::kInfeasible;
return;
}
probUpdate.clearStates();
}
template
Delegator
Presolve::determine_next_round( Problem& problem,
ProblemUpdate& probUpdate,
const Statistics& roundStats,
const Timer& presolvetimer,
bool unchanged )
{
if( is_time_exceeded( presolvetimer ) )
return Delegator::kAbort;
Delegator next_round = increase_round_if_last_run_was_not_successfull(
problem, probUpdate, roundStats, unchanged );
next_round = handle_case_exceeded( next_round );
assert( next_round != Delegator::kExceeded );
return next_round;
}
template
PresolveStatus
Presolve::evaluate_and_apply( const Timer& timer, Problem& problem,
PresolveResult& result,
ProblemUpdate& probUpdate,
const Statistics& oldstats,
bool run_sequential )
{
if( round_to_evaluate == Delegator::kFast )
{
probUpdate.clearChangeInfo();
lastRoundReduced = false;
}
result.status = evaluateResults();
switch( result.status )
{
case PresolveStatus::kUnbndOrInfeas:
case PresolveStatus::kUnbounded:
case PresolveStatus::kInfeasible:
printPresolversStats();
return result.status;
case PresolveStatus::kUnchanged:
round_to_evaluate = determine_next_round(
problem, probUpdate, ( stats - oldstats ), timer, true );
return result.status;
case PresolveStatus::kReduced:
// problem reductions where found by at least one presolver
PresolveStatus status;
if( !run_sequential )
status = apply_all_presolver_reductions( probUpdate );
else
status = PresolveStatus::kReduced;
if( is_status_infeasible_or_unbounded( status ) )
return status;
round_to_evaluate = determine_next_round( problem, probUpdate,
( stats - oldstats ), timer );
finishRound( probUpdate );
return status;
}
return result.status;
}
template
bool
Presolve::is_status_infeasible_or_unbounded(
const PresolveStatus& status ) const
{
return status == PresolveStatus::kUnbndOrInfeas ||
status == PresolveStatus::kUnbounded ||
status == PresolveStatus::kInfeasible;
}
template
PresolveStatus
Presolve::apply_all_presolver_reductions(
ProblemUpdate& probUpdate )
{
probUpdate.setPostponeSubstitutions( true );
postponedReductionToPresolver.push_back( 0 );
for( std::size_t i = 0; i < presolvers.size(); ++i )
{
apply_reduction_of_solver( probUpdate, i );
postponedReductionToPresolver.push_back( postponedReductions.size() );
}
PresolveStatus status = evaluateResults();
if( is_status_infeasible_or_unbounded( status ) )
return status;
probUpdate.flushChangedCoeffs();
applyPostponed( probUpdate );
return probUpdate.flush( true );
}
template
void
Presolve::apply_reduction_of_solver( ProblemUpdate& probUpdate,
size_t index_presolver )
{
if( results[index_presolver] != PresolveStatus::kReduced )
return;
Message::debug( this, "applying reductions of presolver {}\n",
presolvers[index_presolver]->getName() );
auto statistics = applyReductions( index_presolver,
reductions[index_presolver], probUpdate );
// if infeasible it returns -1 -1
if( statistics.first >= 0 && statistics.second >= 0 )
{
presolverStats[index_presolver].first += statistics.first;
presolverStats[index_presolver].second += statistics.second;
}
else
results[index_presolver] = PresolveStatus::kInfeasible;
}
template
std::pair
Presolve::applyReductions( int p, const Reductions& reductions_,
ProblemUpdate& probUpdate )
{
int k = 0;
ApplyResult result;
int nbtsxAppliedStart = stats.ntsxapplied;
int nbtsxTotal = 0;
const auto& reds = reductions_.getReductions();
msg.detailed( "Presolver {} applying \n", presolvers[p]->getName() );
for( const auto& transaction : reductions_.getTransactions() )
{
int start = transaction.start;
int end = transaction.end;
for( ; k != start; ++k )
{
result = probUpdate.applyTransaction( &reds[k], &reds[k + 1] );
if( result == ApplyResult::kApplied )
++stats.ntsxapplied;
else if( result == ApplyResult::kRejected )
++stats.ntsxconflicts;
else if( result == ApplyResult::kInfeasible )
return std::make_pair( -1, -1 );
else if( result == ApplyResult::kPostponed )
postponedReductions.emplace_back( &reds[k], &reds[k + 1] );
++nbtsxTotal;
}
result = probUpdate.applyTransaction( &reds[start], &reds[end] );
if( result == ApplyResult::kApplied )
++stats.ntsxapplied;
else if( result == ApplyResult::kRejected )
++stats.ntsxconflicts;
else if( result == ApplyResult::kInfeasible )
return std::make_pair( -1, -1 );
else if( result == ApplyResult::kPostponed )
postponedReductions.emplace_back( &reds[start], &reds[end] );
k = end;
++nbtsxTotal;
}
for( ; k != static_cast( reds.size() ); ++k )
{
result = probUpdate.applyTransaction( &reds[k], &reds[k + 1] );
if( result == ApplyResult::kApplied )
++stats.ntsxapplied;
else if( result == ApplyResult::kRejected )
++stats.ntsxconflicts;
else if( result == ApplyResult::kInfeasible )
return std::make_pair( -1, -1 );
else if( result == ApplyResult::kPostponed )
postponedReductions.emplace_back( &reds[k], &reds[k + 1] );
++nbtsxTotal;
}
return { nbtsxTotal, ( stats.ntsxapplied - nbtsxAppliedStart ) };
}
template
void
Presolve::applyPostponed( ProblemUpdate& probUpdate )
{
probUpdate.setPostponeSubstitutions( false );
for( int presolver = 0; presolver != (int) presolvers.size(); ++presolver )
{
int first = postponedReductionToPresolver[presolver];
int last = postponedReductionToPresolver[presolver + 1];
if( first < last )
msg.detailed( "Presolver {} applying \n",
presolvers[presolver]->getName() );
for( int i = first; i != last; ++i )
{
const auto& ptrpair = postponedReductions[i];
ApplyResult r =
probUpdate.applyTransaction( ptrpair.first, ptrpair.second );
if( r == ApplyResult::kApplied )
{
++stats.ntsxapplied;
++presolverStats[presolver].second;
}
else if( r == ApplyResult::kRejected )
++stats.ntsxconflicts;
}
}
postponedReductions.clear();
postponedReductionToPresolver.clear();
}
template
void
Presolve::finishRound( ProblemUpdate& probUpdate )
{
probUpdate.clearStates();
probUpdate.check_and_compress();
for( auto& reduction : reductions )
reduction.clear();
std::fill( results.begin(), results.end(), PresolveStatus::kUnchanged );
}
template
Delegator
Presolve::handle_case_exceeded( Delegator& next_round )
{
if( next_round != Delegator::kExceeded )
return next_round;
++nunsuccessful;
if( !( rundelayed && ( !lastRoundReduced || nunsuccessful == 2 ) ) )
{
printRoundStats( !lastRoundReduced, "Exhaustive" );
if( !rundelayed )
{
msg.info( "activating delayed presolvers\n" );
for( auto& p : presolvers )
p->setDelayed( false );
rundelayed = true;
}
++stats.nrounds;
return Delegator::kFast;
}
printRoundStats( !lastRoundReduced, get_round_type( next_round ) );
return Delegator::kAbort;
}
template
bool
Presolve::is_time_exceeded( const Timer& presolvetimer ) const
{
return presolveOptions.tlim != std::numeric_limits::max() &&
presolvetimer.getTime() >= presolveOptions.tlim;
}
template
bool
Presolve::is_only_slighlty_changes( const Problem& problem,
const ProblemUpdate& probUpdate,
const Statistics& roundStats ) const
{
double abort_factor = problem.getNumIntegralCols() == 0
? presolveOptions.lpabortfac
: presolveOptions.abortfac;
return ( 0.1 * roundStats.nboundchgs + roundStats.ndeletedcols ) <=
abort_factor * probUpdate.getNActiveCols() &&
( roundStats.nsidechgs + roundStats.ndeletedrows ) <=
abort_factor * probUpdate.getNActiveRows() &&
( roundStats.ncoefchgs <=
abort_factor * problem.getConstraintMatrix().getNnz() );
}
template
Delegator
Presolve::increase_round_if_last_run_was_not_successfull(
const Problem& problem, const ProblemUpdate& probUpdate,
const Statistics& roundStats, bool unchanged )
{
Delegator next_round;
if( !unchanged )
{
if( is_only_slighlty_changes( problem, probUpdate, roundStats ) )
{
lastRoundReduced =
lastRoundReduced || roundStats.nsidechgs > 0 ||
roundStats.nboundchgs > 0 || roundStats.ndeletedcols > 0 ||
roundStats.ndeletedrows > 0 || roundStats.ncoefchgs > 0;
next_round = increase_delegator( round_to_evaluate );
}
else
{
printRoundStats( false, get_round_type( round_to_evaluate ) );
lastRoundReduced = true;
next_round = Delegator::kFast;
nunsuccessful = 0;
++stats.nrounds;
}
}
else
next_round = increase_delegator( round_to_evaluate );
return next_round;
}
template
Delegator
Presolve::increase_delegator( Delegator delegator )
{
switch( delegator )
{
case Delegator::kFast:
return Delegator::kMedium;
case Delegator::kMedium:
return Delegator::kExhaustive;
case Delegator::kAbort:
case Delegator::kExhaustive:
case Delegator::kExceeded:
break;
}
return Delegator::kExceeded;
}
template
PresolveStatus
Presolve::evaluateResults()
{
int largestValue = static_cast( PresolveStatus::kUnchanged );
for( auto& i : results )
largestValue = std::max( largestValue, static_cast( i ) );
return static_cast( largestValue );
}
template
void
Presolve::printRoundStats( bool unchanged, std::string rndtype )
{
if( unchanged )
{
msg.info( "round {:<3} ({:^10}): Unchanged\n", stats.nrounds, rndtype );
return;
}
msg.info( "round {:<3} ({:^10}): {:>4} del cols, {:>4} del rows, "
"{:>4} chg bounds, {:>4} chg sides, {:>4} chg coeffs, "
"{:>4} tsx applied, {:>4} tsx conflicts\n",
stats.nrounds, rndtype, stats.ndeletedcols, stats.ndeletedrows,
stats.nboundchgs, stats.nsidechgs, stats.ncoefchgs,
stats.ntsxapplied, stats.ntsxconflicts );
}
template
void
Presolve::printPresolversStats()
{
msg.info( "presolved {} rounds: {:>4} del cols, {:>4} del rows, "
"{:>4} chg bounds, {:>4} chg sides, {:>4} chg coeffs, "
"{:>4} tsx applied, {:>4} tsx conflicts\n",
stats.nrounds, stats.ndeletedcols, stats.ndeletedrows,
stats.nboundchgs, stats.nsidechgs, stats.ncoefchgs,
stats.ntsxapplied, stats.ntsxconflicts );
msg.info( "\n {:>18} {:>12} {:>18} {:>18} {:>18} {:>18} \n", "presolver",
"nb calls", "success calls(%)", "nb transactions",
"tsx applied(%)", "execution time(s)" );
for( std::size_t i = 0; i < presolvers.size(); ++i )
{
presolvers[i]->printStats( msg, presolverStats[i] );
}
msg.info( "\n" );
}
template
void
Presolve::logStatus( const Problem& problem,
const PostsolveStorage& postsolveStorage ) const
{
if(msg.getVerbosityLevel() == VerbosityLevel::kQuiet)
return;
msg.info( "reduced problem:\n" );
msg.info( " reduced rows: {}\n", problem.getNRows() );
msg.info( " reduced columns: {}\n", problem.getNCols() );
msg.info( " reduced int. columns: {}\n", problem.getNumIntegralCols() );
msg.info( " reduced cont. columns: {}\n", problem.getNumContinuousCols() );
msg.info( " reduced nonzeros: {}\n",
problem.getConstraintMatrix().getNnz() );
if( problem.getNCols() == 0)
{
// the primaldual can be disabled therefore calculate only primal for obj
Solution solution{};
SolutionType type = postsolveStorage.postsolveType == PostsolveType::kFull
? SolutionType::kPrimalDual
: SolutionType::kPrimal;
Solution empty_sol{ type };
Postsolve postsolve{ msg, num };
postsolve.undo( empty_sol, solution, postsolveStorage );
const Problem& origprob = postsolveStorage.getOriginalProblem();
REAL origobj = origprob.computeSolObjective( solution.primal );
msg.info(
"problem is solved [optimal solution found] [objective value: {} (double precision)]\n",
(double) origobj );
}
}
template
std::string
Presolve::get_round_type( Delegator delegator )
{
switch( delegator )
{
case Delegator::kFast:
return "Fast";
case Delegator::kMedium:
return "Medium";
case Delegator::kExhaustive:
return "Exhaustive";
case Delegator::kExceeded:
return "Final";
case Delegator::kAbort:
break;
}
return "Undefined";
}
template
bool
Presolve::are_only_dual_postsolve_presolvers_enabled()
{
for( int i = 0; i < (int) presolvers.size(); i++ )
{
if( presolvers[i]->isEnabled() )
{
if( presolvers[i]->getName().compare( "substitution" ) == 0 ||
presolvers[i]->getName().compare( "sparsify" ) == 0 ||
presolvers[i]->getName().compare( "dualinfer" ) == 0 ||
presolvers[i]->getName().compare( "doubletoneq" ) == 0 )
return false;
}
}
return true;
}
} // namespace papilo
#endif