/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* */
/* 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_DUAL_INFER_HPP_
#define _PAPILO_PRESOLVERS_DUAL_INFER_HPP_
#include "papilo/core/PresolveMethod.hpp"
#include "papilo/core/Problem.hpp"
#include "papilo/core/ProblemUpdate.hpp"
#include "papilo/core/SingleRow.hpp"
namespace papilo
{
template
class DualInfer : public PresolveMethod
{
public:
DualInfer() : PresolveMethod()
{
this->setName( "dualinfer" );
this->setTiming( PresolverTiming::kExhaustive );
this->setType( PresolverType::kContinuousCols );
}
bool
initialize( const Problem& problem,
const PresolveOptions& presolveOptions ) override
{
if( presolveOptions.dualreds == 0 )
this->setEnabled( false );
return false;
}
virtual 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 DualInfer;
extern template class DualInfer;
extern template class DualInfer;
#endif
template
PresolveStatus
DualInfer::execute( const Problem& problem,
const ProblemUpdate& problemUpdate,
const Num& num, Reductions& reductions, const Timer& timer )
{
assert( problem.getNumContinuousCols() != 0 );
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& colsize = consMatrix.getColSizes();
const int ncols = problem.getNCols();
const int nrows = problem.getNRows();
const auto& activities = problem.getRowActivities();
PresolveStatus result = PresolveStatus::kUnchanged;
// get called less and less over time regardless of success since the
// presolver can be expensive otherwise
this->skipRounds( this->getNCalls() );
Vec> activitiesCopy( activities );
auto checkNonImpliedBounds = [&]( int col, bool& lbinf, bool& ubinf ) {
const REAL& lb = lbValues[col];
const REAL& ub = ubValues[col];
ColFlags colf = cflags[col];
auto colvec = consMatrix.getColumnCoefficients( col );
const int len = colvec.getLength();
const int* inds = colvec.getIndices();
const REAL* vals = colvec.getValues();
int i = 0;
while(
( !colf.test( ColFlag::kLbInf ) || !colf.test( ColFlag::kUbInf ) ) &&
i != len )
{
int row = inds[i];
assert( !consMatrix.isRowRedundant( row ) );
const REAL& lhs = lhsValues[row];
const REAL& rhs = rhsValues[row];
if( !colf.test( ColFlag::kUbInf ) &&
row_implies_UB( num, lhs, rhs, rflags[row], activitiesCopy[row],
vals[i], lb, ub, colf ) )
{
colf.set( ColFlag::kUbInf );
if( !colf.test( ColFlag::kUbHuge ) )
update_activities_remove_finite_bound(
inds, vals, len, BoundChange::kUpper, ub, activitiesCopy );
}
if( !colf.test( ColFlag::kLbInf ) &&
row_implies_LB( num, lhs, rhs, rflags[row], activitiesCopy[row],
vals[i], lb, ub, colf ) )
{
colf.set( ColFlag::kLbInf );
if( !colf.test( ColFlag::kLbHuge ) )
update_activities_remove_finite_bound(
inds, vals, len, BoundChange::kLower, lb, activitiesCopy );
}
++i;
}
lbinf = colf.test( ColFlag::kLbInf );
ubinf = colf.test( ColFlag::kUbInf );
};
// initialize dual variable domains
Vec dualLB;
Vec dualUB;
Vec dualColFlags( nrows );
dualLB.resize( nrows, REAL{ 0 } );
dualUB.resize( nrows, REAL{ 0 } );
for( int i = 0; i != nrows; ++i )
{
if( consMatrix.isRowRedundant( i ) )
continue;
assert( consMatrix.getRowSizes()[i] > 0 );
if( !rflags[i].test( RowFlag::kRhsInf ) )
dualColFlags[i].set( ColFlag::kLbInf );
if( !rflags[i].test( RowFlag::kLhsInf ) )
dualColFlags[i].set( ColFlag::kUbInf );
}
// initialize dual constraint sides, mark constraints of integral columns
// redundant to skip them for propagation
const Vec& dualLHS = obj;
const Vec& dualRHS = obj;
Vec dualRowFlags( ncols );
Vec> checkRedundantBounds;
checkRedundantBounds.reserve( ncols );
const Vec& colperm = problemUpdate.getRandomColPerm();
for( int c = 0; c != ncols; ++c )
{
assert( !dualRowFlags[c].test( RowFlag::kLhsInf ) );
assert( !dualRowFlags[c].test( RowFlag::kRhsInf ) );
if( cflags[c].test( ColFlag::kIntegral, ColFlag::kInactive ) ||
colsize[c] == 0 )
{
dualRowFlags[c].set( RowFlag::kRhsInf );
dualRowFlags[c].set( RowFlag::kLhsInf );
dualRowFlags[c].set( RowFlag::kRedundant );
continue;
}
int64_t colweight = int64_t( !cflags[c].test( ColFlag::kLbInf ) +
!cflags[c].test( ColFlag::kUbInf ) ) *
colsize[c] * ncols +
colperm[c];
checkRedundantBounds.emplace_back(
int( std::min( int64_t( std::numeric_limits::max() ),
colweight ) ),
c );
}
pdqsort( checkRedundantBounds.begin(), checkRedundantBounds.end(),
[]( std::pair c1, std::pair c2 ) {
return c1.first < c2.first;
} );
for( std::pair pair : checkRedundantBounds )
{
int c = pair.second;
assert( colsize[c] > 0 );
bool lbinf;
bool ubinf;
checkNonImpliedBounds( c, lbinf, ubinf );
if( !lbinf && !ubinf )
{
dualRowFlags[c].set( RowFlag::kRhsInf );
dualRowFlags[c].set( RowFlag::kLhsInf );
dualRowFlags[c].set( RowFlag::kRedundant );
continue;
}
if( !lbinf )
dualRowFlags[c].set( RowFlag::kLhsInf );
if( !ubinf )
dualRowFlags[c].set( RowFlag::kRhsInf );
}
// compute initial activities, skip redundant rows
Vec> dualActivities( ncols );
Vec changedActivity;
Vec nextChangedActivity;
auto checkRedundancy = [&]( int dualRow ) {
if( ( dualRowFlags[dualRow].test( RowFlag::kLhsInf ) ||
( dualActivities[dualRow].ninfmin == 0 &&
num.isFeasGE( dualActivities[dualRow].min,
dualLHS[dualRow] ) ) ) &&
( dualRowFlags[dualRow].test( RowFlag::kRhsInf ) ||
( dualActivities[dualRow].ninfmax == 0 &&
num.isFeasLE( dualActivities[dualRow].max,
dualRHS[dualRow] ) ) ) )
return true;
return false;
};
for( int i = 0; i != ncols; ++i )
{
if( dualRowFlags[i].test( RowFlag::kRedundant ) )
{
dualActivities[i].min = 0;
dualActivities[i].max = 0;
dualActivities[i].ninfmin = colsize[i];
dualActivities[i].ninfmax = colsize[i];
continue;
}
auto colvec = consMatrix.getColumnCoefficients( i );
dualActivities[i] = compute_row_activity(
colvec.getValues(), colvec.getIndices(), colvec.getLength(), dualLB,
dualUB, dualColFlags );
if( checkRedundancy( i ) )
{
dualRowFlags[i].set( RowFlag::kRedundant );
continue;
}
if( ( dualActivities[i].ninfmax <= 1 &&
!dualRowFlags[i].test( RowFlag::kLhsInf ) ) ||
( dualActivities[i].ninfmin <= 1 &&
!dualRowFlags[i].test( RowFlag::kRhsInf ) ) )
changedActivity.push_back( i );
}
// do domain propagation
int nrounds = 0;
auto boundChanged = [&]( BoundChange boundChg, int dualCol, REAL newbound, int dualRow ) {
auto rowvec = consMatrix.getRowCoefficients( dualCol );
bool oldboundinf;
REAL oldbound;
if( boundChg == BoundChange::kLower )
{
oldboundinf = dualColFlags[dualCol].test( ColFlag::kLbInf );
oldbound = dualLB[dualCol];
// check against other bound for infeasibility
if( !dualColFlags[dualCol].test( ColFlag::kUbInf ) )
{
REAL bnddist = dualUB[dualCol] - newbound;
// bound exceeded by more then feastol means infeasible
if( bnddist < -num.getFeasTol() )
{
result = PresolveStatus::kUnbndOrInfeas;
return;
}
// bound is equal, or almost equal
if( bnddist <= num.getFeasTol() )
{
bool forcingrow = true;
if( bnddist > 0 )
{
// bound is almost equal, check if fixing the column is
// numerically negligible
auto dualColVec = consMatrix.getRowCoefficients( dualCol );
REAL maxabsval = dualColVec.getMaxAbsValue();
if( bnddist * maxabsval > num.getFeasTol() )
forcingrow = false;
}
// fix column
if( forcingrow )
newbound = dualUB[dualCol];
}
}
// reject too small bound changes
if( !oldboundinf && newbound - oldbound <= +1000 * num.getFeasTol() )
return;
dualColFlags[dualCol].unset( ColFlag::kLbInf );
dualLB[dualCol] = newbound;
assert( dualColFlags[dualCol].test( ColFlag::kUbInf ) ||
dualLB[dualCol] <= dualUB[dualCol] );
}
else
{
oldboundinf = dualColFlags[dualCol].test( ColFlag::kUbInf );
oldbound = dualUB[dualCol];
// check against other bound for infeasibility
if( !dualColFlags[dualCol].test( ColFlag::kLbInf ) )
{
REAL bnddist = newbound - dualLB[dualCol];
// bound exceeded by more then feastol means infeasible
if( bnddist < -num.getFeasTol() )
{
result = PresolveStatus::kUnbndOrInfeas;
return;
}
if( bnddist <= num.getFeasTol() )
{
bool forcingrow = true;
if( bnddist > 0 )
{
auto dualColVec = consMatrix.getRowCoefficients( dualCol );
REAL maxabsval = dualColVec.getMaxAbsValue();
if( bnddist * maxabsval > num.getFeasTol() )
forcingrow = false;
}
if( forcingrow )
newbound = dualLB[dualCol];
}
}
// reject too small bound changes
if( !oldboundinf && newbound - oldbound >= -1000 * num.getFeasTol() )
return;
dualColFlags[dualCol].unset( ColFlag::kUbInf );
dualUB[dualCol] = newbound;
assert( dualColFlags[dualCol].test( ColFlag::kLbInf ) ||
dualUB[dualCol] >= dualLB[dualCol] );
}
const REAL* vals = rowvec.getValues();
const int* inds = rowvec.getIndices();
const int len = rowvec.getLength();
for( int i = 0; i != len; ++i )
{
int dual_row = inds[i];
if( dualRowFlags[dual_row].test( RowFlag::kRedundant ) )
continue;
RowActivity& activity = dualActivities[dual_row];
ActivityChange actChange = update_activity_after_boundchange(
vals[i], boundChg, oldbound, newbound, oldboundinf, activity );
if( checkRedundancy( dual_row ) )
{
dualRowFlags[dual_row].set( RowFlag::kRedundant );
continue;
}
if( activity.lastchange != nrounds )
{
if( actChange == ActivityChange::kMin &&
!dualRowFlags[dual_row].test( RowFlag::kRhsInf ) &&
activity.ninfmin <= 1 )
{
activity.lastchange = nrounds;
nextChangedActivity.push_back( dual_row );
}
else if( actChange == ActivityChange::kMax &&
!dualRowFlags[dual_row].test( RowFlag::kLhsInf ) &&
activity.ninfmax <= 1 )
{
activity.lastchange = nrounds;
nextChangedActivity.push_back( dual_row );
}
}
}
};
using std::swap;
while( !changedActivity.empty() )
{
Message::debug( this, "dual progation round {} on {} dual rows\n",
nrounds, changedActivity.size() );
for( int dualRow : changedActivity )
{
if( dualRowFlags[dualRow].test( RowFlag::kRedundant ) )
continue;
auto colvec = consMatrix.getColumnCoefficients( dualRow );
propagate_row( dualRow, colvec.getValues(), colvec.getIndices(),
colvec.getLength(), dualActivities[dualRow],
dualLHS[dualRow], dualRHS[dualRow],
dualRowFlags[dualRow], dualLB, dualUB, dualColFlags,
boundChanged );
if( result == PresolveStatus::kUnbndOrInfeas )
return result;
}
swap( changedActivity, nextChangedActivity );
nextChangedActivity.clear();
++nrounds;
}
// analyze dual domains:
int impliedeqs = 0;
int fixedints = 0;
int fixedconts = 0;
// change inequality rows to equations if their dual value cannot be zero
for( int i = 0; i < nrows; ++i )
{
if( consMatrix.isRowRedundant( i ) )
continue;
if( !rflags[i].test( RowFlag::kLhsInf ) &&
!rflags[i].test( RowFlag::kRhsInf ) && lhsValues[i] == rhsValues[i] )
continue;
if( ( !dualColFlags[i].test( ColFlag::kLbInf ) &&
num.isFeasGT( dualLB[i], 0 ) ) )
{
assert( !rflags[i].test( RowFlag::kLhsInf ) );
if( activities[i].ninfmax != 0 ||
num.isFeasLT( lhsValues[i], activities[i].max ) )
{
TransactionGuard tg{ reductions };
reductions.lockRow( i );
reductions.changeRowRHS( i, lhsValues[i] );
++impliedeqs;
}
}
else if( !dualColFlags[i].test( ColFlag::kUbInf ) &&
num.isFeasLT( dualUB[i], 0 ) )
{
assert( !rflags[i].test( RowFlag::kRhsInf ) );
if( activities[i].ninfmin != 0 ||
num.isFeasGT( rhsValues[i], activities[i].min ) )
{
TransactionGuard tg{ reductions };
reductions.lockRow( i );
reductions.changeRowLHS( i, rhsValues[i] );
++impliedeqs;
}
}
}
// use dualActivities to compute reduced cost bounds and use them to fix
// variables
for( int i = 0; i < ncols; ++i )
{
if( colsize[i] <= 0 )
continue;
if( dualRowFlags[i].test( RowFlag::kRedundant ) )
{
auto colvec = consMatrix.getColumnCoefficients( i );
dualActivities[i] = compute_row_activity(
colvec.getValues(), colvec.getIndices(), colvec.getLength(),
dualLB, dualUB, dualColFlags );
}
if( dualActivities[i].ninfmax == 0 &&
num.isFeasLT( dualActivities[i].max, obj[i] ) &&
num.isSafeLT( dualActivities[i].max, obj[i] ) )
{
if( cflags[i].test( ColFlag::kLbInf ) )
return PresolveStatus::kUnbndOrInfeas;
TransactionGuard tg{ reductions };
reductions.lockColBounds( i );
reductions.fixCol( i, lbValues[i] );
if( cflags[i].test( ColFlag::kIntegral ) )
++fixedints;
else
++fixedconts;
}
else if( dualActivities[i].ninfmin == 0 &&
num.isFeasGT( dualActivities[i].min, obj[i] ) &&
num.isSafeGT( dualActivities[i].min, obj[i] ) )
{
if( cflags[i].test(ColFlag::kUbInf))
return PresolveStatus::kUnbndOrInfeas;
TransactionGuard tg{reductions};
reductions.lockColBounds(i);
reductions.fixCol(i, ubValues[i]);
if( cflags[i].test( ColFlag::kIntegral ) )
++fixedints;
else
++fixedconts;
}
}
// set result if reductions where found
if( impliedeqs > 0 || fixedints > 0 || fixedconts > 0 )
result = PresolveStatus::kReduced;
return result;
}
} // namespace papilo
#endif