/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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_DUAL_FIX_HPP_ #define _PAPILO_PRESOLVERS_DUAL_FIX_HPP_ #include "papilo/core/PresolveMethod.hpp" #include "papilo/core/Problem.hpp" #include "papilo/core/ProblemUpdate.hpp" namespace papilo { /// dual-fixing presolve method which looks at the coefficients of the objective /// and the column entries and performs a dual fixing if possible /// If fixing is not possible, it tries to strengthen the bounds. template class DualFix : public PresolveMethod { public: DualFix() : PresolveMethod() { this->setName( "dualfix" ); this->setTiming( PresolverTiming::kMedium ); } bool is_fix_to_infinity_allowed = true; bool initialize( const Problem& problem, const PresolveOptions& presolveOptions ) override { if( presolveOptions.dualreds == 0 ) this->setEnabled( false ); return false; } void addPresolverParams( ParameterSet& paramSet ) override { paramSet.addParameter( "dualfix.is_fix_to_infinity_allowed", "should variables be set to infinity if their objective value is 0?", is_fix_to_infinity_allowed ); } void set_fix_to_infinity_allowed( bool val){ is_fix_to_infinity_allowed = val; }; PresolveStatus execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) override; private: PresolveStatus perform_dual_fix_step( const Num& num, Reductions& reductions, const ConstraintMatrix& consMatrix, const Vec>& activities, const Vec& cflags, const Vec& objective, const Vec& lbs, const Vec& ubs, const Vec& rflags, const Vec& lhs, const Vec& rhs, int& i, bool no_strong_reductions, bool skip_variable_tightening, REAL bound_tightening_offset ) const; }; #ifdef PAPILO_USE_EXTERN_TEMPLATES extern template class DualFix; extern template class DualFix; extern template class DualFix; #endif template PresolveStatus DualFix::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& cflags = problem.getColFlags(); const Vec& objective = problem.getObjective().coefficients; const Vec& lbs = problem.getLowerBounds(); const Vec& ubs = problem.getUpperBounds(); const int ncols = consMatrix.getNCols(); const Vec& rflags = consMatrix.getRowFlags(); const Vec& lhs = consMatrix.getLeftHandSides(); const Vec& rhs = consMatrix.getRightHandSides(); PresolveStatus result = PresolveStatus::kUnchanged; bool noStrongReductions = problemUpdate.getPresolveOptions().dualreds < 2; // calculating the basis for variable tightening (not fixings) may lead in // the postsolving step to a solution that is not in a vertex. In this case a // crossover would be required is too expensive performance wise // Exception: for infinity bounds set a finite bound that is worse that the best possible bound const bool skip_variable_tightening = problem.getNumIntegralCols() == 0 && problemUpdate.getPresolveOptions().calculate_basis_for_dual; const REAL bound_tightening_offset = REAL(problemUpdate.getPresolveOptions().get_variable_bound_tightening_offset()); #ifndef PAPILO_TBB assert( problemUpdate.getPresolveOptions().runs_sequential() ); #endif if( problemUpdate.getPresolveOptions().runs_sequential() || !problemUpdate.getPresolveOptions().dual_fix_parallel ) { for( int col = 0; col < ncols; ++col ) { PresolveStatus local_status = perform_dual_fix_step( num, reductions, consMatrix, activities, cflags, objective, lbs, ubs, rflags, lhs, rhs, col, noStrongReductions, skip_variable_tightening, bound_tightening_offset ); assert( local_status == PresolveStatus::kUnchanged || local_status == PresolveStatus::kReduced || local_status == PresolveStatus::kUnbndOrInfeas || local_status == PresolveStatus::kUnbounded ); if( local_status == PresolveStatus::kUnbounded || local_status == PresolveStatus::kUnbndOrInfeas) return local_status; else if( local_status == PresolveStatus::kReduced ) result = PresolveStatus::kReduced; } } #ifdef PAPILO_TBB else { Vec> stored_reductions( ncols ); bool unbounded = false; tbb::parallel_for( tbb::blocked_range( 0, ncols ), [&]( const tbb::blocked_range& r ) { for( int col = r.begin(); col < r.end(); ++col ) { PresolveStatus local_status = perform_dual_fix_step( num, stored_reductions[col], consMatrix, activities, cflags, objective, lbs, ubs, rflags, lhs, rhs, col, noStrongReductions, skip_variable_tightening, bound_tightening_offset ); assert( local_status == PresolveStatus::kUnchanged || local_status == PresolveStatus::kReduced || local_status == PresolveStatus::kUnbounded ); if( local_status == PresolveStatus::kUnbounded ) unbounded = true; else if( local_status == PresolveStatus::kReduced ) result = PresolveStatus::kReduced; } } ); if( unbounded ) return PresolveStatus::kUnbounded; else 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 DualFix::perform_dual_fix_step( const Num& num, Reductions& reductions, const ConstraintMatrix& consMatrix, const Vec>& activities, const Vec& cflags, const Vec& objective, const Vec& lbs, const Vec& ubs, const Vec& rflags, const Vec& lhs, const Vec& rhs, int& i, bool no_strong_reductions, bool skip_variable_tightening, REAL bound_tightening_offset ) const { // skip inactive columns if( cflags[i].test( ColFlag::kInactive ) ) return PresolveStatus::kUnchanged; // if strong dual reductions are not allowed, we cannot dual fix variables // with zero objective if( no_strong_reductions && objective[i] == 0 ) return PresolveStatus::kUnchanged; PresolveStatus result = PresolveStatus::kUnchanged; auto colvec = consMatrix.getColumnCoefficients( i ); int collen = colvec.getLength(); const REAL* values = colvec.getValues(); const int* rowinds = colvec.getIndices(); int nuplocks = 0; int ndownlocks = 0; // count "lock" for objective function if( objective[i] < 0 ) ++ndownlocks; else if( objective[i] > 0 ) ++nuplocks; for( int j = 0; j != collen; ++j ) { count_locks( values[j], rflags[rowinds[j]], ndownlocks, nuplocks ); if( nuplocks != 0 && ndownlocks != 0 ) break; } // fix column to lower or upper bound if( ndownlocks == 0 ) { assert( cflags[i].test( ColFlag::kUnbounded ) || ubs[i] != lbs[i] ); // use a transaction and lock the column to protect it from changes // of other presolvers if( !cflags[i].test( ColFlag::kLbInf ) ) { TransactionGuard guard{ reductions }; reductions.lockCol( i ); reductions.fixCol( i, lbs[i] ); return PresolveStatus::kReduced; } else if( objective[i] != 0 ) { return PresolveStatus::kUnbndOrInfeas; } else if( is_fix_to_infinity_allowed ) { TransactionGuard guard{ reductions }; reductions.lockCol( i ); reductions.fixColNegativeInfinity( i, collen, rowinds ); return PresolveStatus::kReduced; } } else if( nuplocks == 0 ) { assert( cflags[i].test( ColFlag::kUnbounded ) || ubs[i] != lbs[i] ); if( !cflags[i].test( ColFlag::kUbInf ) ) { TransactionGuard guard{ reductions }; reductions.lockCol( i ); reductions.fixCol( i, ubs[i] ); return PresolveStatus::kReduced; } else if( objective[i] != 0 ) { return PresolveStatus::kUnbndOrInfeas; } else if( is_fix_to_infinity_allowed ) { TransactionGuard guard{ reductions }; reductions.lockCol( i ); reductions.fixColPositiveInfinity( i, collen, rowinds ); return PresolveStatus::kReduced; } } // apply dual substitution else { // Function checks if considered row allows dual bound strengthening // and calculates tightest bound for this row. auto check_row = []( int ninf, REAL activity, const REAL& side, const REAL& coeff, const REAL& boundval, bool boundinf, bool& skip_variable, REAL& cand_bound ) { switch( ninf ) { case 0: assert( !boundinf ); // calculate residual activity activity -= boundval * coeff; break; case 1: if( boundinf ) break; skip_variable = true; return; default: // If one of the other variables with non-zero entry is // unbounded, dual bound strengthening is not possible for this // column; skip column. skip_variable = true; return; } // calculate candidate for new bound cand_bound = ( side - activity ) / coeff; }; // If c_i >= 0, we might derive a tighter upper bound. // We consider only rows of // M := { (a_ji < 0 and rhs != inf) or (a_ji > 0 and lhs != inf)}. // If all constraints in M get redundant for x_i = new_UB, the upper // bound can be set to new_UB. if( objective[i] >= 0 ) { // in case of skip_variable_tightening set bounds to an infinite value // if possible, but increase the bound slightly if( skip_variable_tightening && ! cflags[i].test( ColFlag::kUbInf ) ) return PresolveStatus::kUnchanged; bool skip_performing = false; bool new_ub_init = false; REAL new_ub = REAL(0); int best_row = -1; // go through all rows with non-zero entry for( int j = 0; j < collen; ++j ) { int row = rowinds[j]; // candidate for new upper bound REAL cand_bound; if( consMatrix.isRowRedundant( row ) ) continue; // if row is in M, calculate candidate for new upper bound if( values[j] < 0.0 ) { if( !rflags[row].test( RowFlag::kRhsInf ) ) { check_row( activities[row].ninfmax, activities[row].max, rhs[row], values[j], lbs[i], cflags[i].test( ColFlag::kLbInf ), skip_performing, cand_bound ); } else // row is not in M continue; } else { if( !rflags[row].test( RowFlag::kLhsInf ) ) { check_row( activities[row].ninfmin, activities[row].min, lhs[row], values[j], lbs[i], cflags[i].test( ColFlag::kLbInf ), skip_performing, cand_bound ); } else // row is not in M continue; } if( skip_performing ) break; // Only if variable is greater than or equal to new_UB, all rows // in M are redundant. // I. e. we round up for integer variables. if( cflags[i].test( ColFlag::kIntegral ) ) cand_bound = num.epsCeil( cand_bound ); if( !new_ub_init || cand_bound > new_ub ) { new_ub = cand_bound; new_ub_init = true; best_row = row; // check if bound is already equal or worse than current bound // and abort in that case if( ( !cflags[i].test( ColFlag::kUbInf ) && num.isGE( new_ub, ubs[i] ) ) || new_ub >= num.getHugeVal() ) { skip_performing = true; break; } } } // set new upper bound if( !skip_performing && new_ub_init && !num.isHugeVal( new_ub ) ) { assert( cflags[i].test( ColFlag::kUbInf ) || new_ub < ubs[i] ); // cannot detect infeasibility with this method, so at most // tighten the bound to the lower bound if( !cflags[i].test( ColFlag::kLbInf ) ) new_ub = num.max( lbs[i], new_ub ); // A transaction is only needed to group several reductions that // belong together TransactionGuard guard{ reductions }; if( skip_variable_tightening ) new_ub += bound_tightening_offset; assert(best_row >= 0); reductions.lockCol( i ); reductions.lockColBounds( i ); reductions.changeColUB( i, new_ub, best_row ); Message::debug( this, "tightened upper bound of col {} to {}\n", i, double( new_ub ) ); return PresolveStatus::kReduced; // If new upper bound is set, we continue with the next column. // Although, If c=0, we can try to derive an additional lower // bound it will conflict with the locks of this reduction and // hence will never be applied. } } // If c_i <= 0, we might derive a tighter lower bound. // We consider only rows of // M := { (a_ji > 0 and rhs != inf) or (a_ji < 0 and lhs != inf)}. // If all constraints in M get redundant for x_i = new_LB, the lower // bound can be set to new_LB. if( objective[i] <= 0 ) { // in case of skip_variable_tightening set bounds to an infinite value // if possible, but increase the bound slightly if( skip_variable_tightening && ! cflags[i].test( ColFlag::kLbInf ) ) return PresolveStatus::kUnchanged; bool skip_ = false; bool new_lb_init = false; REAL new_lb = REAL(0); int best_row = -1; // go through all rows with non-zero entry for( int j = 0; j != collen; ++j ) { int row = rowinds[j]; // candidate for new lower bound REAL cand_bound; if( consMatrix.isRowRedundant( row ) ) continue; // if row is in M, calculate candidate for new lower bound if( values[j] > 0.0 ) { if( !rflags[row].test( RowFlag::kRhsInf ) ) { check_row( activities[row].ninfmax, activities[row].max, rhs[row], values[j], ubs[i], cflags[i].test( ColFlag::kUbInf ), skip_, cand_bound ); } else // row is not in M continue; } else { if( !rflags[row].test( RowFlag::kLhsInf ) ) { check_row( activities[row].ninfmin, activities[row].min, lhs[row], values[j], ubs[i], cflags[i].test( ColFlag::kUbInf ), skip_, cand_bound ); } else // row is not in M continue; } if( skip_ ) break; // Only if variable is less than or equal to new_LB, all rows in // M are redundant. I. e. we round down for integer variables. if( cflags[i].test( ColFlag::kIntegral ) ) cand_bound = num.epsFloor( cand_bound ); if( !new_lb_init || cand_bound < new_lb ) { new_lb = cand_bound; new_lb_init = true; best_row = row; // check if bound is already equal or worse than current bound // and abort in that case if( ( !cflags[i].test( ColFlag::kLbInf ) && num.isLE( new_lb, lbs[i] ) ) || new_lb <= -num.getHugeVal() ) { skip_ = true; break; } } } // set new lower bound if( !skip_ && new_lb_init && !num.isHugeVal( new_lb ) ) { assert( cflags[i].test( ColFlag::kLbInf ) || new_lb > lbs[i] ); // cannot detect infeasibility with this method, so at most // tighten the bound to the upper bound if( !cflags[i].test( ColFlag::kUbInf ) ) new_lb = num.min( ubs[i], new_lb ); if( skip_variable_tightening ) new_lb -= bound_tightening_offset; // A transaction is only needed to group several reductions that // belong together TransactionGuard guard{ reductions }; reductions.lockCol( i ); reductions.lockColBounds( i ); reductions.changeColLB( i, new_lb, best_row ); Message::debug( this, "tightened lower bound of col {} to {}\n", i, double( new_lb ) ); return PresolveStatus::kReduced; } } } return result; } } // namespace papilo #endif