/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* */
/* 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_PROBLEM_UPDATE_HPP_
#define _PAPILO_CORE_PROBLEM_UPDATE_HPP_
#include "papilo/core/MatrixBuffer.hpp"
#include "papilo/core/PresolveMethod.hpp"
#include "papilo/core/PresolveOptions.hpp"
#include "papilo/core/Problem.hpp"
#include "papilo/core/Reductions.hpp"
#include "papilo/core/SingleRow.hpp"
#include "papilo/core/Statistics.hpp"
#include "papilo/core/postsolve/PostsolveStorage.hpp"
#include "papilo/misc/Flags.hpp"
#include "papilo/misc/MultiPrecision.hpp"
#include "papilo/misc/Num.hpp"
#include
#include
#include "boost/random.hpp"
namespace papilo
{
enum class ConflictType
{
kNoConflict,
kConflict,
kPostpone
};
enum class ApplyResult
{
kApplied,
kRejected,
kPostponed,
kInfeasible
};
template
class ProblemUpdate
{
Problem& problem;
PostsolveStorage& postsolve;
Statistics& stats;
const PresolveOptions& presolveOptions;
const Num& num;
const Message& msg;
bool postponeSubstitutions;
Vec dirty_row_states;
Vec dirty_col_states;
Vec deleted_cols;
Vec redundant_rows;
Vec changed_activities;
Vec singletonRows;
Vec singletonColumns;
Vec emptyColumns;
int firstNewSingletonCol;
MatrixBuffer matrix_buffer;
Vec intbuffer;
Vec realbuffer;
Vec> tripletbuffer;
Vec*> compress_observers;
Vec random_col_perm;
Vec random_row_perm;
int lastcompress_ndelcols;
int lastcompress_ndelrows;
enum class State : uint8_t
{
kUnmodified = 0,
kModified = 1 << 1,
kBoundsModified = 1 << 2,
};
Vec> row_state;
Vec> col_state;
template
void
setColState( int col, Args... flags )
{
assert( col >= 0 && col < problem.getNCols() );
if( col_state[col].equal( State::kUnmodified ) )
dirty_col_states.push_back( col );
col_state[col].set( flags... );
}
template
void
setRowState( int row, Args... flags )
{
assert( row >= 0 && row < problem.getNRows() );
// check that equation flag is set correctly
assert(
problem.getRowFlags()[row].test( RowFlag::kRedundant ) ||
( !problem.getRowFlags()[row].test( RowFlag::kEquation ) &&
( problem.getRowFlags()[row].test( RowFlag::kLhsInf,
RowFlag::kRhsInf ) ||
problem.getConstraintMatrix().getLeftHandSides()[row] !=
problem.getConstraintMatrix().getRightHandSides()[row] ) ) ||
( problem.getRowFlags()[row].test( RowFlag::kEquation ) &&
!problem.getRowFlags()[row].test( RowFlag::kLhsInf,
RowFlag::kRhsInf ) &&
problem.getConstraintMatrix().getLeftHandSides()[row] ==
problem.getConstraintMatrix().getRightHandSides()[row] ) );
if( row_state[row].equal( State::kUnmodified ) )
dirty_row_states.push_back( row );
row_state[row].set( flags... );
}
public:
ProblemUpdate( Problem& problem, PostsolveStorage& postsolve,
Statistics& stats, const PresolveOptions& presolveOptions,
const Num& num, const Message& msg );
void
setPostponeSubstitutions( bool value )
{
this->postponeSubstitutions = value;
}
void
update_activity( ActivityChange actChange, int rowid,
RowActivity& activity );
PresolveStatus
fixCol( int col, REAL val );
PresolveStatus
fixColInfinity( int col, REAL val );
PresolveStatus
changeLB( int col, REAL val );
void
merge_parallel_columns(
int col1, int col2, REAL col2scale,
ConstraintMatrix& constraintMatrix, Vec& lbs, Vec& ubs,
Vec& cflags );
ConstraintMatrix&
getConstraintMatrix()
{
return problem.getConstraintMatrix();
}
Problem&
getProblem()
{
return problem;
}
void
clearDeletedCols()
{
deleted_cols.clear();
}
PresolveStatus
changeUB( int col, REAL val );
void
markRowRedundant( int row )
{
RowFlags& rflags = problem.getRowFlags()[row];
if( !rflags.test( RowFlag::kRedundant ) )
{
redundant_rows.push_back( row );
++stats.ndeletedrows;
rflags.set( RowFlag::kRedundant );
}
postsolve.storeRedundantRow( row );
}
void
observeCompress( PresolveMethod* observer )
{
compress_observers.push_back( observer );
}
void
markColFixed( int col )
{
ColFlags& cflags = problem.getColFlags()[col];
assert( !cflags.test( ColFlag::kInactive ) );
cflags.set( ColFlag::kFixed );
deleted_cols.push_back( col );
++stats.ndeletedcols;
if( cflags.test( ColFlag::kIntegral ) )
--problem.getNumIntegralCols();
else
--problem.getNumContinuousCols();
}
/// removes the constant contribution of fixed columns
/// from the left and right hand sides and the activity
void
removeFixedCols();
/// scans through all columns and does some trivial presolve reductions:
/// * it rounds fractional bounds of integer variables
/// * checks for infeasibility of bound constraints
/// * performs dual fixing if the parameter dualfix is true
/// * remembers singleton columns
/// if dualfixing is true the locks of the problem must have been initialized
/// by calling problem.recomputeLocks()
PresolveStatus
trivialColumnPresolve();
/// scans through all rows and does some trivial presolve reductions:
/// * removes singleton rows and updates the column bounds accordingly
/// * checks if the row is redundant or proves infeasibility w.r.t. the
/// activity bounds
/// The activities of the problem must be initialized before calling this
/// fucntion by callnig problem.recomputeActivities()
PresolveStatus
trivialRowPresolve();
/// performs trivial row and column presolve and initializes the locks and
/// acitivities. Updates the matrix to reflect the changes
PresolveStatus
trivialPresolve();
/// adds a singleton row as a bound change and marks the row redundant
PresolveStatus
removeSingletonRow( int row );
/// cleanup small coefficients from single row, adds coefficients changes to
/// the matrix buffer
void
cleanupSmallCoefficients( int row );
PresolveStatus
removeEmptyColumns();
void
compress( bool full = false );
/// check changed activities for infeasibility and row redundancy
PresolveStatus
checkChangedActivities();
/// flush changes after applying several reductions
PresolveStatus
flush( bool reset_changed_activities );
/// flush changes coefficients after applying several reductions
void
flushChangedCoeffs();
void
clearChangeInfo()
{
changed_activities.clear();
firstNewSingletonCol = singletonColumns.size();
}
void
clearStates();
void
check_and_compress();
const Vec&
getChangedActivities() const
{
return changed_activities;
}
const Vec&
getSingletonCols() const
{
return singletonColumns;
}
void
addDeletedVar(int col)
{
deleted_cols.push_back(col);
}
const Vec&
getRandomColPerm() const
{
return random_col_perm;
}
const Vec&
getRandomRowPerm() const
{
return random_row_perm;
}
bool
isColBetterForSubstitution( int col1, int col2 ) const
{
int col1size = problem.getColSizes()[col1];
int col2size = problem.getColSizes()[col2];
// first criterion is sparsity
if( col1size < col2size )
return true;
if( col2size < col1size )
return false;
// second criterion is whether the objective is zero
bool obj1zero = problem.getObjective().coefficients[col1] == 0;
bool obj2zero = problem.getObjective().coefficients[col2] == 0;
if( obj1zero && !obj2zero )
return true;
if( !obj1zero && obj2zero )
return false;
// tie breaker is the random column permutation
return random_col_perm[col1] < random_col_perm[col2];
}
int
getFirstNewSingletonCol() const
{
return firstNewSingletonCol;
}
int
getNActiveRows() const
{
return problem.getNRows() - stats.ndeletedrows + lastcompress_ndelrows;
}
int
getNActiveCols() const
{
return problem.getNCols() - stats.ndeletedcols + lastcompress_ndelcols;
}
const PresolveOptions&
getPresolveOptions() const
{
return presolveOptions;
}
std::pair
removeRedundantBounds()
{
return problem.removeRedundantBounds( num, problem.getColFlags(),
problem.getRowActivities() );
}
/// returns true if the given transaction conflicts with the current state of
/// changes and false otherwise
ConflictType
checkTransactionConflicts( const Reduction* first,
const Reduction* last );
/// returns true if the given transaction was applied and false otherwise
ApplyResult
applyTransaction( const Reduction* first,
const Reduction* last );
void
roundIntegralColumns( Vec& lbs, Vec& ubs, int col,
Vec& cflags, PresolveStatus& status );
void
mark_huge_values( const Vec& lbs, const Vec& ubs,
Vec& cflags, int col );
bool
is_dualfix_enabled( const Vec& obj, int col ) const;
PresolveStatus
apply_dualfix( Vec& lbs, Vec& ubs, Vec& cflags,
const Vec& obj, const Vec& locks, int col );
void
print_detailed( const Reduction* first,
const Reduction* last ) const;
void
shuffle( std::ranlux24& random_generator, Vec& array );
};
#ifdef PAPILO_USE_EXTERN_TEMPLATES
extern template class ProblemUpdate;
extern template class ProblemUpdate;
extern template class ProblemUpdate;
#endif
template
ProblemUpdate::ProblemUpdate( Problem& _problem,
PostsolveStorage& _postsolve,
Statistics& _stats,
const PresolveOptions& _presolveOptions,
const Num& _num, const Message& _msg )
: problem( _problem ), postsolve( _postsolve ), stats( _stats ),
presolveOptions( _presolveOptions ), num( _num ), msg( _msg )
{
row_state.resize( _problem.getNRows() );
col_state.resize( _problem.getNCols() );
postponeSubstitutions = true;
firstNewSingletonCol = 0;
lastcompress_ndelcols = 0;
lastcompress_ndelrows = 0;
std::ranlux24 randgen( _presolveOptions.randomseed );
random_col_perm.resize( _problem.getNCols() );
for( int i = 0; i < _problem.getNCols(); ++i )
random_col_perm[i] = i;
shuffle( randgen, random_col_perm );
random_row_perm.resize( _problem.getNRows() );
for( int i = 0; i < _problem.getNRows(); ++i )
random_row_perm[i] = i;
shuffle( randgen, random_row_perm );
}
template
void
ProblemUpdate::shuffle( std::ranlux24& random_generator, Vec& array )
{
int tmp;
int i;
int end = (int)array.size();
int begin = 0;
// loop backwards through all elements and always swap the current last
// element to a random position
while( end > begin + 1 )
{
end--;
// get a random position into which the last entry should be shuffled
boost::random::uniform_int_distribution<> distrib( begin, end );
i = distrib( random_generator );
// swap the last element and the random element
tmp = array[i];
array[i] = array[end];
array[end] = tmp;
}
}
template
void
ProblemUpdate::update_activity( ActivityChange actChange, int rowid,
RowActivity& activity )
{
if( activity.lastchange == stats.nrounds ||
( actChange == ActivityChange::kMin && activity.ninfmin > 1 ) ||
( actChange == ActivityChange::kMax && activity.ninfmax > 1 ) ||
problem.getConstraintMatrix().isRowRedundant( rowid ) )
return;
activity.lastchange = stats.nrounds;
changed_activities.push_back( rowid );
}
template
PresolveStatus
ProblemUpdate::fixCol( int col, REAL val )
{
ConstraintMatrix& constraintMatrix = problem.getConstraintMatrix();
Vec& lbs = problem.getLowerBounds();
Vec& ubs = problem.getUpperBounds();
Vec& cflags = problem.getColFlags();
if( cflags[col].test( ColFlag::kSubstituted ) )
return PresolveStatus::kUnchanged;
auto updateActivity = [this]( ActivityChange actChange, int rowid,
RowActivity& activity ) {
update_activity( actChange, rowid, activity );
};
bool lbchanged = cflags[col].test( ColFlag::kLbInf ) || val != lbs[col];
bool ubchanged = cflags[col].test( ColFlag::kUbInf ) || val != ubs[col];
if( lbchanged )
++stats.nboundchgs;
if( ubchanged )
++stats.nboundchgs;
if( lbchanged || ubchanged )
{
auto colvec = constraintMatrix.getColumnCoefficients( col );
if( ( !cflags[col].test( ColFlag::kLbInf ) &&
num.isFeasLT( val, lbs[col] ) ) ||
( !cflags[col].test( ColFlag::kUbInf ) &&
num.isFeasGT( val, ubs[col] ) ) ||
( cflags[col].test( ColFlag::kIntegral ) &&
!num.isFeasIntegral( val ) ) )
{
Message::debug( this,
"fixing {} col {} with bounds [{},{}] to value {} was "
"detected to be infeasible\n",
cflags[col].test( ColFlag::kIntegral ) ? "integral"
: "continuous",
col,
cflags[col].test( ColFlag::kLbInf )
? -std::numeric_limits::infinity()
: double( lbs[col] ),
cflags[col].test( ColFlag::kUbInf )
? std::numeric_limits::infinity()
: double( ubs[col] ),
double( val ) );
return PresolveStatus::kInfeasible;
}
if( cflags[col].test( ColFlag::kFixed ) )
return PresolveStatus::kUnchanged;
if( lbchanged )
{
update_activities_after_boundchange(
colvec.getValues(), colvec.getIndices(), colvec.getLength(),
BoundChange::kLower, lbs[col], val,
cflags[col].test( ColFlag::kLbUseless ),
problem.getRowActivities(), updateActivity );
postsolve.storeVarBoundChange(
true, col, lbs[col],
problem.getColFlags()[col].test( ColFlag::kLbInf ), val );
lbs[col] = val;
cflags[col].unset( ColFlag::kLbUseless );
}
if( ubchanged )
{
update_activities_after_boundchange(
colvec.getValues(), colvec.getIndices(), colvec.getLength(),
BoundChange::kUpper, ubs[col], val,
cflags[col].test( ColFlag::kUbUseless ),
problem.getRowActivities(), updateActivity );
postsolve.storeVarBoundChange(
false, col, ubs[col],
problem.getColFlags()[col].test( ColFlag::kUbInf ), val );
ubs[col] = val;
cflags[col].unset( ColFlag::kUbUseless );
}
// remember fixed column
markColFixed( col );
setColState( col, State::kBoundsModified );
return PresolveStatus::kReduced;
}
assert( cflags[col].test( ColFlag::kFixed ) );
return PresolveStatus::kUnchanged;
}
template
PresolveStatus
ProblemUpdate::fixColInfinity( int col, REAL val )
{
Vec& lbs = problem.getLowerBounds();
Vec& ubs = problem.getUpperBounds();
Vec& cflags = problem.getColFlags();
if( cflags[col].test( ColFlag::kSubstituted ) ||
cflags[col].test( ColFlag::kFixed ) || val == 0 )
return PresolveStatus::kUnchanged;
assert( ( val < 0 && cflags[col].test( ColFlag::kLbInf ) ) ||
( val > 0 && cflags[col].test( ColFlag::kUbInf ) ) );
// activity doesn't need to be upgraded because rows should be mark redundant
markColFixed( col );
setColState( col, State::kBoundsModified );
if( val == -1 )
{
assert(cflags[col].test( ColFlag::kLbInf ));
REAL ub = cflags[col].test( ColFlag::kUbInf )? (double) std::numeric_limits::max() :ubs[col];
postsolve.storeFixedInfCol( col, -1, ub, problem );
}
if( val == 1 )
{
assert(cflags[col].test( ColFlag::kUbInf ));
REAL lb = cflags[col].test( ColFlag::kLbInf )? (double) std::numeric_limits::max() :lbs[col];
postsolve.storeFixedInfCol( col, 1, lb, problem );
}
return PresolveStatus::kReduced;
}
template
PresolveStatus
ProblemUpdate::changeLB( int col, REAL val )
{
ConstraintMatrix& constraintMatrix = problem.getConstraintMatrix();
Vec& cflags = problem.getColFlags();
Vec& lbs = problem.getLowerBounds();
Vec& ubs = problem.getUpperBounds();
if( cflags[col].test( ColFlag::kSubstituted ) )
return PresolveStatus::kUnchanged;
REAL newbound = val;
auto updateActivity = [this]( ActivityChange actChange, int rowid,
RowActivity& activity ) {
update_activity( actChange, rowid, activity );
};
if( cflags[col].test( ColFlag::kIntegral, ColFlag::kImplInt ) )
newbound = num.feasCeil( newbound );
bool isInfinity = cflags[col].test( ColFlag::kLbInf );
if( isInfinity || newbound > lbs[col] )
{
++stats.nboundchgs;
if( !cflags[col].test( ColFlag::kUbInf ) && newbound > ubs[col] )
{
if( num.isFeasGT( newbound, ubs[col] ) )
{
Message::debug( this,
"changing lower bound of {} col {} with bounds "
"[{},{}] to value {} was "
"detected to be infeasible\n",
cflags[col].test( ColFlag::kIntegral )
? "integral"
: "continuous",
col,
cflags[col].test( ColFlag::kLbInf )
? -std::numeric_limits::infinity()
: double( lbs[col] ),
cflags[col].test( ColFlag::kUbInf )
? std::numeric_limits::infinity()
: double( ubs[col] ),
double( newbound ) );
return PresolveStatus::kInfeasible;
}
if( !cflags[col].test( ColFlag::kLbInf ) && lbs[col] == ubs[col] )
return PresolveStatus::kUnchanged;
newbound = ubs[col];
}
if( !num.isHugeVal( newbound ) )
{
auto colvec = constraintMatrix.getColumnCoefficients( col );
update_activities_after_boundchange(
colvec.getValues(), colvec.getIndices(), colvec.getLength(),
BoundChange::kLower, lbs[col], newbound,
cflags[col].test( ColFlag::kLbUseless ),
problem.getRowActivities(), updateActivity );
cflags[col].unset( ColFlag::kLbUseless );
}
else
cflags[col].unset( ColFlag::kLbInf );
postsolve.storeVarBoundChange( true, col, lbs[col], isInfinity,
newbound );
lbs[col] = newbound;
if( !cflags[col].test( ColFlag::kUbInf ) && ubs[col] == lbs[col] )
{
cflags[col].set( ColFlag::kFixed );
deleted_cols.push_back( col );
++stats.ndeletedcols;
if( cflags[col].test( ColFlag::kIntegral ) )
--problem.getNumIntegralCols();
else
--problem.getNumContinuousCols();
}
setColState( col, State::kBoundsModified );
return PresolveStatus::kReduced;
}
return PresolveStatus::kUnchanged;
}
template
PresolveStatus
ProblemUpdate::changeUB( int col, REAL val )
{
ConstraintMatrix& constraintMatrix = problem.getConstraintMatrix();
Vec& cflags = problem.getColFlags();
Vec& lbs = problem.getLowerBounds();
Vec& ubs = problem.getUpperBounds();
if( cflags[col].test( ColFlag::kSubstituted ) )
return PresolveStatus::kUnchanged;
REAL newbound = val;
auto updateActivity = [this]( ActivityChange actChange, int rowid,
RowActivity& activity ) {
update_activity( actChange, rowid, activity );
};
if( cflags[col].test( ColFlag::kIntegral, ColFlag::kImplInt ) )
newbound = num.feasFloor( newbound );
bool isInfinity = cflags[col].test( ColFlag::kUbInf );
if( isInfinity || newbound < ubs[col] )
{
++stats.nboundchgs;
if( !cflags[col].test( ColFlag::kLbInf ) && newbound < lbs[col] )
{
if( num.isFeasLT( newbound, lbs[col] ) )
{
Message::debug( this,
"changing upper bound of {} col {} with bounds "
"[{},{}] to value {} was "
"detected to be infeasible\n",
cflags[col].test( ColFlag::kIntegral )
? "integral"
: "continuous",
col,
cflags[col].test( ColFlag::kLbInf )
? -std::numeric_limits::infinity()
: double( lbs[col] ),
cflags[col].test( ColFlag::kUbInf )
? std::numeric_limits::infinity()
: double( ubs[col] ),
double( newbound ) );
return PresolveStatus::kInfeasible;
}
if( !cflags[col].test( ColFlag::kUbInf ) && lbs[col] == ubs[col] )
return PresolveStatus::kUnchanged;
newbound = lbs[col];
}
if( !num.isHugeVal( newbound ) )
{
auto colvec = constraintMatrix.getColumnCoefficients( col );
update_activities_after_boundchange(
colvec.getValues(), colvec.getIndices(), colvec.getLength(),
BoundChange::kUpper, ubs[col], newbound,
cflags[col].test( ColFlag::kUbUseless ),
problem.getRowActivities(), updateActivity );
cflags[col].unset( ColFlag::kUbUseless );
}
else
cflags[col].unset( ColFlag::kUbInf );
postsolve.storeVarBoundChange( false, col, ubs[col], isInfinity,
newbound );
ubs[col] = newbound;
if( !cflags[col].test( ColFlag::kLbInf ) && ubs[col] == lbs[col] )
{
cflags[col].set( ColFlag::kFixed );
deleted_cols.push_back( col );
++stats.ndeletedcols;
if( cflags[col].test( ColFlag::kIntegral ) )
--problem.getNumIntegralCols();
else
--problem.getNumContinuousCols();
}
setColState( col, State::kBoundsModified );
return PresolveStatus::kReduced;
}
return PresolveStatus::kUnchanged;
}
template
void
ProblemUpdate::compress( bool full )
{
if( problem.getNCols() == getNActiveCols() &&
problem.getNRows() == getNActiveRows() && !full )
return;
// TODO: do not compress if ActiveRows are zero because rowmapping in Postsolve is deleted.
// if(getNActiveRows() <= 0)
// return;
Message::debug( this,
"compressing problem ({} rows, {} cols) to active problem "
"({} rows, {} cols)\n",
problem.getNRows(), problem.getNCols(), getNActiveRows(),
getNActiveCols() );
std::pair, Vec> mappings = problem.compress( full );
assert( redundant_rows.empty() );
assert( deleted_cols.empty() );
assert( dirty_col_states.empty() );
assert( dirty_row_states.empty() );
assert( matrix_buffer.empty() );
row_state.resize( problem.getNRows() );
col_state.resize( problem.getNCols() );
#ifdef PAPILO_TBB
tbb::parallel_invoke(
[this, &mappings, full]() {
compress_index_vector( mappings.first, random_row_perm );
if( full )
random_row_perm.shrink_to_fit();
},
[this, &mappings, full]() {
compress_index_vector( mappings.second, random_col_perm );
if( full )
random_col_perm.shrink_to_fit();
},
[this, &mappings, full]() {
postsolve.compress( mappings.first, mappings.second, full );
},
[this, &mappings, full]() {
// update row index sets
compress_index_vector( mappings.first, changed_activities );
if( full )
changed_activities.shrink_to_fit();
},
[this, &mappings, full]() {
compress_index_vector( mappings.first, singletonRows );
if( full )
singletonRows.shrink_to_fit();
},
// update column index sets
[this, &mappings, full]() {
int numNewSingletonCols =
static_cast( singletonColumns.size() ) -
firstNewSingletonCol;
compress_index_vector( mappings.second, singletonColumns );
firstNewSingletonCol =
std::max( 0, static_cast( singletonColumns.size() ) -
numNewSingletonCols );
if( full )
singletonColumns.shrink_to_fit();
},
[this, &mappings, full]() {
compress_index_vector( mappings.second, emptyColumns );
if( full )
emptyColumns.shrink_to_fit();
},
[this, &mappings]() {
for( PresolveMethod* observer : compress_observers )
observer->compress( mappings.first, mappings.second );
} );
#else
compress_index_vector( mappings.first, random_row_perm );
compress_index_vector( mappings.second, random_col_perm );
postsolve.compress( mappings.first, mappings.second, full );
compress_index_vector( mappings.first, changed_activities );
compress_index_vector( mappings.first, singletonRows );
compress_index_vector( mappings.second, emptyColumns );
int numNewSingletonCols =
static_cast( singletonColumns.size() ) -
firstNewSingletonCol;
compress_index_vector( mappings.second, singletonColumns );
firstNewSingletonCol =
std::max( 0, static_cast( singletonColumns.size() ) -
numNewSingletonCols );
if( full )
{
random_row_perm.shrink_to_fit();
random_col_perm.shrink_to_fit();
changed_activities.shrink_to_fit();
singletonRows.shrink_to_fit();
emptyColumns.shrink_to_fit();
singletonColumns.shrink_to_fit();
}
for( PresolveMethod* observer : compress_observers )
observer->compress( mappings.first, mappings.second );
#endif
lastcompress_ndelrows = stats.ndeletedrows;
lastcompress_ndelcols = stats.ndeletedcols;
}
template
PresolveStatus
ProblemUpdate::checkChangedActivities()
{
ConstraintMatrix& consmatrix = problem.getConstraintMatrix();
const Vec& rflags = consmatrix.getRowFlags();
const Vec& lhs = consmatrix.getLeftHandSides();
const Vec& rhs = consmatrix.getRightHandSides();
PresolveStatus status = PresolveStatus::kUnchanged;
for( int r : changed_activities )
{
if( rflags[r].test( RowFlag::kRedundant ) )
continue;
RowStatus st = problem.getRowActivities()[r].checkStatus(
num, rflags[r], lhs[r], rhs[r] );
switch( st )
{
case RowStatus::kRedundant:
markRowRedundant( r );
status = PresolveStatus::kReduced;
break;
case RowStatus::kRedundantLhs:
postsolve.storeRowBoundChange(
true, r, REAL{ 0 }, true, REAL{ 0 },
consmatrix.getRowFlags()[r].test( RowFlag::kLhsInf ) );
consmatrix.template modifyLeftHandSide( r, num );
status = PresolveStatus::kReduced;
break;
case RowStatus::kRedundantRhs:
postsolve.storeRowBoundChange(
false, r, REAL{ 0 }, true, REAL{ 0 },
consmatrix.getRowFlags()[r].test( RowFlag::kRhsInf ) );
consmatrix.template modifyRightHandSide( r, num );
status = PresolveStatus::kReduced;
break;
case RowStatus::kInfeasible:
return PresolveStatus::kInfeasible;
case RowStatus::kUnknown:
continue;
}
}
return status;
}
template
void
ProblemUpdate::flushChangedCoeffs()
{
// apply outstanding coefficient change
if( !matrix_buffer.empty() )
{
const Vec& lbs = problem.getLowerBounds();
const Vec& ubs = problem.getUpperBounds();
const Vec& cflags = problem.getColFlags();
Vec>& activities = problem.getRowActivities();
auto coeffChanged = [this, &lbs, &cflags, &ubs, &activities](
int row, int col, REAL oldval, REAL newval ) {
auto rowvec = problem.getConstraintMatrix().getRowCoefficients( row );
update_activity_after_coeffchange(
lbs[col], ubs[col], cflags[col], oldval, newval, activities[row],
rowvec.getLength(), rowvec.getIndices(), rowvec.getValues(),
problem.getVariableDomains(), num,
[this, row]( ActivityChange actChange,
RowActivity& activity )
{ update_activity( actChange, row, activity ); } );
++stats.ncoefchgs;
// TODO: update up/down-locks -> so that i.e. DualFix can use it
};
problem.getConstraintMatrix().changeCoefficients(
matrix_buffer, singletonRows, singletonColumns, emptyColumns,
activities, coeffChanged );
matrix_buffer.clear();
}
}
template
PresolveStatus
ProblemUpdate::flush( bool reset_changed_activities )
{
Vec& rflags = problem.getRowFlags();
Vec>& activities = problem.getRowActivities();
ConstraintMatrix& consMatrix = problem.getConstraintMatrix();
// apply outstanding coefficient change
flushChangedCoeffs();
// remove all singleton rows after applying the coefficient changes
if( !singletonRows.empty() )
{
for( int row : singletonRows )
{
if( removeSingletonRow( row ) == PresolveStatus::kInfeasible )
{
Message::debug(
this, "[{}:{}] removeSingletonRow detected infeasible row\n",
__FILE__, __LINE__ );
return PresolveStatus::kInfeasible;
}
}
singletonRows.clear();
}
// check rows with changed activities for redundancy or infeasibility
// and remove them from the changed activities vector
if( checkChangedActivities() == PresolveStatus::kInfeasible )
return PresolveStatus::kInfeasible;
if( reset_changed_activities )
{
auto iter =
std::remove_if( changed_activities.begin(), changed_activities.end(),
[&rflags]( int row ) {
return rflags[row].test( RowFlag::kRedundant );
} );
changed_activities.erase( iter, changed_activities.end() );
}
// remove constants of fixed columns
removeFixedCols();
// delete fixed columns and redundant rows form the matrix
// TODO update locks in delete rows and cols function
consMatrix.deleteRowsAndCols( redundant_rows, deleted_cols, activities,
singletonRows, singletonColumns,
emptyColumns );
// remove singleton columns from list of singleton columns if they are not
// singletons anymore
if( !singletonColumns.empty() )
{
const Vec& colsizes = problem.getColSizes();
int k = 0;
int i;
assert( firstNewSingletonCol <= (int) singletonColumns.size() );
for( i = 0; i != firstNewSingletonCol; ++i )
{
if( colsizes[singletonColumns[i]] != 1 )
++k;
else if( k != 0 )
singletonColumns[i - k] = singletonColumns[i];
}
firstNewSingletonCol -= k;
int nsingletoncols = static_cast( singletonColumns.size() );
assert( i <= nsingletoncols );
for( ; i != nsingletoncols; ++i )
{
if( colsizes[singletonColumns[i]] != 1 )
++k;
else if( k != 0 )
singletonColumns[i - k] = singletonColumns[i];
}
nsingletoncols -= k;
singletonColumns.resize( nsingletoncols );
assert( firstNewSingletonCol >= 0 &&
firstNewSingletonCol <= nsingletoncols );
assert( std::all_of( singletonColumns.begin(), singletonColumns.end(),
[&]( int col ) { return colsizes[col] == 1; } ) );
}
// fix empty columns
if( removeEmptyColumns() == PresolveStatus::kUnbndOrInfeas )
return PresolveStatus::kUnbndOrInfeas;
return PresolveStatus::kReduced;
}
template
void
ProblemUpdate::clearStates()
{
// clear states of rows
for( int row : dirty_row_states )
row_state[row] = State::kUnmodified;
dirty_row_states.clear();
assert(
std::all_of( row_state.begin(), row_state.end(), []( Flags s ) {
return s.equal( State::kUnmodified );
} ) );
// clear states of columns
for( int col : dirty_col_states )
col_state[col] = State::kUnmodified;
dirty_col_states.clear();
assert(
std::all_of( col_state.begin(), col_state.end(), []( Flags s ) {
return s.equal( State::kUnmodified );
} ) );
}
template
void
ProblemUpdate::check_and_compress()
{
if( presolveOptions.compressfac != 0 &&
( ( problem.getNCols() > 100 &&
getNActiveCols() <
problem.getNCols() * presolveOptions.compressfac ) ||
( problem.getNRows() > 100 &&
getNActiveRows() <
problem.getNRows() * presolveOptions.compressfac ) ) )
compress();
}
template
void
ProblemUpdate::removeFixedCols()
{
Objective& obj = problem.getObjective();
const Vec& lbs = problem.getLowerBounds();
const Vec& cflags = problem.getColFlags();
Vec>& activities = problem.getRowActivities();
ConstraintMatrix& consMatrix = problem.getConstraintMatrix();
Vec& rflags = consMatrix.getRowFlags();
Vec& lhs = consMatrix.getLeftHandSides();
Vec& rhs = consMatrix.getRightHandSides();
for( int col : deleted_cols )
{
if( !cflags[col].test( ColFlag::kFixed ) )
continue;
if( cflags[col].test( ColFlag::kLbInf ) || cflags[col].test( ColFlag::kUbInf ) )
continue;
assert(
num.isEq( lbs[col], problem.getUpperBounds()[col] ) && !problem.getColFlags()[col]
.test( ColFlag::kUbInf ) &&
!problem.getColFlags()[col].test( ColFlag::kLbInf ) );
auto colvec = consMatrix.getColumnCoefficients( col );
postsolve.storeFixedCol( col, lbs[col], colvec, obj.coefficients );
// if it is fixed to zero activities and sides do not need to be
// updated
if( lbs[col] == 0 )
continue;
// update objective offset
if( obj.coefficients[col] != 0 )
{
obj.offset += lbs[col] * obj.coefficients[col];
obj.coefficients[col] = 0;
}
// fixed to nonzero value, so update sides and activities
int collen = colvec.getLength();
const int* colrows = colvec.getIndices();
const REAL* colvals = colvec.getValues();
for( int i = 0; i != collen; ++i )
{
int row = colrows[i];
// if the row is redundant it will also be removed and does not need
// to be updated
if( rflags[row].test( RowFlag::kRedundant ) )
continue;
// subtract constant contribution from activity and sides
REAL constant = lbs[col] * colvals[i];
activities[row].min -= constant;
activities[row].max -= constant;
if( !rflags[row].test( RowFlag::kLhsInf ) )
lhs[row] -= constant;
if( !rflags[row].test( RowFlag::kRhsInf ) )
rhs[row] -= constant;
// due to numerics a ranged row can become an equality
if( !rflags[row].test( RowFlag::kLhsInf, RowFlag::kRhsInf,
RowFlag::kEquation ) &&
lhs[row] == rhs[row] )
rflags[row].set( RowFlag::kEquation );
}
}
}
template
PresolveStatus
ProblemUpdate::trivialColumnPresolve()
{
Vec& lbs = problem.getLowerBounds();
Vec& ubs = problem.getUpperBounds();
Vec& cflags = problem.getColFlags();
Vec& colsize = problem.getColSizes();
Vec& obj = problem.getObjective().coefficients;
Vec& locks = problem.getLocks();
PresolveStatus status = PresolveStatus::kUnchanged;
for( int col = 0; col < problem.getNCols(); ++col )
{
if( cflags[col].test( ColFlag::kInactive ) )
continue;
// for integral columns round the bounds to integral values
roundIntegralColumns( lbs, ubs, col, cflags, status );
mark_huge_values( lbs, ubs, cflags, col );
if( !cflags[col].test( ColFlag::kUnbounded ) )
{
if( lbs[col] > ubs[col] )
{
Message::debug(
this, "[{}:{}] trivial presolve detected conflicting bounds\n",
__FILE__, __LINE__ );
return PresolveStatus::kInfeasible;
}
// remember fixed columns
if( lbs[col] == ubs[col] )
{
markColFixed( col );
status = PresolveStatus::kReduced;
continue;
}
}
status = apply_dualfix( lbs, ubs, cflags, obj, locks, col );
if( status == PresolveStatus::kUnbndOrInfeas )
return status;
else if( status == PresolveStatus::kReduced )
continue;
switch( colsize[col] )
{
case 0:
emptyColumns.push_back( col );
break;
case 1:
singletonColumns.push_back( col );
}
}
return status;
}
template
PresolveStatus
ProblemUpdate::apply_dualfix( Vec& lbs, Vec& ubs,
Vec& cflags, const Vec& obj,
const Vec& locks, int col )
{
if( is_dualfix_enabled( obj, col ) )
{
if( locks[col].down == 0 && obj[col] >= 0 )
{
if( cflags[col].test( ColFlag::kLbInf ) )
{
if( obj[col] != 0 )
{
Message::debug( this,
"[{}:{}] dual fixing in trivial presolve "
"detected status UNBND_OR_INFEAS\n",
__FILE__, __LINE__ );
return PresolveStatus::kUnbndOrInfeas;
}
}
else
{
postsolve.storeVarBoundChange( false, col, ubs[col],
cflags[col].test( ColFlag::kUbInf ),
lbs[col] );
ubs[col] = lbs[col];
cflags[col].unset( ColFlag::kUbInf );
++stats.nboundchgs;
markColFixed( col );
return PresolveStatus::kReduced;
}
}
if( locks[col].up == 0 && obj[col] <= 0 )
{
if( cflags[col].test( ColFlag::kUbInf ) )
{
if( obj[col] != 0 )
{
Message::debug( this,
"[{}:{}] dual fixing in trivial presolve "
"detected status UNBND_OR_INFEAS\n",
__FILE__, __LINE__ );
return PresolveStatus::kUnbndOrInfeas;
}
}
else
{
postsolve.storeVarBoundChange( true, col, lbs[col],
cflags[col].test( ColFlag::kLbInf ),
ubs[col] );
lbs[col] = ubs[col];
cflags[col].unset( ColFlag::kLbInf );
++stats.nboundchgs;
markColFixed( col );
return PresolveStatus::kReduced;
}
}
}
return PresolveStatus::kUnchanged;
}
template
bool
ProblemUpdate::is_dualfix_enabled( const Vec& obj, int col ) const
{
bool dualfix;
switch( presolveOptions.dualreds )
{
default:
assert( false );
case 0:
dualfix = false;
break;
case 1:
dualfix = obj[col] != 0;
break;
case 2:
dualfix = true;
}
return dualfix;
}
template
void
ProblemUpdate::mark_huge_values( const Vec& lbs,
const Vec& ubs,
Vec& cflags, int col )
{
if( !cflags[col].test( ColFlag::kLbInf ) && num.isHugeVal( lbs[col] ) )
cflags[col].set( ColFlag::kLbHuge );
if( !cflags[col].test( ColFlag::kUbInf ) && num.isHugeVal( ubs[col] ) )
cflags[col].set( ColFlag::kUbHuge );
}
template
void
ProblemUpdate::roundIntegralColumns( Vec& lbs, Vec& ubs,
int col, Vec& cflags,
PresolveStatus& status )
{
if( cflags[col].test( ColFlag::kIntegral ) )
{
if( !cflags[col].test( ColFlag::kLbInf ) )
{
REAL ceillb = ceil( lbs[col] );
if( ceillb != lbs[col] )
{
++stats.nboundchgs;
lbs[col] = ceillb;
status = PresolveStatus::kReduced;
}
}
if( !cflags[col].test( ColFlag::kUbInf ) )
{
REAL floorub = floor( ubs[col] );
if( floorub != ubs[col] )
{
++stats.nboundchgs;
ubs[col] = floorub;
status = PresolveStatus::kReduced;
}
}
}
}
template
PresolveStatus
ProblemUpdate::trivialRowPresolve()
{
ConstraintMatrix& consMatrix = problem.getConstraintMatrix();
Vec& rowsize = consMatrix.getRowSizes();
Vec& rflags = consMatrix.getRowFlags();
Vec>& activities = problem.getRowActivities();
const Vec& lhs = consMatrix.getLeftHandSides();
const Vec& rhs = consMatrix.getRightHandSides();
assert( (int) activities.size() == problem.getNRows() );
PresolveStatus status = PresolveStatus::kUnchanged;
for( int row = 0; row != problem.getNRows(); ++row )
{
switch( rowsize[row] )
{
case 0:
if( !rflags[row].test( RowFlag::kLhsInf ) &&
num.isFeasGT( lhs[row], 0 ) )
{
Message::debug(
this, "[{}:{}] trivial presolve detected infeasible row\n",
__FILE__, __LINE__ );
return PresolveStatus::kInfeasible;
}
if( !rflags[row].test( RowFlag::kRhsInf ) &&
num.isFeasLT( rhs[row], 0 ) )
{
Message::debug(
this, "[{}:{}] trivial presolve detected infeasible row\n",
__FILE__, __LINE__ );
return PresolveStatus::kInfeasible;
}
rflags[row].set( RowFlag::kRedundant );
rowsize[row] = -1;
postsolve.storeRedundantRow( row );
status = PresolveStatus::kReduced;
break;
case 1:
status = removeSingletonRow( row );
if( status == PresolveStatus::kInfeasible )
{
Message::debug(
this, "[{}:{}] removeSingletonRow detected infeasible row\n",
__FILE__, __LINE__ );
return status;
}
break;
default:
{
RowStatus st = activities[row].checkStatus( num, rflags[row], lhs[row],
rhs[row] );
switch( st )
{
case RowStatus::kRedundant:
break;
case RowStatus::kRedundantLhs:
postsolve.storeRowBoundChange(
true, row, REAL{ 0 }, true, REAL{ 0 },
consMatrix.getRowFlags()[row].test( RowFlag::kLhsInf ) );
consMatrix.template modifyLeftHandSide( row, num );
status = PresolveStatus::kReduced;
cleanupSmallCoefficients( row );
break;
case RowStatus::kRedundantRhs:
postsolve.storeRowBoundChange(
false, row, REAL{ 0 }, true, REAL{ 0 },
consMatrix.getRowFlags()[row].test( RowFlag::kRhsInf ) );
consMatrix.template modifyRightHandSide( row, num );
status = PresolveStatus::kReduced;
cleanupSmallCoefficients( row );
break;
case RowStatus::kInfeasible:
return PresolveStatus::kInfeasible;
case RowStatus::kUnknown:
if( !rflags[row].test( RowFlag::kRhsInf, RowFlag::kLhsInf,
RowFlag::kEquation ) )
{
assert( !rflags[row].test( RowFlag::kRhsInf ) );
assert( !rflags[row].test( RowFlag::kLhsInf ) );
assert( !rflags[row].test( RowFlag::kEquation ) );
if( lhs[row] == rhs[row] )
rflags[row].set( RowFlag::kEquation );
}
cleanupSmallCoefficients( row );
}
}
}
// row should be either redundant, or the equality flag must be set
// correctly
assert( rflags[row].test( RowFlag::kRedundant ) ||
( !rflags[row].test( RowFlag::kEquation ) &&
( rflags[row].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) ||
lhs[row] != rhs[row] ) ) ||
( rflags[row].test( RowFlag::kEquation ) &&
lhs[row] == rhs[row] &&
!rflags[row].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) ) );
}
flushChangedCoeffs();
return status;
}
template
PresolveStatus
ProblemUpdate::trivialPresolve()
{
if( presolveOptions.dualreds != 0 )
problem.recomputeLocks();
PresolveStatus status = trivialColumnPresolve();
if( status == PresolveStatus::kInfeasible ||
status == PresolveStatus::kUnbndOrInfeas )
return status;
problem.recomputeAllActivities();
status = trivialRowPresolve();
if( status == PresolveStatus::kInfeasible ||
status == PresolveStatus::kUnbndOrInfeas )
return status;
removeFixedCols();
problem.getConstraintMatrix().deleteRowsAndCols(
redundant_rows, deleted_cols, problem.getRowActivities(), singletonRows,
singletonColumns, emptyColumns );
for( int row : singletonRows )
{
status = removeSingletonRow( row );
if( status == PresolveStatus::kInfeasible )
{
Message::debug( this,
"[{}:{}] removeSingletonRow detected infeasible row\n",
__FILE__, __LINE__ );
return status;
}
}
if( !singletonColumns.empty() )
{
int numNewSingletonCols =
static_cast( singletonColumns.size() ) - firstNewSingletonCol;
assert( numNewSingletonCols >= 0 );
// erasing variables from the singleton Cols with >1 or ==0
// if a variable is aggregated its appearance can again be raised
auto it = std::remove_if(
singletonColumns.begin(), singletonColumns.end(),
[this]( int c ) { return problem.getColSizes()[c] != 1; } );
singletonColumns.erase( it, singletonColumns.end() );
firstNewSingletonCol =
std::max( 0, static_cast( singletonColumns.size() ) -
numNewSingletonCols );
}
status = checkChangedActivities();
if( status == PresolveStatus::kInfeasible ||
status == PresolveStatus::kUnbndOrInfeas )
return status;
changed_activities.clear();
const Vec& rflags = problem.getRowFlags();
for( int r = 0; r != problem.getNRows(); ++r )
{
if( rflags[r].test( RowFlag::kRedundant ) )
continue;
RowActivity& activity = problem.getRowActivities()[r];
if( activity.ninfmin == 0 || activity.ninfmax == 0 ||
( activity.ninfmax == 1 && !rflags[r].test( RowFlag::kLhsInf ) ) ||
( activity.ninfmin == 1 && !rflags[r].test( RowFlag::kRhsInf ) ) )
changed_activities.push_back( r );
}
flush( true );
return status;
}
template
PresolveStatus
ProblemUpdate::removeSingletonRow( int row )
{
ConstraintMatrix& consMatrix = problem.getConstraintMatrix();
const Vec& rowsize = consMatrix.getRowSizes();
Vec& rflags = consMatrix.getRowFlags();
PresolveStatus status = PresolveStatus::kUnchanged;
if( rowsize[row] != 1 || rflags[row].test( RowFlag::kRedundant ) )
return status;
auto rowvec = consMatrix.getRowCoefficients( row );
assert( rowvec.getLength() == 1 );
const REAL val = rowvec.getValues()[0];
const int col = rowvec.getIndices()[0];
const REAL lhs = consMatrix.getLeftHandSides()[row];
const REAL rhs = consMatrix.getRightHandSides()[row];
const bool isLhsInfinity = rflags[row].test( RowFlag::kLhsInf );
const bool isRhsInfinity = rflags[row].test( RowFlag::kRhsInf );
if( rflags[row].test( RowFlag::kEquation ) )
{
postsolve.storeSavedRow( row, rowvec, lhs, rhs, rflags[row] );
REAL fixed_val = rhs / val;
if( num.isZero( rhs ) )
fixed_val = 0.0;
status = fixCol( col, fixed_val );
}
else
{
if( val < 0 )
{
if( !isLhsInfinity )
{
REAL fixed_val = lhs / val;
if( num.isZero( lhs ) )
fixed_val = 0.0;
postsolve.storeSavedRow( row, rowvec, lhs, rhs, rflags[row] );
status = changeUB( col, fixed_val );
}
if( !isRhsInfinity && status != PresolveStatus::kInfeasible )
{
REAL fixed_val = rhs / val;
if( num.isZero( rhs ) )
fixed_val = 0.0;
postsolve.storeSavedRow( row, rowvec, lhs, rhs, rflags[row] );
status = changeLB( col, fixed_val );
}
}
else
{
assert( val > 0 );
if( !isLhsInfinity )
{
REAL fixed_val = lhs / val;
if( num.isZero( lhs ) )
fixed_val = 0.0;
postsolve.storeSavedRow( row, rowvec, lhs, rhs, rflags[row] );
status = changeLB( col, fixed_val );
}
if( !isRhsInfinity && status != PresolveStatus::kInfeasible )
{
REAL fixed_val = rhs / val;
if( num.isZero( rhs ) )
fixed_val = 0.0;
postsolve.storeSavedRow( row, rowvec, lhs, rhs, rflags[row] );
status = changeUB( col, fixed_val );
}
}
}
markRowRedundant( row );
return status;
} // namespace papilo
template
void
ProblemUpdate::cleanupSmallCoefficients( int row )
{
ConstraintMatrix& consMatrix = problem.getConstraintMatrix();
const Vec& lbs = problem.getLowerBounds();
const Vec& ubs = problem.getUpperBounds();
const Vec& cflags = problem.getColFlags();
auto rowvec = consMatrix.getRowCoefficients( row );
// arrays with nonzeros and their column index of this row
const REAL* values = rowvec.getValues();
const int* columns = rowvec.getIndices();
// number of nonzeros in row, i.e. length of arrays above
int len = rowvec.getLength();
// acces to sides of the given row
REAL& lhs = consMatrix.getLeftHandSides()[row];
REAL& rhs = consMatrix.getRightHandSides()[row];
RowFlags& rowf = consMatrix.getRowFlags()[row];
// loop over non-zeros of this row
REAL total_mod = 0;
for( int i = 0; i != len; ++i )
{
int col = columns[i];
if( cflags[col].test( ColFlag::kUnbounded, ColFlag::kInactive ) )
continue;
assert( ubs[col] > lbs[col] );
// model Cleanup
REAL absval = abs( values[i] );
if( absval < presolveOptions.minabscoeff )
{
matrix_buffer.addEntry( row, col, 0 );
Message::debug( this, "removed tiny coefficient with value {}\n",
double( values[i] ) );
continue;
}
if( absval <= 1e-3 &&
absval * ( ubs[col] - lbs[col] ) * len <= 1e-2 * num.getFeasTol() )
{
REAL temp_total_mod = total_mod + absval * ( ubs[col] - lbs[col] );
if( temp_total_mod <= 0.1 * num.getFeasTol() )
{
matrix_buffer.addEntry( row, col, 0 );
Message::debug( this, "removed small coefficient with value {}\n",
double( values[i] ) );
if( lbs[col] != 0 )
{
REAL sidechange = values[i] * lbs[col];
if( !rowf.test( RowFlag::kRhsInf ) )
{
rhs -= sidechange;
++stats.nsidechgs;
}
if( !rowf.test( RowFlag::kLhsInf ) )
{
lhs -= sidechange;
++stats.nsidechgs;
}
// due to numerics a ranged row can become an equality
if( !rowf.test( RowFlag::kLhsInf, RowFlag::kRhsInf,
RowFlag::kEquation ) &&
lhs == rhs )
rowf.set( RowFlag::kEquation );
}
total_mod = temp_total_mod;
}
}
}
}
template
PresolveStatus
ProblemUpdate::removeEmptyColumns()
{
if( presolveOptions.dualreds != 0 && !emptyColumns.empty() )
{
Objective& obj = problem.getObjective();
VariableDomains& domains = problem.getVariableDomains();
Vec& colsize = problem.getConstraintMatrix().getColSizes();
SparseVectorView empty_column;
for( int col : emptyColumns )
{
if( colsize[col] != 0 )
continue;
if( presolveOptions.dualreds == 1 && num.isZero(obj.coefficients[col]) )
continue;
if( !domains.flags[col].test( ColFlag::kInactive ) )
{
assert( colsize[col] == 0 );
REAL fixval;
if( num.isZero(obj.coefficients[col]) )
{
fixval = 0;
if( !domains.flags[col].test( ColFlag::kUbInf ) &&
num.isLT(domains.upper_bounds[col], 0) )
fixval = domains.upper_bounds[col];
else if( !domains.flags[col].test( ColFlag::kLbInf ) &&
num.isGT(domains.lower_bounds[col], 0) )
fixval = domains.lower_bounds[col];
// notify for storing the bound for recalculation
if( domains.flags[col].test( ColFlag::kLbInf ) ||
!num.isEq( domains.lower_bounds[col], fixval ) )
postsolve.storeVarBoundChange(
true, col, domains.lower_bounds[col],
domains.flags[col].test( ColFlag::kLbInf ), fixval );
if( domains.flags[col].test( ColFlag::kUbInf ) ||
!num.isEq( domains.upper_bounds[col], fixval ) )
postsolve.storeVarBoundChange(
false, col, domains.upper_bounds[col],
domains.flags[col].test( ColFlag::kUbInf ), fixval );
}
else
{
if( obj.coefficients[col] < 0 )
{
if( domains.flags[col].test( ColFlag::kUbInf ) )
return PresolveStatus::kUnbndOrInfeas;
fixval = domains.upper_bounds[col];
postsolve.storeVarBoundChange(
true, col, domains.lower_bounds[col],
domains.flags[col].test( ColFlag::kLbInf ), fixval );
}
else
{
assert( obj.coefficients[col] > 0 );
if( domains.flags[col].test( ColFlag::kLbInf ) )
return PresolveStatus::kUnbndOrInfeas;
fixval = domains.lower_bounds[col];
postsolve.storeVarBoundChange(
false, col, domains.upper_bounds[col],
domains.flags[col].test( ColFlag::kUbInf ), fixval );
}
}
postsolve.storeFixedCol( col, fixval, empty_column,
obj.coefficients );
if( obj.coefficients[col] != 0 )
{
obj.offset += obj.coefficients[col] * fixval;
obj.coefficients[col] = 0;
}
domains.flags[col].set( ColFlag::kFixed );
++stats.ndeletedcols;
if( domains.flags[col].test( ColFlag::kIntegral ) )
--problem.getNumIntegralCols();
else
--problem.getNumContinuousCols();
}
assert( num.isZero(obj.coefficients[col]) );
colsize[col] = -1;
}
emptyColumns.clear();
return PresolveStatus::kReduced;
}
return PresolveStatus::kUnchanged;
}
/// returns true if the given transaction conflicts with the current state of
/// changes and false otherwise
template
ConflictType
ProblemUpdate::checkTransactionConflicts( const Reduction* first,
const Reduction* last )
{
// check if transaction conflicts with current state
for( const Reduction* iter = first; iter != last; ++iter )
{
const Reduction& reduction = *iter;
if( reduction.row < 0 )
{
assert( reduction.col >= 0 );
int colop = reduction.row;
switch( colop )
{
case ColReduction::LOCKED:
// if the transaction wants to lock the column it must not be
// modifed yet
if( col_state[reduction.col].test( State::kModified ) )
{
msg.detailed( "CONFLICT lock col {}\n", reduction.col );
return ConflictType::kConflict;
}
break;
case ColReduction::BOUNDS_LOCKED:
if( col_state[reduction.col].test( State::kBoundsModified ) )
{
msg.detailed( "CONFLICT bounds lock col {}\n", reduction.col );
return ConflictType::kConflict;
}
break;
case ColReduction::OBJECTIVE:
case ColReduction::PARALLEL:
case ColReduction::SUBSTITUTE_OBJ:
break;
case ColReduction::SUBSTITUTE:
case ColReduction::REPLACE:
// we postponed the substitution to be performed last
if( postponeSubstitutions )
return ConflictType::kPostpone;
break;
default:
break;
}
}
else if( reduction.col < 0 )
{
assert( reduction.row >= 0 && reduction.col < 0 );
int rowop = reduction.col;
switch( rowop )
{
case RowReduction::LOCKED:
// if the transaction wants to lock the row it must not be
// modified yet
if( row_state[reduction.row].test( State::kModified,
State::kBoundsModified ) )
{
msg.detailed( "CONFLICT row lock row {}\n", reduction.row );
return ConflictType::kConflict;
}
break;
case RowReduction::LHS_INF:
case RowReduction::LHS:
case RowReduction::LHS_LESS_RESTRICTIVE:
case RowReduction::RHS_INF:
case RowReduction::RHS:
case RowReduction::RHS_LESS_RESTRICTIVE:
case RowReduction::SAVE_ROW:
break;
case RowReduction::SPARSIFY:
if( postponeSubstitutions )
return ConflictType::kPostpone;
default:
break;
}
}
}
// no conflicts found
return ConflictType::kNoConflict;
}
/// returns true if the given transaction was applied and false otherwise
template
ApplyResult
ProblemUpdate::applyTransaction( const Reduction* first,
const Reduction* last )
{
Objective& objective = problem.getObjective();
Vec& lbs = problem.getLowerBounds();
Vec& ubs = problem.getUpperBounds();
Vec& cflags = problem.getColFlags();
ConstraintMatrix& constraintMatrix = problem.getConstraintMatrix();
Vec& rflags = constraintMatrix.getRowFlags();
auto updateActivity = [this]( ActivityChange actChange, int rowid,
RowActivity& activity ) {
update_activity( actChange, rowid, activity );
};
// check if transaction conflicts with current state
ConflictType conflictType = checkTransactionConflicts( first, last );
if( conflictType == ConflictType::kConflict )
{
print_detailed( first, last );
return ApplyResult::kRejected;
}
else if( conflictType == ConflictType::kPostpone )
return ApplyResult::kPostponed;
print_detailed( first, last );
for( auto iter = first; iter < last; ++iter )
{
const auto& reduction = *iter;
if( reduction.row >= 0 && reduction.col >= 0 )
{
setRowState( reduction.row, State::kModified );
setColState( reduction.col, State::kModified );
postsolve.storeCoefficientChange( reduction.row, reduction.col,
reduction.newval );
matrix_buffer.addEntry( reduction.row, reduction.col,
reduction.newval );
}
else if( reduction.row < 0 )
{
assert( reduction.col >= 0 );
int colop = reduction.row;
switch( colop )
{
case ColReduction::NONE:
assert( false );
break;
case ColReduction::OBJECTIVE:
setColState( reduction.col, State::kModified );
objective.coefficients[reduction.col] = reduction.newval;
break;
case ColReduction::FIXED:
{
if( fixCol( reduction.col, reduction.newval ) ==
PresolveStatus::kInfeasible )
return ApplyResult::kInfeasible;
break;
}
case ColReduction::FIXED_INFINITY:
{
if( fixColInfinity( reduction.col, reduction.newval ) ==
PresolveStatus::kInfeasible )
return ApplyResult::kInfeasible;
break;
}
case ColReduction::LOWER_BOUND:
{
if( changeLB( reduction.col, reduction.newval ) ==
PresolveStatus::kInfeasible )
return ApplyResult::kInfeasible;
break;
}
case ColReduction::UPPER_BOUND:
{
if( changeUB( reduction.col, reduction.newval ) ==
PresolveStatus::kInfeasible )
return ApplyResult::kInfeasible;
break;
}
case ColReduction::IMPL_INT:
{
if( !cflags[reduction.col].test( ColFlag::kInactive ) )
{
cflags[reduction.col].set( ColFlag::kImplInt );
if( !cflags[reduction.col].test( ColFlag::kLbInf ) )
{
if( changeLB( reduction.col, lbs[reduction.col] ) ==
PresolveStatus::kInfeasible )
return ApplyResult::kInfeasible;
}
if( !cflags[reduction.col].test( ColFlag::kUbInf ) )
{
if( changeUB( reduction.col, ubs[reduction.col] ) ==
PresolveStatus::kInfeasible )
return ApplyResult::kInfeasible;
}
}
break;
}
case ColReduction::SUBSTITUTE:
{
int col = reduction.col;
int equalityrow = static_cast( reduction.newval );
if( constraintMatrix.getRowCoefficients( equalityrow )
.getLength() == 1 )
{
assert( !rflags[equalityrow].test( RowFlag::kLhsInf,
RowFlag::kRhsInf ) );
REAL val = constraintMatrix.getLeftHandSides()[equalityrow] /
constraintMatrix.getRowCoefficients( equalityrow )
.getValues()[0];
if( fixCol( col, val ) == PresolveStatus::kInfeasible )
return ApplyResult::kInfeasible;
break;
}
assert( row_state[equalityrow].equal( State::kUnmodified ) );
assert( !col_state[col].test( State::kBoundsModified ) );
// check that the conditions for substitution are verified
if( !constraintMatrix.checkAggregationSparsityCondition(
col, constraintMatrix.getRowCoefficients( equalityrow ),
presolveOptions.maxfillinpersubstitution,
presolveOptions.maxshiftperrow, intbuffer ) )
{
msg.detailed( "canceled\n" );
return ApplyResult::kRejected;
}
const auto colvec = constraintMatrix.getColumnCoefficients( col );
const int* colindices = colvec.getIndices();
const int nbrelevantrows = colvec.getLength();
postsolve.storeSubstitution( col, equalityrow, problem );
assert(
!cflags[col].test( ColFlag::kSubstituted, ColFlag::kFixed ) );
cflags[col].set( ColFlag::kSubstituted );
// change the objective coefficients and offset
problem.substituteVarInObj( num, col, equalityrow );
// update row states
msg.detailed( "modified rows: " );
for( int k = 0; k < nbrelevantrows; ++k )
{
msg.detailed( "{},", colindices[k] );
setRowState( colindices[k], State::kModified );
}
msg.detailed( "\n" );
// update col states
const auto rowvec =
constraintMatrix.getRowCoefficients( equalityrow );
const int length = rowvec.getLength();
const int* indices = rowvec.getIndices();
msg.detailed( "modified columns: " );
for( int j = 0; j < length; ++j )
{
msg.detailed( "{},", indices[j] );
setColState( indices[j], State::kModified );
}
msg.detailed( "\n" );
auto eqRHS = constraintMatrix.getLeftHandSides()[equalityrow];
// make the changes in the constraint matrix
constraintMatrix.aggregate(
num, col, rowvec, eqRHS, problem.getVariableDomains(),
intbuffer, realbuffer, tripletbuffer, changed_activities,
problem.getRowActivities(), singletonRows, singletonColumns,
emptyColumns, stats.nrounds );
stats.ncoefchgs += length * nbrelevantrows;
assert( constraintMatrix.getRowSizes()[equalityrow] == -1 );
assert( constraintMatrix.getRowCoefficients( equalityrow )
.getLength() == 0 );
assert( constraintMatrix.getLeftHandSides()[equalityrow] ==
REAL{ 0 } );
assert( constraintMatrix.getRightHandSides()[equalityrow] ==
REAL{ 0 } );
assert( constraintMatrix.getColSizes()[col] == -1 );
assert( constraintMatrix.getColumnCoefficients( col ).getLength() ==
REAL{ 0 } );
assert( objective.coefficients[col] == REAL{ 0 } );
assert( row_state[equalityrow].test( State::kModified ) );
assert( col_state[col].test( State::kModified ) );
// statistics
++stats.ndeletedcols;
// statistics
++stats.ndeletedrows;
if( cflags[col].test( ColFlag::kIntegral ) )
--problem.getNumIntegralCols();
else
--problem.getNumContinuousCols();
if( constraintMatrix.getLeftHandSides()[equalityrow] != 0 )
stats.nsidechgs += 2 * nbrelevantrows;
break;
}
case ColReduction::SUBSTITUTE_OBJ:
{
int col = reduction.col;
int equalityrow = static_cast( reduction.newval );
assert( !cflags[col].test( ColFlag::kInactive ) );
cflags[col].set( ColFlag::kSubstituted );
const auto rowvec =
constraintMatrix.getRowCoefficients( equalityrow );
postsolve.storeSubstitution( col, equalityrow, problem );
// change the objective coefficients and offset
problem.substituteVarInObj( num, col, equalityrow );
auto colvec = constraintMatrix.getColumnCoefficients( col );
if( cflags[col].test( ColFlag::kLbUseless ) || lbs[col] != 0 )
update_activities_after_boundchange(
colvec.getValues(), colvec.getIndices(), colvec.getLength(),
BoundChange::kLower, lbs[col], REAL{ 0 },
cflags[col].test( ColFlag::kLbUseless ),
problem.getRowActivities(), updateActivity );
if( cflags[col].test( ColFlag::kUbUseless ) || ubs[col] != 0 )
update_activities_after_boundchange(
colvec.getValues(), colvec.getIndices(), colvec.getLength(),
BoundChange::kUpper, ubs[col], REAL{ 0 },
cflags[col].test( ColFlag::kUbUseless ),
problem.getRowActivities(), updateActivity );
cflags[col].unset( ColFlag::kLbUseless, ColFlag::kUbUseless );
lbs[col] = 0;
ubs[col] = 0;
deleted_cols.push_back( col );
const int length = rowvec.getLength();
const int* indices = rowvec.getIndices();
msg.detailed( "modified columns: ", ColReduction::SUBSTITUTE, col,
equalityrow );
for( int j = 0; j != length; ++j )
{
msg.detailed( "{},", indices[j] );
setColState( indices[j], State::kModified );
}
msg.detailed( "\n" );
// statistics
++stats.ndeletedcols;
if( cflags[col].test( ColFlag::kIntegral ) )
--problem.getNumIntegralCols();
else
--problem.getNumContinuousCols();
break;
}
case ColReduction::PARALLEL:
{
int col1 = reduction.col;
int col2 = static_cast( reduction.newval );
if( cflags[col1].test( ColFlag::kInactive ) ||
cflags[col2].test( ColFlag::kInactive ) )
return ApplyResult::kRejected;
setColState( col1, State::kBoundsModified );
setColState( col2, State::kBoundsModified );
auto col1vec = constraintMatrix.getColumnCoefficients( col1 );
auto col2vec = constraintMatrix.getColumnCoefficients( col2 );
const REAL* vals1 = col1vec.getValues();
const REAL* vals2 = col2vec.getValues();
assert( col1vec.getLength() > 0 );
REAL col2scale = vals1[0] / vals2[0];
assert( col2vec.getLength() == col1vec.getLength() );
assert( num.isEq( objective.coefficients[col1],
objective.coefficients[col2] * col2scale ) );
++stats.ndeletedcols;
merge_parallel_columns( col1, col2, col2scale, constraintMatrix,
lbs, ubs, cflags );
break;
}
case ColReduction::REPLACE:
{
int col1 = reduction.col;
REAL factor = reduction.newval;
// get the rest of the information from the next reduction
++iter;
assert( iter->row == ColReduction::NONE );
int col2 = iter->col;
REAL offset = iter->newval;
// one variable is fixed, try to fix the other one
if( cflags[col1].test( ColFlag::kFixed ) ||
cflags[col2].test( ColFlag::kFixed ) )
{
if( !cflags[col1].test( ColFlag::kFixed,
ColFlag::kSubstituted ) )
{
assert( cflags[col2].test( ColFlag::kFixed ) );
if( fixCol( col1, factor * lbs[col2] + offset ) ==
PresolveStatus::kInfeasible )
return ApplyResult::kInfeasible;
}
else if( !cflags[col2].test( ColFlag::kFixed,
ColFlag::kSubstituted ) )
{
assert( cflags[col1].test( ColFlag::kFixed ) );
if( fixCol( col2, ( lbs[col1] - offset ) / factor ) ==
PresolveStatus::kInfeasible )
return ApplyResult::kInfeasible;
}
break;
}
// one variable might have been substituted
if( cflags[col1].test( ColFlag::kFixed, ColFlag::kSubstituted ) ||
cflags[col2].test( ColFlag::kFixed, ColFlag::kSubstituted ) )
break;
assert( constraintMatrix.getColSizes()[col1] > 0 &&
constraintMatrix.getColSizes()[col2] > 0 );
REAL col2_imp_lb;
REAL col2_imp_ub;
if( factor > 0.0 )
{
col2_imp_lb = ( lbs[col1] - offset ) / factor;
col2_imp_ub = ( ubs[col1] - offset ) / factor;
}
else
{
col2_imp_lb = ( ubs[col1] - offset ) / factor;
col2_imp_ub = ( lbs[col1] - offset ) / factor;
}
if( col2_imp_lb > lbs[col2] )
{
if( changeLB( col2, col2_imp_lb ) ==
PresolveStatus::kInfeasible )
return ApplyResult::kInfeasible;
}
else if( col2_imp_ub < ubs[col2] )
{
if( changeUB( col2, col2_imp_ub ) ==
PresolveStatus::kInfeasible )
return ApplyResult::kInfeasible;
}
// set up the equality
// x_1 - factor * x_2 = offset
int indices[] = { col1, col2 };
REAL coefficients[] = { 1.0, -factor };
// argument needs to be sorted
if( col1 > col2 )
{
std::swap( indices[0], indices[1] );
std::swap( coefficients[0], coefficients[1] );
}
SparseVectorView equalityLHS( coefficients, indices, 2 );
// check sparsity
if( constraintMatrix.checkAggregationSparsityCondition(
col1, equalityLHS, presolveOptions.maxfillinpersubstitution,
presolveOptions.maxshiftperrow, intbuffer ) )
{
auto colvec = constraintMatrix.getColumnCoefficients( col1 );
const int* colindices = colvec.getIndices();
int length = colvec.getLength();
cflags[col1].set( ColFlag::kSubstituted );
if( cflags[col1].test( ColFlag::kIntegral ) )
--problem.getNumIntegralCols();
else
--problem.getNumContinuousCols();
// update row flags
msg.detailed( "modified rows: " );
for( int k = 0; k < length; ++k )
{
msg.detailed( "{},", colindices[k] );
setRowState( colindices[k], State::kModified );
}
msg.detailed( "\n" );
// perform changes in matrix and sides
//TODO:
postsolve.storeSubstitution( col1, equalityLHS, offset );
constraintMatrix.aggregate(
num, col1, equalityLHS, offset, problem.getVariableDomains(),
intbuffer, realbuffer, tripletbuffer, changed_activities,
problem.getRowActivities(), singletonRows, singletonColumns,
emptyColumns, stats.nrounds );
// update col flags
setColState( col1, State::kModified );
setColState( col2, State::kModified );
// change the objective
auto& obj = problem.getObjective();
auto& obj_coef = obj.coefficients;
if( obj_coef[col1] != REAL{ 0 } )
{
obj_coef[col2] += obj_coef[col1] * factor;
if( num.isZero( obj_coef[col2] ) )
obj_coef[col2] = REAL{ 0 };
obj.offset += obj_coef[col1] * offset;
obj_coef[col1] = REAL{ 0 };
}
// statistics
if( offset != REAL{ 0 } )
stats.nsidechgs += 2 * length;
stats.ncoefchgs += 2 * length;
++stats.ndeletedcols;
}
break;
}
default:
break;
}
}
else
{
assert( reduction.row >= 0 && reduction.col < 0 );
int rowop = reduction.col;
switch( rowop )
{
case RowReduction::NONE:
assert( false );
break;
case RowReduction::SAVE_ROW:
{
int row = reduction.row;
postsolve.storeSavedRow( row,
constraintMatrix.getRowCoefficients( row ),
constraintMatrix.getLeftHandSides()[row],
constraintMatrix.getRightHandSides()[row],
problem.getRowFlags()[row] );
}
break;
case RowReduction::LHS:
assert( rflags[reduction.row].test( RowFlag::kLhsInf ) ||
reduction.newval !=
constraintMatrix.getLeftHandSides()[reduction.row] );
setRowState( reduction.row, State::kBoundsModified );
if( rflags[reduction.row].test( RowFlag::kLhsInf ) )
{
auto rowvec =
constraintMatrix.getRowCoefficients( reduction.row );
const int rowlen = rowvec.getLength();
const int* rowcols = rowvec.getIndices();
msg.detailed( "modified columns: " );
for( int i = 0; i < rowlen; ++i )
{
msg.detailed( "{},", rowcols[i] );
setColState( rowcols[i], State::kModified );
}
msg.detailed( "\n" );
}
if( !rflags[reduction.row].test( RowFlag::kRhsInf ) &&
num.isFeasGT(
reduction.newval,
constraintMatrix.getRightHandSides()[reduction.row] ) )
{
Message::debug(
this,
"fixing the lhs of row {} with bounds [{},{}] to value {} "
"is "
"detected to be infeasible\n",
reduction.row,
rflags[reduction.row].test( RowFlag::kLhsInf )
? -std::numeric_limits::infinity()
: double( constraintMatrix
.getLeftHandSides()[reduction.row] ),
double(
constraintMatrix.getRightHandSides()[reduction.row] ),
double( reduction.newval ) );
return ApplyResult::kInfeasible;
}
postsolve.storeRowBoundChange(
true, reduction.row, reduction.newval, false,
constraintMatrix.getLeftHandSides()[reduction.row],
constraintMatrix.getRowFlags()[reduction.row].test(
RowFlag::kLhsInf ) );
constraintMatrix.modifyLeftHandSide( reduction.row, num,
reduction.newval );
++stats.nsidechgs;
break;
case RowReduction::LHS_LESS_RESTRICTIVE:
assert( rflags[reduction.row].test( RowFlag::kLhsInf ) ||
reduction.newval !=
constraintMatrix.getLeftHandSides()[reduction.row] );
setRowState( reduction.row, State::kBoundsModified );
if( !rflags[reduction.row].test( RowFlag::kRhsInf ) &&
num.isFeasGT(
reduction.newval,
constraintMatrix.getRightHandSides()[reduction.row] ) )
{
Message::debug(
this,
"fixing the lhs of row {} with bounds [{},{}] to value {} "
"is "
"detected to be infeasible\n",
reduction.row,
rflags[reduction.row].test( RowFlag::kLhsInf )
? -std::numeric_limits::infinity()
: double( constraintMatrix
.getLeftHandSides()[reduction.row] ),
double(
constraintMatrix.getRightHandSides()[reduction.row] ),
double( reduction.newval ) );
return ApplyResult::kInfeasible;
}
constraintMatrix.modifyLeftHandSide( reduction.row, num,
reduction.newval );
postsolve.storeRowBoundChangeForcedByRow( true, reduction.row,
reduction.newval, false );
++stats.nsidechgs;
break;
case RowReduction::REASON_FOR_LESS_RESTRICTIVE_BOUND_CHANGE:
{
REAL factor = problem.getConstraintMatrix()
.getRowCoefficients( (int)reduction.newval )
.getValues()[0] /
problem.getConstraintMatrix()
.getRowCoefficients( reduction.row )
.getValues()[0];
postsolve.storeReasonForRowBoundChangeForcedByRow(
(int)reduction.newval, reduction.row, factor );
break;
}
case RowReduction::RHS:
assert( rflags[reduction.row].test( RowFlag::kRhsInf ) ||
reduction.newval !=
constraintMatrix.getRightHandSides()[reduction.row] );
setRowState( reduction.row, State::kBoundsModified );
if( rflags[reduction.row].test( RowFlag::kRhsInf ) )
{
auto rowvec =
constraintMatrix.getRowCoefficients( reduction.row );
const int rowlen = rowvec.getLength();
const int* rowcols = rowvec.getIndices();
msg.detailed( "modified columns: " );
for( int i = 0; i < rowlen; ++i )
{
msg.detailed( "{},", rowcols[i] );
setColState( rowcols[i], State::kModified );
}
msg.detailed( "\n" );
}
if( !rflags[reduction.row].test( RowFlag::kLhsInf ) &&
num.isFeasGT(
constraintMatrix.getLeftHandSides()[reduction.row],
reduction.newval ) )
{
Message::debug(
this,
"fixing the rhs of row {} with bounds [{},{}] to value {} "
"is "
"detected to be infeasible\n",
reduction.row,
double( constraintMatrix.getLeftHandSides()[reduction.row] ),
rflags[reduction.row].test( RowFlag::kRhsInf )
? -std::numeric_limits::infinity()
: double( constraintMatrix
.getRightHandSides()[reduction.row] ),
double( reduction.newval ) );
return ApplyResult::kInfeasible;
}
postsolve.storeRowBoundChange(
false, reduction.row, reduction.newval, false,
constraintMatrix.getRightHandSides()[reduction.row],
constraintMatrix.getRowFlags()[reduction.row].test(
RowFlag::kRhsInf ) );
constraintMatrix.modifyRightHandSide( reduction.row, num,
reduction.newval );
++stats.nsidechgs;
break;
case RowReduction::RHS_LESS_RESTRICTIVE:
assert( rflags[reduction.row].test( RowFlag::kRhsInf ) ||
reduction.newval !=
constraintMatrix.getRightHandSides()[reduction.row] );
setRowState( reduction.row, State::kBoundsModified );
if( !rflags[reduction.row].test( RowFlag::kLhsInf ) &&
num.isFeasGT(
constraintMatrix.getLeftHandSides()[reduction.row],
reduction.newval ) )
{
Message::debug(
this,
"fixing the rhs of row {} with bounds [{},{}] to value {} "
"is "
"detected to be infeasible\n",
reduction.row,
double( constraintMatrix.getLeftHandSides()[reduction.row] ),
rflags[reduction.row].test( RowFlag::kRhsInf )
? -std::numeric_limits::infinity()
: double( constraintMatrix
.getRightHandSides()[reduction.row] ),
double( reduction.newval ) );
return ApplyResult::kInfeasible;
}
constraintMatrix.modifyRightHandSide( reduction.row, num,
reduction.newval );
postsolve.storeRowBoundChangeForcedByRow( false, reduction.row,
reduction.newval, false );
++stats.nsidechgs;
break;
case RowReduction::LHS_INF:
if( !rflags[reduction.row].test( RowFlag::kLhsInf ) )
{
setRowState( reduction.row, State::kBoundsModified );
postsolve.storeRowBoundChange(
true, reduction.row, REAL{ 0 }, true, REAL{ 0 },
constraintMatrix.getRowFlags()[reduction.row].test(
RowFlag::kLhsInf ) );
constraintMatrix.template modifyLeftHandSide(
reduction.row, num, REAL{ 0 } );
++stats.nsidechgs;
}
break;
case RowReduction::RHS_INF:
if( !rflags[reduction.row].test( RowFlag::kRhsInf ) )
{
setRowState( reduction.row, State::kBoundsModified );
postsolve.storeRowBoundChange(
false, reduction.row, REAL{ 0 }, true, REAL{ 0 },
constraintMatrix.getRowFlags()[reduction.row].test(
RowFlag::kRhsInf ) );
constraintMatrix.template modifyRightHandSide(
reduction.row, num, REAL{ 0 } );
++stats.nsidechgs;
}
break;
case RowReduction::REDUNDANT:
if( !rflags[reduction.row].test( RowFlag::kRedundant ) )
{
setRowState( reduction.row, State::kBoundsModified );
markRowRedundant( reduction.row );
}
break;
case RowReduction::SPARSIFY:
{
int nsparsifyrows = static_cast( reduction.newval );
int eqrow = reduction.row;
assert( matrix_buffer.empty() );
int ncancel = 0;
int ncanceledrows = 0;
auto eqrowvec = constraintMatrix.getRowCoefficients( eqrow );
const REAL& eqrhs = constraintMatrix.getRightHandSides()[eqrow];
int eqlen = eqrowvec.getLength();
for( int i = 0; i != nsparsifyrows; ++i )
{
++iter;
int candrow = iter->row;
const REAL& scale = iter->newval;
assert( candrow != eqrow );
int canceled = constraintMatrix.sparsify(
num, eqrow, scale, candrow, intbuffer, realbuffer,
problem.getVariableDomains(), changed_activities,
problem.getRowActivities(), singletonRows, singletonColumns,
emptyColumns, stats.nrounds );
if( canceled != 0 )
{
setRowState( candrow, State::kModified );
msg.detailed( "modified rows: {}, \n", candrow );
++ncanceledrows;
ncancel += canceled;
if( eqrhs != 0 )
{
if( !rflags[candrow].test( RowFlag::kLhsInf ) )
++stats.nsidechgs;
if( !rflags[candrow].test( RowFlag::kRhsInf ) )
++stats.nsidechgs;
}
}
}
if( ncancel != 0 )
{
stats.ncoefchgs += eqlen * ncanceledrows;
const int* eqrowcols = eqrowvec.getIndices();
msg.detailed( "modified columns: " );
for( int j = 0; j != eqlen; ++j )
{
msg.detailed( "{},", eqrowcols[j] );
setColState( eqrowcols[j], State::kModified );
}
msg.detailed( "\n" );
}
}
break;
default:
break;
}
}
}
// no conflicts found
return ApplyResult::kApplied;
}
template
void
ProblemUpdate::merge_parallel_columns(
int col1, int col2, REAL col2scale,
ConstraintMatrix& constraintMatrix, Vec& lbs, Vec& ubs,
Vec& cflags )
{
const SparseVectorView& col1vec = constraintMatrix.getColumnCoefficients(col1);
const SparseVectorView& col2vec = constraintMatrix.getColumnCoefficients(col2);
bool col1lbinf = cflags[col1].test( ColFlag::kLbInf );
bool col1ubinf = cflags[col1].test( ColFlag::kUbInf );
bool col1int = cflags[col1].test( ColFlag::kIntegral );
bool col2lbinf = cflags[col2].test( ColFlag::kLbInf );
bool col2ubinf = cflags[col2].test( ColFlag::kUbInf );
bool col2int = cflags[col2].test( ColFlag::kIntegral );
postsolve.storeParallelCols( col1, col1int, col1lbinf, lbs[col1], col1ubinf,
ubs[col1], col2, col2int, col2lbinf, lbs[col2],
col2ubinf, ubs[col2], col2scale );
auto updateActivity = [this]( ActivityChange actChange, int rowid,
RowActivity& activity ) {
update_activity( actChange, rowid, activity );
};
const int* inds = col1vec.getIndices();
const REAL* vals1 = col1vec.getValues();
const REAL* vals2 = col2vec.getValues();
const int collen = col1vec.getLength();
// compute the new domains for column 2
REAL newlb = 0;
REAL newub = 0;
ColFlags newflags;
newflags.set( ColFlag::kLbInf, ColFlag::kUbInf );
// in the case that column 1 is not integral the new column
// is also not integral regardless of whether column 2 is integral
// or not (the necessary conditions must have been checked by the
// presolver)
if( cflags[col1].test( ColFlag::kIntegral ) )
{
--problem.getNumIntegralCols();
newflags.set( ColFlag::kIntegral );
}
else if( cflags[col2].test( ColFlag::kIntegral ) )
--problem.getNumIntegralCols();
else
--problem.getNumContinuousCols();
if( col2scale < 0 )
{
if( !col2lbinf && !col1ubinf )
{
newlb = lbs[col2] + col2scale * ubs[col1];
newflags.unset( ColFlag::kLbInf );
if( cflags[col1].test( ColFlag::kUbHuge ) ||
cflags[col2].test( ColFlag::kLbHuge ) )
newflags.set( ColFlag::kLbHuge );
}
if( !col2ubinf && !col1lbinf )
{
newub = ubs[col2] + col2scale * lbs[col1];
newflags.unset( ColFlag::kUbInf );
if( cflags[col1].test( ColFlag::kLbHuge ) ||
cflags[col2].test( ColFlag::kUbHuge ) )
newflags.set( ColFlag::kUbHuge );
}
}
else
{
if( !col2lbinf && !col1lbinf )
{
newlb = lbs[col2] + col2scale * lbs[col1];
newflags.unset( ColFlag::kLbInf );
if( cflags[col1].test( ColFlag::kLbHuge ) ||
cflags[col2].test( ColFlag::kLbHuge ) )
newflags.set( ColFlag::kLbHuge );
}
if( !col2ubinf && !col1ubinf )
{
newub = ubs[col2] + col2scale * ubs[col1];
newflags.unset( ColFlag::kUbInf );
if( cflags[col1].test( ColFlag::kUbHuge ) ||
cflags[col2].test( ColFlag::kUbHuge ) )
newflags.set( ColFlag::kUbHuge );
}
}
// update the activities if required
if( newflags.test( ColFlag::kLbUseless ) )
{
// the new columns lower bound does not contribute to the
// activities
if( !cflags[col2].test( ColFlag::kLbUseless ) )
{
// The current bound of column 2 contributes to the activity,
// therefore column 1 must have a infinite or huge bound from
// which we keep the infinite contribution for the new columns
// domains. The finite constribution of the lower bound of
// column 2 is removed.
if( lbs[col2] != 0 )
{
update_activities_after_boundchange(
vals2, inds, collen, BoundChange::kLower, lbs[col2], REAL{ 0 },
false, problem.getRowActivities(),
[]( ActivityChange, int, const RowActivity