/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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_PROBING_VIEW_HPP_ #define _PAPILO_CORE_PROBING_VIEW_HPP_ #include "papilo/core/Problem.hpp" #include "papilo/core/SingleRow.hpp" #include "papilo/io/Message.hpp" #include #include "papilo/misc/MultiPrecision.hpp" #include "papilo/misc/Vec.hpp" namespace papilo { template struct ProbingBoundChg { REAL bound; unsigned int col : 31; unsigned int upper : 1; ProbingBoundChg( bool upper_, int col_, REAL bound_ ) { this->upper = upper_ ? 1 : 0; this->col = static_cast( col_ ); this->bound = bound_; } }; template struct ProbingSubstitution { REAL col2scale; REAL col2const; int col1; int col2; ProbingSubstitution( int col1_, REAL col2scale_, int col2_, REAL col2const_ ) : col2scale( col2scale_ ), col2const( col2const_ ), col1( col1_ ), col2( col2_ ) { } }; template class ProbingView { public: ProbingView( const Problem& problem, const Num& num ); void setMinIntDomRed( const REAL& value ) { this->minintdomred = value; } void setMinContDomRed( const REAL& value ) { this->mincontdomred = value; } void reset(); void setProbingColumn( int col, bool value ) { // remember probing column and probed value probingCol = col; probingValue = value; // fix upper/lower bound of probed column if( value ) changeLb( col, 1.0 ); else changeUb( col, 0.0 ); } void activityChanged( ActivityChange actchange, int rowid, RowActivity& activity ); void changeLb( int col, REAL newlb ); void changeUb( int col, REAL newub ); void storeImplications(); bool analyzeImplications(); void propagateDomains(); bool isInfeasible() const { return infeasible; } int getNumSubstitutions() const { return static_cast( substitutions.size() ); } Vec>& getProbingBoundChanges() { return boundChanges; } Vec>& getProbingSubstitutions() { return substitutions; } const Vec>& getProbingBoundChanges() const { return boundChanges; } const Vec>& getProbingSubstitutions() const { return substitutions; } int64_t getAmountOfWork() const { return amountofwork; } const Vec& getProbingLowerBounds() const { return probing_lower_bounds; } const Vec& getProbingUpperBounds() const { return probing_upper_bounds; } const Vec& getProbingDomainFlags() const { return probing_domain_flags; } void clearResults() { amountofwork = 0; boundChanges.clear(); substitutions.clear(); } private: // reference to problem and numerics class const Problem& problem; const Num& num; REAL minintdomred; REAL mincontdomred; // datastructures used for probing Vec changed_lbs; Vec changed_ubs; Vec changed_activities; Vec probing_lower_bounds; Vec probing_upper_bounds; Vec probing_domain_flags; Vec> probing_activities; Vec prop_activities; Vec next_prop_activities; bool infeasible; int round; int probingCol; bool probingValue; // datastructures for storing result of probing on one value Vec> otherValueImplications; bool otherValueInfeasible; // results of probing and statistics Vec> boundChanges; Vec> substitutions; int64_t amountofwork; }; #ifdef PAPILO_USE_EXTERN_TEMPLATES extern template class ProbingView; extern template class ProbingView; extern template class ProbingView; #endif template ProbingView::ProbingView( const Problem& problem_, const Num& num_ ) : problem( problem_ ), num( num_ ), probing_lower_bounds( problem_.getLowerBounds() ), probing_upper_bounds( problem_.getUpperBounds() ), probing_domain_flags( problem_.getColFlags() ), probing_activities( problem_.getRowActivities() ) { round = -2; infeasible = false; amountofwork = 0; probingCol = -1; probingValue = false; otherValueInfeasible = false; minintdomred = num.getFeasTol() * 1000; mincontdomred = 0.3; } template void ProbingView::reset() { const Vec& rowsize = problem.getConstraintMatrix().getRowSizes(); const auto& orig_lbs = problem.getLowerBounds(); for( int i : changed_lbs ) { if( i < 0 ) { int c = -i - 1; assert( !probing_domain_flags[c].test( ColFlag::kLbUseless ) && problem.getColFlags()[c].test( ColFlag::kLbUseless ) ); probing_domain_flags[c].set( ColFlag::kLbUseless ); #ifndef NDEBUG probing_lower_bounds[c] = orig_lbs[c]; #endif } else probing_lower_bounds[i] = orig_lbs[i]; } changed_lbs.clear(); const auto& orig_ubs = problem.getUpperBounds(); for( int i : changed_ubs ) { if( i < 0 ) { int c = -i - 1; assert( !probing_domain_flags[c].test( ColFlag::kUbUseless ) && problem.getColFlags()[c].test( ColFlag::kUbUseless ) ); probing_domain_flags[c].set( ColFlag::kUbUseless ); #ifndef NDEBUG probing_upper_bounds[c] = orig_ubs[c]; #endif } else probing_upper_bounds[i] = orig_ubs[i]; } changed_ubs.clear(); const auto& orig_activities = problem.getRowActivities(); for( int i : changed_activities ) { amountofwork += rowsize[i]; probing_activities[i] = orig_activities[i]; } changed_activities.clear(); // reset should result in original domains and activities assert( std::equal( orig_lbs.begin(), orig_lbs.end(), probing_lower_bounds.begin() ) ); assert( std::equal( orig_ubs.begin(), orig_ubs.end(), probing_upper_bounds.begin() ) ); assert( std::equal( orig_activities.begin(), orig_activities.end(), probing_activities.begin(), []( const RowActivity& a, const RowActivity& b ) { return a.ninfmax == b.ninfmax && a.ninfmin == b.ninfmin && a.min == b.min && a.max == b.max && a.lastchange == b.lastchange; } ) ); round = -2; prop_activities.clear(); next_prop_activities.clear(); infeasible = false; probingCol = -1; } template void ProbingView::activityChanged( ActivityChange actchange, int rowid, RowActivity& activity ) { const auto& consMatrix = problem.getConstraintMatrix(); const auto& lhs = consMatrix.getLeftHandSides(); const auto& rhs = consMatrix.getRightHandSides(); const auto& rflags = consMatrix.getRowFlags(); // mark the lastchange fields with round values starting from -2 // and counting backward By doing this we avoid that we need to // alter the lastchange field after copying the original activities // since they are always larger or equal to -1 if( activity.lastchange > -2 ) changed_activities.push_back( rowid ); // activity was changed for the first time if( activity.lastchange != round ) next_prop_activities.push_back( rowid ); activity.lastchange = round; // check if the updated activity is reliable or if it is zero relative to // the initial activity const RowActivity& origactivity = problem.getRowActivities()[rowid]; bool unreliable; if( actchange == ActivityChange::kMin ) unreliable = ( activity.ninfmin <= 1 && activity.min != 0 && origactivity.min != 0 && num.isZero( activity.min / origactivity.min ) ); else unreliable = ( activity.ninfmax <= 1 && activity.max != 0 && origactivity.max != 0 && num.isZero( activity.max / origactivity.max ) ); if( unreliable ) { auto rowvec = problem.getConstraintMatrix().getRowCoefficients( rowid ); activity = compute_row_activity( rowvec.getValues(), rowvec.getIndices(), rowvec.getLength(), probing_lower_bounds, probing_upper_bounds, probing_domain_flags, round ); } // check for infeasibility if( actchange == ActivityChange::kMin && activity.ninfmin == 0 && !rflags[rowid].test( RowFlag::kRhsInf ) && num.isFeasLT( rhs[rowid], activity.min ) && num.isSafeLT( rhs[rowid], activity.min ) ) { Message::debug( this, "[{}:{}] probing on col {} with val {} is infeasible min " "activity is {:.15}, right hand side is {:.15}, and " "original max activity was {:.15}\n", __FILE__, __LINE__, probingCol, probingValue, double( activity.min ), double( rhs[rowid] ), double( problem.getRowActivities()[rowid].min ) ); infeasible = true; } if( actchange == ActivityChange::kMax && activity.ninfmax == 0 && !rflags[rowid].test( RowFlag::kLhsInf ) && num.isFeasGT( lhs[rowid], activity.max ) && num.isSafeGT( lhs[rowid], activity.max ) ) { Message::debug( this, "[{}:{}] probing on col {} with val {} is infeasible max " "activity is {:.15}, left hand side is {:.15}, and " "original max activity was {:.15}\n", __FILE__, __LINE__, probingCol, probingValue, double( activity.max ), double( lhs[rowid] ), double( problem.getRowActivities()[rowid].max ) ); infeasible = true; } } template void ProbingView::changeLb( int col, REAL newlb ) { const auto& consMatrix = problem.getConstraintMatrix(); auto colvec = consMatrix.getColumnCoefficients( col ); const auto& orig_lbs = problem.getLowerBounds(); // bound must be tighter than current domains bool lbinf = probing_domain_flags[col].test( ColFlag::kLbUseless ); assert( lbinf || probing_lower_bounds[col] != newlb ); Message::debug( this, "changing probing lower bound of col {} to {}\n", col, double( newlb ) ); if( lbinf ) { // bound was not altered yet, store the negative (index + 1) to // indicate that the infinity flag was altered probing_domain_flags[col].unset( ColFlag::kLbUseless ); changed_lbs.push_back( -col - 1 ); } else if( probing_lower_bounds[col] == orig_lbs[col] && !problem.getColFlags()[col].test( ColFlag::kLbUseless ) ) // if bound was not altered yet remember it in the index vector changed_lbs.push_back( col ); // change the bound in the domain vector REAL oldlb = probing_lower_bounds[col]; probing_lower_bounds[col] = newlb; // update the probing activities by using the column view update_activities_after_boundchange( colvec.getValues(), colvec.getIndices(), colvec.getLength(), BoundChange::kLower, oldlb, newlb, lbinf, probing_activities, [this]( ActivityChange actChange, int rowid, RowActivity& activity ) { activityChanged( actChange, rowid, activity ); }, true ); } template void ProbingView::changeUb( int col, REAL newub ) { const auto& consMatrix = problem.getConstraintMatrix(); auto colvec = consMatrix.getColumnCoefficients( col ); const auto& orig_ubs = problem.getUpperBounds(); // bound must be tighter than current domains bool ubinf = probing_domain_flags[col].test( ColFlag::kUbUseless ); assert( ubinf || probing_upper_bounds[col] != newub ); Message::debug( this, "changing probing upper bound of col {} to {}\n", col, double( newub ) ); if( ubinf ) { // bound was not altered yet, store the negative (index + 1) to // indicate that the infinity flag was altered probing_domain_flags[col].unset( ColFlag::kUbUseless ); changed_ubs.push_back( -col - 1 ); } else if( probing_upper_bounds[col] == orig_ubs[col] && !problem.getColFlags()[col].test( ColFlag::kUbUseless ) ) // if bound was not altered yet remember it in the index vector changed_ubs.push_back( col ); // change the bound in the domain vector REAL oldub = probing_upper_bounds[col]; probing_upper_bounds[col] = newub; // update the probing activities by using the column view update_activities_after_boundchange( colvec.getValues(), colvec.getIndices(), colvec.getLength(), BoundChange::kUpper, oldub, newub, ubinf, probing_activities, [this]( ActivityChange actChange, int rowid, RowActivity& activity ) { activityChanged( actChange, rowid, activity ); }, true ); } template void ProbingView::storeImplications() { otherValueInfeasible = isInfeasible(); if( otherValueInfeasible ) return; otherValueImplications.clear(); otherValueImplications.reserve( changed_lbs.size() + changed_ubs.size() - 1 ); for( int c : changed_lbs ) { int col = c < 0 ? -c - 1 : c; if( col == probingCol ) continue; otherValueImplications.emplace_back( ProbingBoundChg( false, col, probing_lower_bounds[col] ) ); } for( int c : changed_ubs ) { int col = c < 0 ? -c - 1 : c; if( col == probingCol ) continue; otherValueImplications.emplace_back( ProbingBoundChg( true, col, probing_upper_bounds[col] ) ); } } template bool ProbingView::analyzeImplications() { const auto& orig_ubs = problem.getUpperBounds(); const auto& orig_lbs = problem.getLowerBounds(); const Vec& orig_domain_flags = problem.getColFlags(); // check infeasibility if( otherValueInfeasible ) { // both probing values are infeasible if( infeasible ) return true; // only the other probing value is infeasible, so store all changed // bounds as bound changes note that this includes the fixing of the // probing column to the probed value boundChanges.reserve( boundChanges.size() + changed_lbs.size() + changed_ubs.size() ); for( int c : changed_lbs ) { int col = c < 0 ? -c - 1 : c; assert( c >= 0 || ( !probing_domain_flags[col].test( ColFlag::kLbUseless ) && orig_domain_flags[col].test( ColFlag::kLbUseless ) ) ); assert( c < 0 || ( !probing_domain_flags[col].test( ColFlag::kLbUseless ) && !orig_domain_flags[col].test( ColFlag::kLbUseless ) ) ); assert( c < 0 || probing_lower_bounds[col] > orig_lbs[col] ); boundChanges.emplace_back( ProbingBoundChg( false, col, probing_lower_bounds[col] ) ); } for( int c : changed_ubs ) { int col = c < 0 ? -c - 1 : c; assert( c >= 0 || ( !probing_domain_flags[col].test( ColFlag::kUbUseless ) && orig_domain_flags[col].test( ColFlag::kUbUseless ) ) ); assert( c < 0 || ( !probing_domain_flags[col].test( ColFlag::kUbUseless ) && !orig_domain_flags[col].test( ColFlag::kUbUseless ) ) ); assert( c < 0 || probing_upper_bounds[col] < orig_ubs[col] ); boundChanges.emplace_back( ProbingBoundChg( true, col, probing_upper_bounds[col] ) ); } return false; } boundChanges.reserve( boundChanges.size() + otherValueImplications.size() + 1 ); if( infeasible ) { if( probingValue ) { // probing to 1 is infeasible, fix probing column to 0 boundChanges.emplace_back( ProbingBoundChg( true, probingCol, 0.0 ) ); } else { // probing to 0 is infeasible, fix probing column to 1 boundChanges.emplace_back( ProbingBoundChg( false, probingCol, 1.0 ) ); } } assert( !otherValueInfeasible ); for( const ProbingBoundChg& boundChg : otherValueImplications ) { bool impliedFixing = ( boundChg.upper && !orig_domain_flags[boundChg.col].test( ColFlag::kLbUseless ) && orig_lbs[boundChg.col] == boundChg.bound ) || ( !boundChg.upper && !orig_domain_flags[boundChg.col].test( ColFlag::kUbUseless ) && orig_ubs[boundChg.col] == boundChg.bound ); // only this probing branch is infeasible, so add all implications of // the other value as bound change if( infeasible ) { boundChanges.emplace_back( boundChg ); continue; } bool fixed = ( !probing_domain_flags[boundChg.col].test( ColFlag::kUnbounded ) && probing_lower_bounds[boundChg.col] == probing_upper_bounds[boundChg.col] ); if( impliedFixing && fixed && !num.isFeasEq( probing_lower_bounds[boundChg.col], boundChg.bound ) ) { // column is fixed to different values in both probing branches REAL zerofixval; REAL onefixval; // Determine the fixed values for probing with value 0 and 1 if( probingValue ) { zerofixval = boundChg.bound; onefixval = probing_lower_bounds[boundChg.col]; } else { zerofixval = probing_lower_bounds[boundChg.col]; onefixval = boundChg.bound; } int col1 = boundChg.col; int col2 = probingCol; REAL scale = onefixval - zerofixval; // in case both columns are binary, we keep the one whith the // smaller index if( col1 < col2 && abs( scale ) == 1 && ( zerofixval == 1 || zerofixval == 0 ) && probing_domain_flags[col1].test( ColFlag::kIntegral ) ) std::swap( col1, col2 ); // add the corresponding substitution. substitutions.emplace_back( ProbingSubstitution( col1, scale, col2, zerofixval ) ); } else if( boundChg.upper && !probing_domain_flags[boundChg.col].test( ColFlag::kUbInf ) && ( orig_domain_flags[boundChg.col].test( ColFlag::kUbInf ) || orig_ubs[boundChg.col] > probing_upper_bounds[boundChg.col] ) ) { assert( orig_domain_flags[boundChg.col].test( ColFlag::kUbInf ) || orig_ubs[boundChg.col] > boundChg.bound ); // upper bound is tightened in both probing branches boundChanges.emplace_back( ProbingBoundChg( true, boundChg.col, num.max( boundChg.bound, probing_upper_bounds[boundChg.col] ) ) ); } else if( !boundChg.upper && !probing_domain_flags[boundChg.col].test( ColFlag::kLbInf ) && ( orig_domain_flags[boundChg.col].test( ColFlag::kLbInf ) || orig_lbs[boundChg.col] < probing_lower_bounds[boundChg.col] ) ) { assert( orig_domain_flags[boundChg.col].test( ColFlag::kLbInf ) || orig_lbs[boundChg.col] < boundChg.bound ); // lower bound is tightened in both probing branches boundChanges.emplace_back( ProbingBoundChg( false, boundChg.col, num.min( boundChg.bound, probing_lower_bounds[boundChg.col] ) ) ); } } return false; } template void ProbingView::propagateDomains() { const auto& consMatrix = problem.getConstraintMatrix(); const auto& lhs = consMatrix.getLeftHandSides(); const auto& rhs = consMatrix.getRightHandSides(); const auto& rflags = consMatrix.getRowFlags(); using std::swap; swap( prop_activities, next_prop_activities ); next_prop_activities.clear(); while( !prop_activities.empty() ) { int curr_round = round--; Message::debug( this, "starting probing propagation round {} on {} rows\n", -curr_round - 2, prop_activities.size() ); for( int candrow : prop_activities ) { bool propagate = false; if( !rflags[candrow].test( RowFlag::kRhsInf ) && probing_activities[candrow].ninfmin <= 1 ) propagate = true; if( !rflags[candrow].test( RowFlag::kLhsInf ) && probing_activities[candrow].ninfmax <= 1 ) propagate = true; if( !propagate ) continue; auto rowvec = consMatrix.getRowCoefficients( candrow ); propagate_row(candrow, rowvec.getValues(), rowvec.getIndices(), rowvec.getLength(), probing_activities[candrow], lhs[candrow], rhs[candrow], rflags[candrow], probing_lower_bounds, probing_upper_bounds, probing_domain_flags, [this]( BoundChange bndChg, int colid, REAL newbound , int row ) { if( num.isHugeVal( newbound ) ) return; bool isint = probing_domain_flags[colid].test( ColFlag::kIntegral, ColFlag::kImplInt ); if( bndChg == BoundChange::kLower ) { if( isint ) newbound = num.feasCeil( newbound ); if( !probing_domain_flags[colid].test( ColFlag::kUbInf ) && newbound > probing_upper_bounds[colid] ) { if( num.isFeasGT( newbound, probing_upper_bounds[colid] ) ) { Message::debug( this, "[{}:{}] probing on col {} with " "val {} is infeasible\n", __FILE__, __LINE__, probingCol, probingValue ); infeasible = true; return; } newbound = probing_upper_bounds[colid]; } REAL delta = newbound - probing_lower_bounds[colid]; bool finiteDomain = !probing_domain_flags[colid].test( ColFlag::kUbInf ); REAL mindomred = isint ? minintdomred : mincontdomred; if( probing_domain_flags[colid].test( ColFlag::kLbUseless ) || ( finiteDomain && delta > 0 && ( delta / ( probing_upper_bounds[colid] - probing_lower_bounds[colid] ) >= mindomred ) ) ) changeLb( colid, newbound ); } else { assert( bndChg == BoundChange::kUpper ); if( isint ) newbound = num.feasFloor( newbound ); if( !probing_domain_flags[colid].test( ColFlag::kLbInf ) && newbound < probing_lower_bounds[colid] ) { if( num.isFeasLT( newbound, probing_lower_bounds[colid] ) ) { Message::debug( this, "[{}:{}] probing on col {} with " "val {} is infeasible\n", __FILE__, __LINE__, probingCol, probingValue ); infeasible = true; return; } newbound = probing_lower_bounds[colid]; } REAL delta = probing_upper_bounds[colid] - newbound; bool finiteDomain = !probing_domain_flags[colid].test( ColFlag::kLbInf ); REAL mindomred = isint ? minintdomred : mincontdomred; if( probing_domain_flags[colid].test( ColFlag::kUbUseless ) || ( finiteDomain && delta > 0 && ( delta / ( probing_upper_bounds[colid] - probing_lower_bounds[colid] ) >= mindomred ) ) ) changeUb( colid, newbound ); } } ); if( infeasible ) return; } swap( prop_activities, next_prop_activities ); next_prop_activities.clear(); } } } // namespace papilo #endif