/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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_SIMPLE_FREEVAR_HPP_ #define _PAPILO_PRESOLVERS_SIMPLE_FREEVAR_HPP_ #include "papilo/core/PresolveMethod.hpp" #include "papilo/core/Problem.hpp" #include "papilo/core/ProblemUpdate.hpp" #include "papilo/misc/Num.hpp" #include "papilo/misc/fmt.hpp" #if BOOST_VERSION >= 107000 #include #else // use a copy to enable also older boost versions #include "papilo/misc/extended_euclidean.hpp" #endif // TODO: before this presolver starts feasibility needs to be checked // TODO: -> maybe do the simple check before? that means inf <= b <= sup // TODO: 4x + 2y = 4; x,y in {0,1} is substitute false namespace papilo { template class SimpleSubstitution : public PresolveMethod { public: SimpleSubstitution() : PresolveMethod() { this->setName( "doubletoneq" ); this->setTiming( PresolverTiming::kMedium ); } PresolveStatus execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) override; private: bool isConstraintsFeasibleWithGivenBounds( const Num& num, const Vec& lower_bounds, const Vec& upper_bounds, const REAL* vals, REAL rhs, int subst, int stay, const boost::integer::euclidean_result_t& res ) const; PresolveStatus perform_simple_subsitution_step( const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const VariableDomains& domains, const Vec& cflags, const ConstraintMatrix& constMatrix, const Vec& lhs_values, const Vec& rhs_values, const Vec& lower_bounds, const Vec& upper_bounds, const Vec& rflags, const Vec& rowperm, int k ); }; #ifdef PAPILO_USE_EXTERN_TEMPLATES extern template class SimpleSubstitution; extern template class SimpleSubstitution; extern template class SimpleSubstitution; #endif template PresolveStatus SimpleSubstitution::execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) { // go over the rows and get the equalities, extract the columns that // verify the conditions add them to a hash map, loop over the hash map // and compute the implied bounds and finally look for implied free // variables and add reductions const auto& domains = problem.getVariableDomains(); const auto& lower_bounds = domains.lower_bounds; const auto& upper_bounds = domains.upper_bounds; const auto& cflags = domains.flags; const auto& constMatrix = problem.getConstraintMatrix(); const auto& lhs_values = constMatrix.getLeftHandSides(); const auto& rhs_values = constMatrix.getRightHandSides(); const auto& rflags = constMatrix.getRowFlags(); const auto& nrows = constMatrix.getNRows(); const auto& rowperm = problemUpdate.getRandomRowPerm(); PresolveStatus result = PresolveStatus::kUnchanged; #ifndef PAPILO_TBB assert( problemUpdate.getPresolveOptions().runs_sequential() ); #endif if( problemUpdate.getPresolveOptions().runs_sequential() || !problemUpdate.getPresolveOptions().simple_substitution_parallel ) { for( int k = 0; k < nrows; ++k ) { PresolveStatus s = perform_simple_subsitution_step( problemUpdate, num, reductions, domains, cflags, constMatrix, lhs_values, rhs_values, lower_bounds, upper_bounds, rflags, rowperm, k ); if( s == PresolveStatus::kReduced ) result = PresolveStatus::kReduced; else if( s == PresolveStatus::kInfeasible ) return PresolveStatus::kInfeasible; } } #ifdef PAPILO_TBB else { PresolveStatus infeasible = PresolveStatus::kUnchanged; Vec> stored_reductions( nrows ); tbb::parallel_for( tbb::blocked_range( 0, nrows ), [&]( const tbb::blocked_range& r ) { for( int j = r.begin(); j < r.end(); ++j ) { PresolveStatus s = perform_simple_subsitution_step( problemUpdate, num, stored_reductions[j], domains, cflags, constMatrix, lhs_values, rhs_values, lower_bounds, upper_bounds, rflags, rowperm, j ); assert( s == PresolveStatus::kUnchanged || s == PresolveStatus::kReduced || s == PresolveStatus::kInfeasible ); if( s == PresolveStatus::kReduced ) result = PresolveStatus::kReduced; else if( s == PresolveStatus::kInfeasible ) infeasible = PresolveStatus::kInfeasible; } } ); if( infeasible == PresolveStatus::kInfeasible ) return PresolveStatus::kInfeasible; 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 SimpleSubstitution::perform_simple_subsitution_step( const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const VariableDomains& domains, const Vec& cflags, const ConstraintMatrix& constMatrix, const Vec& lhs_values, const Vec& rhs_values, const Vec& lower_bounds, const Vec& upper_bounds, const Vec& rflags, const Vec& rowperm, int k ) { PresolveStatus result; int i = rowperm[k]; // check that equality flag is correct or row is redundant assert( rflags[i].test( RowFlag::kRedundant ) || ( !rflags[i].test( RowFlag::kEquation ) && ( lhs_values[i] != rhs_values[i] || rflags[i].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) ) ) || ( rflags[i].test( RowFlag::kEquation ) && !rflags[i].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) && lhs_values[i] == rhs_values[i] ) ); if( rflags[i].test( RowFlag::kRedundant ) || !rflags[i].test( RowFlag::kEquation ) || constMatrix.getRowSizes()[i] != 2 ) return PresolveStatus::kUnchanged; auto rowvec = constMatrix.getRowCoefficients( i ); assert( rowvec.getLength() == 2 ); const REAL* vals = rowvec.getValues(); const int* inds = rowvec.getIndices(); REAL rhs = rhs_values[i]; int subst; int stay; if( cflags[inds[0]].test( ColFlag::kIntegral ) != cflags[inds[1]].test( ColFlag::kIntegral ) ) { if( cflags[inds[0]].test( ColFlag::kIntegral ) ) subst = 1; else subst = 0; stay = 1 - subst; } else if( cflags[inds[0]].test( ColFlag::kIntegral ) ) { assert( cflags[inds[1]].test( ColFlag::kIntegral ) ); if( abs( vals[0] ) < abs( vals[1] ) || ( abs( vals[0] ) == abs( vals[1] ) && problemUpdate.isColBetterForSubstitution( inds[0], inds[1] ) ) ) subst = 0; else subst = 1; stay = 1 - subst; if( !num.isIntegral( vals[stay] / vals[subst] ) ) { if( !num.isIntegral( vals[stay] ) || !num.isIntegral( vals[subst] ) ) return PresolveStatus::kUnchanged; auto res = boost::integer::extended_euclidean( static_cast( abs( vals[stay] ) ), static_cast( abs( vals[subst] ) ) ); if( !num.isIntegral( rhs / res.gcd ) ) return PresolveStatus::kInfeasible; // TODO: ensure isConstraintsFeasibleWithGivenBounds() works for negative sign else if( !isConstraintsFeasibleWithGivenBounds( num, lower_bounds, upper_bounds, vals, rhs, subst, stay, res ) ) return PresolveStatus::kInfeasible; else return PresolveStatus::kUnchanged; } // problem is infeasible if gcd (i.e. vals[subst]) is not divisor of // rhs if( !num.isFeasIntegral( rhs / vals[subst] ) ) return PresolveStatus::kInfeasible; } else { REAL absval0 = abs( vals[0] ); REAL absval1 = abs( vals[1] ); if( absval0 * problemUpdate.getPresolveOptions().markowitz_tolerance > absval1 ) subst = 0; else if( absval1 * problemUpdate.getPresolveOptions().markowitz_tolerance > absval0 ) subst = 1; else if( problemUpdate.isColBetterForSubstitution( inds[0], inds[1] ) ) subst = 0; else subst = 1; stay = 1 - subst; } result = PresolveStatus::kReduced; TransactionGuard guard{ reductions }; reductions.lockRow( i ); reductions.lockColBounds( inds[subst] ); REAL s = vals[subst] * vals[stay]; if( !cflags[inds[subst]].test( ColFlag::kLbInf ) ) { REAL staybound = ( rhs - vals[subst] * domains.lower_bounds[inds[subst]] ) / vals[stay]; if( s < 0 && ( cflags[inds[stay]].test( ColFlag::kLbInf ) || num.isGT( staybound, domains.lower_bounds[inds[stay]] ) ) ) reductions.changeColLB( inds[stay], staybound ); if( s > 0 && ( cflags[inds[stay]].test( ColFlag::kUbInf ) || num.isLT( staybound, domains.upper_bounds[inds[stay]] ) ) ) reductions.changeColUB( inds[stay], staybound ); } if( !cflags[inds[subst]].test( ColFlag::kUbInf ) ) { REAL staybound = ( rhs - vals[subst] * domains.upper_bounds[inds[subst]] ) / vals[stay]; if( s > 0 && ( cflags[inds[stay]].test( ColFlag::kLbInf ) || staybound > domains.lower_bounds[inds[stay]] ) ) reductions.changeColLB( inds[stay], staybound ); if( s < 0 && ( cflags[inds[stay]].test( ColFlag::kUbInf ) || staybound < domains.upper_bounds[inds[stay]] ) ) reductions.changeColUB( inds[stay], staybound ); } reductions.aggregateFreeCol( inds[subst], i ); return result; } /** * check if the aggregated variable y of the equation a2x1 + a2x2 = b is within its bounds. * 1. generate a solution for s a1 + t a2 = gcd(a1,a2) * 2. substitute variable x1 = -a2 y + s and x2 = a1 y + t * 3. check bounds of y * * see chapter 10.1.1 Constraint Integer Programming of Tobias Achterberg * */ template bool SimpleSubstitution::isConstraintsFeasibleWithGivenBounds( const Num& num, const Vec& lower_bounds, const Vec& upper_bounds, const REAL* vals, REAL rhs, int subst, int stay, const boost::integer::euclidean_result_t& res ) const { int res_x = vals[stay] < 0 ? res.x * -1 : res.x; int res_y = vals[subst] < 0 ? res.y * -1 : res.y; REAL initial_solution_for_x = res_x * rhs; REAL initial_solution_for_y = res_y * rhs; REAL factor = (int)(initial_solution_for_y * res.gcd / vals[stay]); REAL s = initial_solution_for_x + factor / res.gcd * vals[subst]; REAL t = initial_solution_for_y - factor / res.gcd * vals[stay]; REAL ub_sol_y = ( t - lower_bounds[subst] ) / vals[stay]; REAL lb_sol_y = ( t - upper_bounds[subst] ) / vals[stay]; if( vals[stay] < 0 ) std::swap( ub_sol_y, lb_sol_y ); REAL ub_sol_x = ( upper_bounds[stay] - s ) / vals[subst]; REAL lb_sol_x = ( lower_bounds[stay] - s ) / vals[subst]; if( vals[subst] < 0 ) std::swap( ub_sol_x, lb_sol_x ); return num.isFeasLE( num.epsCeil( lb_sol_y ), num.epsFloor( ub_sol_y ) ) && num.isFeasLE( num.epsCeil( lb_sol_x ), num.epsFloor( ub_sol_x ) ); } } // namespace papilo #endif