/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* */
/* 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_HPP_
#define _PAPILO_CORE_PROBLEM_HPP_
#include "papilo/core/ConstraintMatrix.hpp"
#include "papilo/core/Objective.hpp"
#include "papilo/core/SingleRow.hpp"
#include "papilo/core/VariableDomains.hpp"
#include "papilo/io/Message.hpp"
#include "papilo/misc/MultiPrecision.hpp"
#include "papilo/misc/StableSum.hpp"
#include "papilo/misc/String.hpp"
#include "papilo/misc/Vec.hpp"
#include "papilo/misc/fmt.hpp"
#ifdef PAPILO_TBB
#include "papilo/misc/tbb.hpp"
#endif
namespace papilo
{
/// struct to hold counters for up an downlocks of a column
struct Locks
{
int up;
int down;
template
void
serialize( Archive& ar, const unsigned int version )
{
ar& up;
ar& down;
}
};
/// class representing the problem consisting of the constraint matrix, the left
/// and right hand side values, the variable domains, the column bounds,
/// column integrality restrictions, and the objective function
template
class Problem
{
public:
/// set objective function
void
setObjective( Vec coefficients, REAL offset = 0.0 )
{
objective = Objective{ std::move( coefficients ), offset };
}
/// set objective function
void
setObjective( Objective&& obj )
{
objective = obj;
}
/// set (transposed) constraint matrix
void
setConstraintMatrix( SparseStorage cons_matrix, Vec lhs_values,
Vec rhs_values, Vec row_flags,
bool transposed = false )
{
assert( lhs_values.size() == rhs_values.size() );
assert( lhs_values.size() == row_flags.size() );
assert( ( transposed ? cons_matrix.getNCols()
: cons_matrix.getNRows() ) == row_flags.size() );
auto cons_matrix_other = cons_matrix.getTranspose();
if( transposed )
constraintMatrix = ConstraintMatrix{
std::move( cons_matrix_other ), std::move( cons_matrix ),
std::move( lhs_values ), std::move( rhs_values ),
std::move( row_flags ) };
else
constraintMatrix = ConstraintMatrix{
std::move( cons_matrix ), std::move( cons_matrix_other ),
std::move( lhs_values ), std::move( rhs_values ),
std::move( row_flags ) };
}
/// set constraint matrix
void
setConstraintMatrix( ConstraintMatrix&& cons_matrix )
{
constraintMatrix = cons_matrix;
}
/// set domains of variables
void
setVariableDomains( VariableDomains&& domains )
{
variableDomains = domains;
nintegers = 0;
ncontinuous = 0;
for( ColFlags cf : variableDomains.flags )
{
if( cf.test( ColFlag::kIntegral ) )
++nintegers;
else
++ncontinuous;
}
}
/// set domains of variables
void
setVariableDomains( Vec lower_bounds, Vec upper_bounds,
Vec col_flags )
{
variableDomains = VariableDomains{ std::move( lower_bounds ),
std::move( upper_bounds ),
std::move( col_flags ) };
nintegers = 0;
ncontinuous = 0;
for( ColFlags cf : variableDomains.flags )
{
if( cf.test( ColFlag::kIntegral ) )
++nintegers;
else
++ncontinuous;
}
}
/// returns number of active integral columns
int
getNumIntegralCols() const
{
return nintegers;
}
/// returns number of active integral columns
int&
getNumIntegralCols()
{
return nintegers;
}
/// returns number of active continuous columns
int
getNumContinuousCols() const
{
return ncontinuous;
}
/// returns number of active continuous columns
int&
getNumContinuousCols()
{
return ncontinuous;
}
/// set variable names
void
setVariableNames( Vec var_names )
{
variableNames = std::move( var_names );
}
/// set constraint names
void
setConstraintNames( Vec cons_names )
{
constraintNames = std::move( cons_names );
}
/// set problem name
void
setName( String name_ )
{
this->name = std::move( name_ );
}
/// get the problem matrix
const ConstraintMatrix&
getConstraintMatrix() const
{
return constraintMatrix;
}
/// get the problem matrix
ConstraintMatrix&
getConstraintMatrix()
{
return constraintMatrix;
}
/// get number of columns
int
getNCols() const
{
return constraintMatrix.getNCols();
}
/// get number of rows
int
getNRows() const
{
return constraintMatrix.getNRows();
}
/// get the objective function
const Objective&
getObjective() const
{
return objective;
}
/// get the objective function
Objective&
getObjective()
{
return objective;
}
/// get the variable domains
const VariableDomains&
getVariableDomains() const
{
return variableDomains;
}
/// get the variable domains
VariableDomains&
getVariableDomains()
{
return variableDomains;
}
const Vec&
getColFlags() const
{
return variableDomains.flags;
}
Vec&
getColFlags()
{
return variableDomains.flags;
}
const Vec&
getRowFlags() const
{
return constraintMatrix.getRowFlags();
}
Vec&
getRowFlags()
{
return constraintMatrix.getRowFlags();
}
/// get the variable names
const Vec&
getVariableNames() const
{
return variableNames;
}
/// get the constraint names
const Vec&
getConstraintNames() const
{
return constraintNames;
}
/// get the problem name
const String&
getName() const
{
return name;
}
/// get the (dense) vector of variable lower bounds
const Vec&
getLowerBounds() const
{
return variableDomains.lower_bounds;
}
/// get the (dense) vector of variable lower bounds
Vec&
getLowerBounds()
{
return variableDomains.lower_bounds;
}
/// get the (dense) vector of variable upper bounds
const Vec&
getUpperBounds() const
{
return variableDomains.upper_bounds;
}
/// get the (dense) vector of variable upper bounds
Vec&
getUpperBounds()
{
return variableDomains.upper_bounds;
}
/// get the (dense) vector of column sizes
const Vec&
getColSizes() const
{
return constraintMatrix.getColSizes();
}
/// get the (dense) vector of column sizes
Vec&
getColSizes()
{
return constraintMatrix.getColSizes();
}
/// get the (dense) vector of row sizes
const Vec&
getRowSizes() const
{
return constraintMatrix.getRowSizes();
}
/// get the (dense) vector of row sizes
Vec&
getRowSizes()
{
return constraintMatrix.getRowSizes();
}
/// substitute a variable in the objective using an equality constraint
/// given by a row index
void
substituteVarInObj( const Num& num, int col, int equalityrow );
bool
computeSolViolations( const Num& num, const Vec& sol,
REAL& boundviolation, REAL& rowviolation,
REAL& intviolation ) const
{
if( (int) sol.size() != getNCols() )
return false;
boundviolation = 0;
intviolation = 0;
for( int i = 0; i != getNCols(); ++i )
{
if( !variableDomains.flags[i].test( ColFlag::kLbInf ) &&
sol[i] < variableDomains.lower_bounds[i] )
{
REAL thisviol = variableDomains.lower_bounds[i] - sol[i];
if( !num.isFeasZero( thisviol ) )
Message::debug( this,
"lower bound {} of column {} with solution "
"value {} is violated by {}\n",
double( variableDomains.lower_bounds[i] ), i,
double( sol[i] ), double( thisviol ) );
boundviolation = num.max( boundviolation, thisviol );
}
if( !variableDomains.flags[i].test( ColFlag::kUbInf ) &&
sol[i] > variableDomains.upper_bounds[i] )
{
REAL thisviol = sol[i] - variableDomains.upper_bounds[i];
if( !num.isFeasZero( thisviol ) )
Message::debug( this,
"upper bound {} of column {} with solution "
"value {} is violated by {}\n",
double( variableDomains.upper_bounds[i] ), i,
double( sol[i] ), double( thisviol ) );
boundviolation = num.max( boundviolation, thisviol );
}
if( variableDomains.flags[i].test( ColFlag::kIntegral ) )
{
REAL thisviol = abs( num.round( sol[i] ) - sol[i] );
if( !num.isFeasZero( thisviol ) )
Message::debug( this,
"integrality of column {} with solution value "
"{} is violated by {}\n",
i, double( sol[i] ), double( thisviol ) );
intviolation = num.max( intviolation, thisviol );
}
}
rowviolation = 0;
const Vec& rflags = getRowFlags();
const Vec& lhs = constraintMatrix.getLeftHandSides();
const Vec& rhs = constraintMatrix.getRightHandSides();
for( int i = 0; i != getNRows(); ++i )
{
auto rowvec = constraintMatrix.getRowCoefficients( i );
const REAL* vals = rowvec.getValues();
const int* inds = rowvec.getIndices();
StableSum activitySum;
for( int j = 0; j != rowvec.getLength(); ++j )
activitySum.add( sol[inds[j]] * vals[j] );
REAL activity = activitySum.get();
if( !rflags[i].test( RowFlag::kRhsInf )
&& num.isFeasGT( activity, rhs[i] ) )
{
Message::debug( this,
"the activity {} of constraint {} "
"{} is greater than the righthandside {}\n",
activity, i, rhs[i] );
rowviolation = num.max( rowviolation, activity - rhs[i] );
}
if( !rflags[i].test( RowFlag::kLhsInf )
&& num.isFeasLT( activity, rhs[i] ) )
{
Message::debug( this,
"the activity {} of constraint {} "
"{} is greater than the lefthandside {}\n",
activity, i, lhs[i] );
rowviolation = num.max( rowviolation, lhs[i] - activity );
}
}
return num.isFeasZero( boundviolation ) &&
num.isFeasZero( intviolation ) && num.isFeasZero( rowviolation );
}
REAL
computeSolObjective( const Vec& sol ) const
{
assert( (int) sol.size() == getNCols() );
StableSum obj( objective.offset );
for( int i = 0; i < getNCols(); ++i )
obj.add( sol[i] * objective.coefficients[i] );
return obj.get();
}
/// return const reference to vector of row activities
const Vec>&
getRowActivities() const
{
return rowActivities;
}
/// return reference to vector of row activities
Vec>&
getRowActivities()
{
return rowActivities;
}
/// returns a reference to the vector of locks of each column, the locks
/// include the objective cutoff constraint
Vec&
getLocks()
{
return locks;
}
std::pair, Vec>
compress( bool full = false )
{
std::pair, Vec> mappings =
constraintMatrix.compress( full );
// update information about columns that is stored by index
#ifdef PAPILO_TBB
tbb::parallel_invoke(
[this, &mappings, full]() {
compress_vector( mappings.second, objective.coefficients );
if( full )
objective.coefficients.shrink_to_fit();
},
[this, &mappings, full]() {
variableDomains.compress( mappings.second, full );
},
[this, &mappings, full]() {
// compress row activities
// recomputeAllActivities();
if( rowActivities.size() != 0 )
compress_vector( mappings.first, rowActivities );
if( full )
rowActivities.shrink_to_fit();
} );
#else
compress_vector( mappings.second, objective.coefficients );
variableDomains.compress( mappings.second, full );
if( rowActivities.size() != 0 )
compress_vector( mappings.first, rowActivities );
if( full )
{
objective.coefficients.shrink_to_fit();
rowActivities.shrink_to_fit();
}
#endif
// compress row activities
return mappings;
}
/// sets the tolerance of the input format
void
setInputTolerance( REAL inputTolerance_ )
{
this->inputTolerance = std::move( inputTolerance_ );
}
void
recomputeAllActivities()
{
rowActivities.resize( getNRows() );
// loop through rows once, compute initial acitvities, detect trivial
// redundancy
#ifdef PAPILO_TBB
tbb::parallel_for(
tbb::blocked_range( 0, getNRows() ),
[this]( const tbb::blocked_range& r ) {
for( int row = r.begin(); row < r.end(); ++row )
#else
for( int row = 0; row < getNRows(); ++row )
#endif
{
auto rowvec = constraintMatrix.getRowCoefficients( row );
rowActivities[row] = compute_row_activity(
rowvec.getValues(), rowvec.getIndices(), rowvec.getLength(),
variableDomains.lower_bounds, variableDomains.upper_bounds,
variableDomains.flags );
}
#ifdef PAPILO_TBB
} );
#endif
}
void
recomputeLocks()
{
locks.resize( getNCols() );
// loop through rows once, compute initial activities, detect trivial
// redundancy
#ifdef PAPILO_TBB
tbb::parallel_for(
tbb::blocked_range( 0, getNCols() ),
[this]( const tbb::blocked_range& c ) {
for( int col = c.begin(); col != c.end(); ++col )
#else
for( int col = 0; col < getNCols(); ++col )
#endif
{
auto colvec = constraintMatrix.getColumnCoefficients( col );
const REAL* vals = colvec.getValues();
const int* inds = colvec.getIndices();
int len = colvec.getLength();
const auto& rflags = getRowFlags();
for( int i = 0; i != len; ++i )
count_locks( vals[i], rflags[inds[i]], locks[col].down,
locks[col].up );
}
#ifdef PAPILO_TBB
} );
#endif
}
std::pair
removeRedundantBounds( const Num& num, Vec& cflags,
Vec>& activities ) const
{
const Vec& lhs = constraintMatrix.getLeftHandSides();
const Vec& rhs = constraintMatrix.getRightHandSides();
const Vec& colsize = constraintMatrix.getColSizes();
const Vec& rflags = getRowFlags();
const Vec& lbs = getLowerBounds();
const Vec& ubs = getUpperBounds();
int nremoved = 0;
int nnewfreevars = 0;
Vec> colperm( getNCols() );
for( int i = 0; i != getNCols(); ++i )
colperm[i] = std::make_tuple(
colsize[i],
constraintMatrix.getColumnCoefficients( i ).getDynamism(), i );
pdqsort( colperm.begin(), colperm.end() );
for( const auto& tuple : colperm )
{
int col = std::get<2>( tuple );
if( cflags[col].test( ColFlag::kInactive ) ||
!cflags[col].test( ColFlag::kUnbounded ) )
continue;
auto colvec = constraintMatrix.getColumnCoefficients( col );
const int* colrows = colvec.getIndices();
const REAL* colvals = colvec.getValues();
const int collen = colvec.getLength();
int k = 0;
ColFlags colf = cflags[col];
while( ( !colf.test( ColFlag::kLbInf ) ||
!colf.test( ColFlag::kUbInf ) ) &&
k != collen )
{
int row = colrows[k];
if( rflags[row].test( RowFlag::kRedundant ) )
{
++k;
continue;
}
if( !colf.test( ColFlag::kLbInf ) &&
row_implies_LB( num, lhs[row], rhs[row], rflags[row],
activities[row], colvals[k], lbs[col], ubs[col],
cflags[col] ) )
colf.set( ColFlag::kLbInf );
if( !colf.test( ColFlag::kUbInf ) &&
row_implies_UB( num, lhs[row], rhs[row], rflags[row],
activities[row], colvals[k], lbs[col], ubs[col],
cflags[col] ) )
colf.set( ColFlag::kUbInf );
++k;
}
if( colf.test( ColFlag::kLbInf ) && colf.test( ColFlag::kUbInf ) )
{
int oldnremoved = nremoved;
if( !cflags[col].test( ColFlag::kLbInf ) )
{
update_activities_remove_finite_bound( colrows, colvals, collen,
BoundChange::kLower,
lbs[col], activities );
cflags[col].set( ColFlag::kLbInf );
++nremoved;
}
if( !cflags[col].test( ColFlag::kUbInf ) )
{
update_activities_remove_finite_bound( colrows, colvals, collen,
BoundChange::kUpper,
ubs[col], activities );
cflags[col].set( ColFlag::kUbInf );
++nremoved;
}
if( oldnremoved != nremoved )
++nnewfreevars;
}
}
return std::make_pair( nremoved, nnewfreevars );
}
template
void
serialize( Archive& ar, const unsigned int version )
{
ar& name;
ar& inputTolerance;
ar& objective;
ar& constraintMatrix;
ar& variableDomains;
ar& ncontinuous;
ar& nintegers;
ar& variableNames;
ar& constraintNames;
ar& rowActivities;
ar& locks;
}
private:
String name;
REAL inputTolerance{ 0 };
Objective objective;
ConstraintMatrix constraintMatrix;
VariableDomains variableDomains;
int ncontinuous;
int nintegers;
Vec variableNames;
Vec constraintNames;
/// minimal and maximal row activities
Vec> rowActivities;
/// up and down locks for each column
Vec locks;
};
template
void
Problem::substituteVarInObj( const Num& num, int col, int row )
{
auto& consMatrix = getConstraintMatrix();
auto& objcoefficients = getObjective().coefficients;
REAL freevarCoefInObj = objcoefficients[col];
if( freevarCoefInObj == REAL{ 0 } )
return;
const auto equalityrow = consMatrix.getRowCoefficients( row );
const int length = equalityrow.getLength();
const REAL* values = equalityrow.getValues();
const int* indices = equalityrow.getIndices();
int consid = consMatrix.getSparseIndex( col, row );
assert( consid >= 0 );
assert( indices[consid] == col );
REAL freevarCoefInCons = values[consid];
REAL substscale = -freevarCoefInObj / freevarCoefInCons;
objcoefficients[col] = REAL{ 0.0 };
for( int j = 0; j < length; ++j )
{
if( indices[j] == col )
continue;
REAL newobjcoeff = objcoefficients[indices[j]] + values[j] * substscale;
if( num.isZero( newobjcoeff ) )
newobjcoeff = 0;
objcoefficients[indices[j]] = newobjcoeff;
}
assert( consMatrix.getRowFlags()[row].test( RowFlag::kEquation ) &&
!consMatrix.getRowFlags()[row].test( RowFlag::kRhsInf ) &&
!consMatrix.getRowFlags()[row].test( RowFlag::kLhsInf ) &&
consMatrix.getLeftHandSides()[row] ==
consMatrix.getRightHandSides()[row] );
getObjective().offset -= consMatrix.getLeftHandSides()[row] * substscale;
}
} // namespace papilo
#endif