/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* This file is part of the program and library */ /* PaPILO --- Parallel Presolve for Integer and Linear Optimization */ /* */ /* Copyright (C) 2020-2022 Konrad-Zuse-Zentrum */ /* fuer Informationstechnik Berlin */ /* */ /* This program is free software: you can redistribute it and/or modify */ /* it under the terms of the GNU Lesser General Public License as published */ /* by the Free Software Foundation, either version 3 of the License, or */ /* (at your option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU Lesser General Public License for more details. */ /* */ /* You should have received a copy of the GNU Lesser General Public License */ /* along with this program. If not, see . */ /* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef _PAPILO_PRESOLVERS_GCD_REDUCTIONS_HPP_ #define _PAPILO_PRESOLVERS_GCD_REDUCTIONS_HPP_ #include "papilo/core/PresolveMethod.hpp" #include "papilo/core/Problem.hpp" #include "papilo/core/ProblemUpdate.hpp" #include "papilo/external/pdqsort/pdqsort.h" #include namespace papilo { /** * Simplify Inequalities removes the "unneccessary" variables in a constraint: * Example: * 15x1 +15x2 +7x3 +3x4 +y1 <= 26 * <=> 15x1 +15x2 <= 26 # delete variables * <=> x1 +x2 <=1 # divide by greatestCommonDivisor and round right side down * * if this is not possible, then the GCD is calculated, so this is used to * reduce rhs/lhs -> (floor[rhs/gcd]* gcd ) * Example * 15x1 +15x2 +10x3 +5x4 <= 18 -> 18 can be reduced to 15 * @tparam REAL */ template class SimplifyInequalities : public PresolveMethod { public: SimplifyInequalities() : PresolveMethod() { this->setName( "simplifyineq" ); this->setTiming( PresolverTiming::kMedium ); this->setType( PresolverType::kIntegralCols ); } PresolveStatus execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) override; private: REAL computeGreatestCommonDivisor( REAL val1, REAL val2, const Num& num ); void simplify( const REAL* values, const int* colinds, int rowLength, const RowActivity& activity, const RowFlags& rflag, const Vec& cflags, const REAL& rhs, const REAL& lhs, const Vec& lbs, const Vec& ubs, Vec& colOrder, Vec& coeffDelete, REAL& gcd, bool& change, const Num& num ); bool isUnbounded( int row, const Vec& rowFlags ) const; bool isRedundant( int row, const Vec& rflags ) const; bool isInfiniteActivity( const Vec>& activities, int row ) const; PresolveStatus perform_simplify_ineq_task( const Num& num, const ConstraintMatrix& consMatrix, const Vec>& activities, const Vec& rflags, const Vec& cflags, const Vec& lhs, const Vec& rhs, const Vec& lbs, const Vec& ubs, int row, Reductions& reductions, Vec& coefficientsThatCanBeDeleted, Vec& colOrder ); }; #ifdef PAPILO_USE_EXTERN_TEMPLATES extern template class SimplifyInequalities; extern template class SimplifyInequalities; extern template class SimplifyInequalities; #endif /*** * to calculate the Greatest Common Divisor heuristics are used according to * "Presolve Reductions in Mixed Integer Programming" from T. Achterberg et. al. * * - Euclidian algorithm for integral values (numerical issues for flaoting * point) * * 1. Divide all coefficients by a_min = min{|a_ij| j in supp(A_i)}. If this * leads to integer values for all coefficients return * d= a_min * gcd(a_i1/a_min,..., a_in /a_min) * * 2. Use a_min = 1/600 (multiply by 600), because it is a multiple of many * small integer values that arise as denominators in real-world problems * * @tparam REAL * @param val1 * @param val2 * @param num * @return gcd (with heuristics for floating points) */ template REAL SimplifyInequalities::computeGreatestCommonDivisor( REAL val1, REAL val2, const Num& num ) { auto is_int64_castable = [&num]( REAL val ) { return num.isIntegral( val ) && static_cast( val ) == val; }; if( num.isZero( val1 ) || num.isZero( val2 ) ) return 0; // gcd for integer values if( is_int64_castable( val1 ) && is_int64_castable( val2 ) ) { #ifndef BOOST_VERSION_NUMBER_PATCH return boost::gcd( static_cast( val1 ), static_cast( val2 ) ); #elif BOOST_VERSION_NUMBER_PATCH( BOOST_VERSION ) / 100 < 78 return boost::gcd( static_cast( val1 ), static_cast( val2 ) ); #else return boost::integer::gcd( static_cast( val1 ), static_cast( val2 ) ); #endif } // heuristic for fractional values // if max(abs(val1), abs(val2)) divided by d:=min(abs(val1), abs(val2)) is // integral, return d if( abs( val2 ) < abs( val1 ) ) { if( is_int64_castable( val1 / val2 ) ) return abs( val2 ); } else { if( is_int64_castable( val2 / val1 ) ) return abs( val1 ); } double multiplier = 600; if( is_int64_castable( multiplier * val1 ) && is_int64_castable( multiplier * val2 ) ) #ifndef BOOST_VERSION_NUMBER_PATCH return boost::gcd( static_cast( val1 * multiplier ), static_cast( val2 * multiplier ) ) / REAL{ multiplier }; #elif BOOST_VERSION_NUMBER_PATCH( BOOST_VERSION ) / 100 < 78 return boost::gcd( static_cast( val1 * multiplier ), static_cast( val2 * multiplier ) ) / REAL{ multiplier }; #else return boost::integer::gcd( static_cast( val1 * multiplier ), static_cast( val2 * multiplier ) ) / REAL{ multiplier }; #endif // applied heuristics didn't find an greatest common divisor return 0; } template void SimplifyInequalities::simplify( const REAL* values, const int* colinds, int rowLength, const RowActivity& activity, const RowFlags& rflag, const Vec& cflags, const REAL& rhs, const REAL& lhs, const Vec& lbs, const Vec& ubs, Vec& colOrder, Vec& coeffDelete, REAL& gcd, bool& change, const Num& num ) { auto maxActivity = activity.max; auto minActivity = activity.min; // sort the list 'colOrder' for integer/continuous and then for absolute // coefficient for( int i = 0; i < rowLength; ++i ) colOrder.push_back( i ); Vec::iterator start_cont; start_cont = partition( colOrder.begin(), colOrder.end(), [&colinds, &cflags]( int const& a ) { return cflags[colinds[a]].test( ColFlag::kIntegral ); } ); pdqsort( colOrder.begin(), start_cont, [&values]( int const& a, int const& b ) { return abs( values[a] ) > abs( values[b] ); } ); // check if continuous variables or variables with small absolut value // always fit into the constraint REAL resmaxact = maxActivity; REAL resminact = minActivity; assert( num.isGE( resmaxact, resminact ) ); // start value important for first variable gcd = values[colOrder[0]]; assert( gcd != 0 ); REAL siderest; bool redundant = false; // i is index of last non-redundant variable int i = 0; // iterate over ordered non-zero entries for( ; i != rowLength; ++i ) { int column_index = colOrder[i]; // break if variable not integral if( !cflags[colinds[column_index]].test( ColFlag::kIntegral ) ) break; // update gcd gcd = computeGreatestCommonDivisor( gcd, values[column_index], num ); if( num.isLE( gcd, 1 ) ) break; assert( !cflags[colinds[column_index]].test( ColFlag::kLbInf, ColFlag::kUbInf ) ); // update residual activities // attention: the calculation inaccuracy can be greater than epsilon if( values[column_index] > 0 ) { resmaxact -= values[column_index] * ubs[colinds[column_index]]; resminact -= values[column_index] * lbs[colinds[column_index]]; } else { resmaxact -= values[column_index] * lbs[colinds[column_index]]; resminact -= values[column_index] * ubs[colinds[column_index]]; } // calculate siderest if( !rflag.test( RowFlag::kRhsInf ) ) { siderest = rhs - num.epsFloor( rhs / gcd ) * gcd; } else { siderest = lhs - num.epsFloor( lhs / gcd ) * gcd; if( num.isZero( siderest ) ) siderest = gcd; } // check if the ordered variables on the right of i are redundant if( ( !rflag.test( RowFlag::kRhsInf ) && resmaxact <= siderest && num.isFeasLT( siderest - gcd, resminact ) ) || ( !rflag.test( RowFlag::kLhsInf ) && resminact >= siderest - gcd && num.isFeasGT( siderest, resmaxact ) ) ) { redundant = true; break; } } if( redundant ) { change = true; // safe indices of redundant variables for( int w = i + 1; w < rowLength; ++w ) { coeffDelete.push_back( colOrder[w] ); } } } template PresolveStatus SimplifyInequalities::execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) { const auto& consMatrix = problem.getConstraintMatrix(); const Vec>& activities = problem.getRowActivities(); const Vec& rflags = consMatrix.getRowFlags(); const Vec& cflags = problem.getColFlags(); const Vec& lhs = consMatrix.getLeftHandSides(); const Vec& rhs = consMatrix.getRightHandSides(); const int nrows = consMatrix.getNRows(); const Vec& lbs = problem.getLowerBounds(); const Vec& ubs = problem.getUpperBounds(); PresolveStatus result = PresolveStatus::kUnchanged; #ifndef PAPILO_TBB assert( problemUpdate.getPresolveOptions().runs_sequential() ); #endif if( problemUpdate.getPresolveOptions().runs_sequential() || !problemUpdate.getPresolveOptions().simplify_inequalities_parallel ) { // allocate only once Vec colOrder; Vec coefficientsThatCanBeDeleted; for( int row = 0; row < nrows; row++ ) { if( perform_simplify_ineq_task( num, consMatrix, activities, rflags, cflags, lhs, rhs, lbs, ubs, row, reductions, coefficientsThatCanBeDeleted, colOrder ) == PresolveStatus::kReduced ) result = PresolveStatus::kReduced; } } #ifdef PAPILO_TBB else { Vec> stored_reductions( nrows ); // iterate over all constraints and try to simplify them tbb::parallel_for( tbb::blocked_range( 0, nrows ), [&]( const tbb::blocked_range& r ) { // allocate only once per thread Vec colOrder; Vec coefficientsThatCanBeDeleted; for( int row = r.begin(); row < r.end(); ++row ) { PresolveStatus status = perform_simplify_ineq_task( num, consMatrix, activities, rflags, cflags, lhs, rhs, lbs, ubs, row, stored_reductions[row], coefficientsThatCanBeDeleted, colOrder ); if( status == PresolveStatus::kReduced ) result = PresolveStatus::kReduced; assert( status == PresolveStatus::kReduced || status == PresolveStatus::kUnchanged ); } } ); if( result == PresolveStatus::kUnchanged ) return PresolveStatus::kUnchanged; for( int i = 0; i < (int)stored_reductions.size(); ++i ) { Reductions reds = stored_reductions[i]; if( reds.size() > 0 ) { for( const auto& transaction : reds.getTransactions() ) { int start = transaction.start; int end = transaction.end; TransactionGuard guard{ reductions }; for( int c = start; c < end; c++ ) { Reduction& reduction = reds.getReduction( c ); reductions.add_reduction( reduction.row, reduction.col, reduction.newval ); } } } } } #endif return result; } template PresolveStatus SimplifyInequalities::perform_simplify_ineq_task( const Num& num, const ConstraintMatrix& consMatrix, const Vec>& activities, const Vec& rflags, const Vec& cflags, const Vec& lhs, const Vec& rhs, const Vec& lbs, const Vec& ubs, int row, Reductions& reductions, Vec& coefficientsThatCanBeDeleted, Vec& colOrder ) { PresolveStatus result = PresolveStatus::kUnchanged; auto rowvec = consMatrix.getRowCoefficients( row ); int rowLength = rowvec.getLength(); if( isRedundant( row, rflags ) || isUnbounded( row, rflags ) || isInfiniteActivity( activities, row ) || // ignore empty or bound-constraints rowLength < 2 ) return PresolveStatus::kUnchanged; const int* colinds = rowvec.getIndices(); REAL greatestCommonDivisor = 0; bool isSimplificationPossible = false; colOrder.clear(); coefficientsThatCanBeDeleted.clear(); simplify( rowvec.getValues(), colinds, rowLength, activities[row], rflags[row], cflags, rhs[row], lhs[row], lbs, ubs, colOrder, coefficientsThatCanBeDeleted, greatestCommonDivisor, isSimplificationPossible, num ); if( isSimplificationPossible ) { assert( greatestCommonDivisor >= 1 ); bool col_can_be_deleted = !coefficientsThatCanBeDeleted.empty(); bool rhs_needs_update = false; bool lhs_needs_update = false; REAL new_rhs = 0; REAL new_lhs = 0; if( !rflags[row].test( RowFlag::kRhsInf ) && rhs[row] != 0 ) { new_rhs = num.feasFloor( rhs[row] / greatestCommonDivisor ) * greatestCommonDivisor; rhs_needs_update = new_rhs != rhs[row]; } else if( !rflags[row].test( RowFlag::kLhsInf ) && lhs[row] != 0 ) { new_lhs = num.feasCeil( lhs[row] / greatestCommonDivisor ) * greatestCommonDivisor; lhs_needs_update = new_lhs != lhs[row]; } if( !rhs_needs_update && !lhs_needs_update && !col_can_be_deleted ) return PresolveStatus::kUnchanged; TransactionGuard guard{ reductions }; reductions.lockRow( row ); for( int col : coefficientsThatCanBeDeleted ) { reductions.changeMatrixEntry( row, colinds[col], 0 ); result = PresolveStatus::kReduced; } // round side to multiple of greatestCommonDivisor; don't divide // row by greatestCommonDivisor if( rhs_needs_update ) { assert( rhs[row] != 0 ); reductions.changeRowRHS( row, new_rhs ); result = PresolveStatus::kReduced; } if( lhs_needs_update ) { assert( lhs[row] != 0 ); reductions.changeRowLHS( row, new_lhs ); result = PresolveStatus::kReduced; } } return result; } template bool SimplifyInequalities::isInfiniteActivity( const Vec>& activities, int row ) const { return activities[row].ninfmax != 0 || activities[row].ninfmin != 0; } template bool SimplifyInequalities::isRedundant( int row, const Vec& rflags ) const { return rflags[row].test( RowFlag::kRedundant ); } template bool SimplifyInequalities::isUnbounded( int row, const Vec& rowFlags ) const { return !rowFlags[row].test( RowFlag::kRhsInf, RowFlag::kLhsInf ); } } // namespace papilo #endif