/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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_CONSTRAINT_PROPAGATION_HPP_ #define _PAPILO_PRESOLVERS_CONSTRAINT_PROPAGATION_HPP_ #include "papilo/core/PresolveMethod.hpp" #include "papilo/core/Problem.hpp" #include "papilo/core/ProblemUpdate.hpp" #include "papilo/core/SingleRow.hpp" namespace papilo { template class ConstraintPropagation : public PresolveMethod { public: ConstraintPropagation() : PresolveMethod() { this->setName( "propagation" ); this->setTiming( PresolverTiming::kFast ); } /// todo how to communicate about postsolve information PresolveStatus execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) override; }; #ifdef PAPILO_USE_EXTERN_TEMPLATES extern template class ConstraintPropagation; extern template class ConstraintPropagation; extern template class ConstraintPropagation; #endif template PresolveStatus ConstraintPropagation::execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) { const auto& domains = problem.getVariableDomains(); const auto& activities = problem.getRowActivities(); const auto& changedactivities = problemUpdate.getChangedActivities(); const auto& consMatrix = problem.getConstraintMatrix(); const auto& lhsValues = consMatrix.getLeftHandSides(); const auto& rhsValues = consMatrix.getRightHandSides(); const auto& rflags = consMatrix.getRowFlags(); PresolveStatus result = PresolveStatus::kUnchanged; // for LP constraint propagation we might want to weaken the bounds by some // small amount above the feasibility tolerance const REAL weaken_bounds = problem.getNumIntegralCols() == 0 ? REAL(problemUpdate.getPresolveOptions().weakenlpvarbounds) * num.getFeasTol() : REAL{ 0.0 }; // 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().constraint_propagation_parallel ) { auto add_boundchange = [&]( BoundChange boundChange, int col, REAL val, int row ) { // do not accept huge values as bounds if( num.isHugeVal( val ) ) return; if( boundChange == BoundChange::kLower ) { assert( domains.flags[col].test( ColFlag::kLbInf ) || val > domains.lower_bounds[col] ); if( domains.flags[col].test( ColFlag::kIntegral, ColFlag::kImplInt ) ) val = num.feasCeil( val ); if( !domains.flags[col].test( ColFlag::kUbInf ) ) { // compute distance of new lower bound to the upper bound REAL bnddist = domains.upper_bounds[col] - val; // bound exceeded by more then feastol means infeasible if( bnddist < -num.getFeasTol() ) { result = PresolveStatus::kInfeasible; return; } // if the upper bound is reached, or reached within tolerances // and the change of feasibility is also within tolerances fix to // the upper bound if( bnddist <= 0 || ( bnddist <= num.getFeasTol() && consMatrix.getMaxFeasChange( col, bnddist ) <= num.getFeasTol() ) ) { reductions.fixCol( col, domains.upper_bounds[col], row ); result = PresolveStatus::kReduced; return; } } val -= weaken_bounds; if( domains.flags[col].test( ColFlag::kLbInf ) || val - domains.lower_bounds[col] > +1000 * num.getFeasTol() ) { if(!skip_variable_tightening) { reductions.changeColLB( col, val, row ); result = PresolveStatus::kReduced; } else if( domains.flags[col].test( ColFlag::kLbInf ) ) { // in case of skip_variable_tightening set bounds to an // infinite value if possible REAL offset = bound_tightening_offset * abs( val ); if( offset < bound_tightening_offset ) offset = bound_tightening_offset; reductions.changeColLB( col, val - offset, row ); result = PresolveStatus::kReduced; } } } else { assert( boundChange == BoundChange::kUpper ); assert( domains.flags[col].test( ColFlag::kUbInf ) || val < domains.upper_bounds[col] ); if( domains.flags[col].test( ColFlag::kIntegral, ColFlag::kImplInt ) ) val = num.feasFloor( val ); if( !domains.flags[col].test( ColFlag::kLbInf ) ) { // compute distance of new upper bound to the lower bound REAL bnddist = val - domains.lower_bounds[col]; // bound exceeded by more then feastol means infeasible if( bnddist < -num.getFeasTol() ) { result = PresolveStatus::kInfeasible; return; } // if the lower bound is reached, or reached within tolerances // and the change of feasibility is also within tolerances fix to // the lower bound if( bnddist <= 0 || ( bnddist <= num.getFeasTol() && consMatrix.getMaxFeasChange( col, bnddist ) <= num.getFeasTol() ) ) { reductions.fixCol( col, domains.lower_bounds[col], row ); result = PresolveStatus::kReduced; return; } } val += weaken_bounds; if( domains.flags[col].test( ColFlag::kUbInf ) || val - domains.upper_bounds[col] < -1000 * num.getFeasTol() ) { if(!skip_variable_tightening) { reductions.changeColUB( col, val, row ); result = PresolveStatus::kReduced; } else if( domains.flags[col].test( ColFlag::kUbInf ) ) { // in case of skip_variable_tightening set bounds to an // infinite value if possible REAL offset = bound_tightening_offset * abs( val ); if( offset < bound_tightening_offset ) offset = bound_tightening_offset; reductions.changeColUB( col, val + offset, row ); result = PresolveStatus::kReduced; } } } }; for( int row : changedactivities ) { auto rowvec = consMatrix.getRowCoefficients( row ); // in sequential mode no trivialPresolve is performed if( consMatrix.isRowRedundant( row ) ) continue; switch( rowvec.getLength() ) { case 0: if( ( !rflags[row].test( RowFlag::kLhsInf ) && num.isFeasGT( lhsValues[row], 0 ) ) || ( !rflags[row].test( RowFlag::kRhsInf ) && num.isFeasLT( rhsValues[row], 0 ) ) ) result = PresolveStatus::kInfeasible; else reductions.markRowRedundant( row ); break; case 1: // do nothing, singleton row presolver handles this bound change break; default: propagate_row( row, rowvec.getValues(), rowvec.getIndices(), rowvec.getLength(), activities[row], lhsValues[row], rhsValues[row], rflags[row], domains.lower_bounds, domains.upper_bounds, domains.flags, add_boundchange ); } if( result == PresolveStatus::kInfeasible ) break; } } #ifdef PAPILO_TBB else { Vec> stored_reductions( changedactivities.size() ); bool infeasible = false; tbb::parallel_for( tbb::blocked_range( 0, changedactivities.size() ), [&]( const tbb::blocked_range& r ) { for( int j = r.begin(); j < r.end(); ++j ) { PresolveStatus local_status = PresolveStatus::kUnchanged; auto add_boundchange = [&]( BoundChange boundChange, int col, REAL val, int row ) { // do not accept huge values as bounds if( num.isHugeVal( val ) ) return; if( boundChange == BoundChange::kLower ) { assert( domains.flags[col].test( ColFlag::kLbInf ) || val > domains.lower_bounds[col] ); if( domains.flags[col].test( ColFlag::kIntegral, ColFlag::kImplInt ) ) val = num.feasCeil( val ); if( !domains.flags[col].test( ColFlag::kUbInf ) ) { // compute distance of new lower bound to the upper // bound REAL bnddist = domains.upper_bounds[col] - val; // bound exceeded by more then feastol means infeasible if( bnddist < -num.getFeasTol() ) { local_status = PresolveStatus::kInfeasible; return; } // if the upper bound is reached, or reached within // tolerances and the change of feasibility is also // within tolerances fix to the upper bound if( bnddist <= 0 || ( bnddist <= num.getFeasTol() && consMatrix.getMaxFeasChange( col, bnddist ) <= num.getFeasTol() ) ) { stored_reductions[j].fixCol( col, domains.upper_bounds[col], row ); local_status = PresolveStatus::kReduced; return; } } val -= weaken_bounds; if( domains.flags[col].test( ColFlag::kLbInf ) || val - domains.lower_bounds[col] > +1000 * num.getFeasTol() ) { if( !skip_variable_tightening ) { stored_reductions[j].changeColLB( col, val, row ); local_status = PresolveStatus::kReduced; } // in case of skip_variable_tightening set bounds to an // infinite value if possible else if( domains.flags[col].test( ColFlag::kLbInf ) ) { // in case of skip_variable_tightening set bounds to an // infinite value if possible REAL offset = bound_tightening_offset * abs( val ); if( offset < bound_tightening_offset ) offset = bound_tightening_offset; stored_reductions[j].changeColLB( col, val - offset, row ); result = PresolveStatus::kReduced; } } } else { assert( boundChange == BoundChange::kUpper ); assert( domains.flags[col].test( ColFlag::kUbInf ) || val < domains.upper_bounds[col] ); if( domains.flags[col].test( ColFlag::kIntegral, ColFlag::kImplInt ) ) val = num.feasFloor( val ); if( !domains.flags[col].test( ColFlag::kLbInf ) ) { // compute distance of new upper bound to the lower // bound REAL bnddist = val - domains.lower_bounds[col]; // bound exceeded by more then feastol means infeasible if( bnddist < -num.getFeasTol() ) { local_status = PresolveStatus::kInfeasible; return; } // if the lower bound is reached, or reached within // tolerances and the change of feasibility is also // within tolerances fix to the lower bound if( bnddist <= 0 || ( bnddist <= num.getFeasTol() && consMatrix.getMaxFeasChange( col, bnddist ) <= num.getFeasTol() ) ) { stored_reductions[j].fixCol( col, domains.lower_bounds[col], row ); local_status = PresolveStatus::kReduced; return; } } val += weaken_bounds; if( domains.flags[col].test( ColFlag::kUbInf ) || val - domains.upper_bounds[col] < -1000 * num.getFeasTol() ) { if( !skip_variable_tightening ) { stored_reductions[j].changeColUB( col, val, row ); local_status = PresolveStatus::kReduced; } else if( domains.flags[col].test( ColFlag::kUbInf ) ) { // in case of skip_variable_tightening set bounds to an // infinite value if possible REAL offset = bound_tightening_offset * abs( val ); if( offset < bound_tightening_offset ) offset = bound_tightening_offset; stored_reductions[j].changeColUB( col, val + offset, row ); result = PresolveStatus::kReduced; } } } }; int row = changedactivities[j]; auto rowvec = consMatrix.getRowCoefficients( row ); assert( !consMatrix.isRowRedundant( row ) ); switch( rowvec.getLength() ) { case 0: if( ( !rflags[row].test( RowFlag::kLhsInf ) && num.isFeasGT( lhsValues[row], 0 ) ) || ( !rflags[row].test( RowFlag::kRhsInf ) && num.isFeasLT( rhsValues[row], 0 ) ) ) local_status = PresolveStatus::kInfeasible; else reductions.markRowRedundant( row ); break; case 1: // do nothing, singleton row presolver handles this bound // change break; default: propagate_row( row, rowvec.getValues(), rowvec.getIndices(), rowvec.getLength(), activities[row], lhsValues[row], rhsValues[row], rflags[row], domains.lower_bounds, domains.upper_bounds, domains.flags, add_boundchange ); } assert( local_status == PresolveStatus::kReduced || local_status == PresolveStatus::kInfeasible || local_status == PresolveStatus::kUnchanged ); if( local_status == PresolveStatus::kInfeasible ) infeasible = true; else if( local_status == PresolveStatus::kReduced ) { result = PresolveStatus::kReduced; } } } ); if( infeasible ) return PresolveStatus::kInfeasible; 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& reduction : reds.getReductions() ) { reductions.add_reduction( reduction.row, reduction.col, reduction.newval ); } } } } #endif return result; } } // namespace papilo #endif