/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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