/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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_FREEVAR_HPP_ #define _PAPILO_PRESOLVERS_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" #include "papilo/external/pdqsort/pdqsort.h" #include namespace papilo { template class Substitution : public PresolveMethod { Vec ntried; public: Substitution() : PresolveMethod() { this->setName( "substitution" ); this->setTiming( PresolverTiming::kExhaustive ); } void compress( const Vec& rowmap, const Vec& colmap ) override { assert( rowmap.size() == ntried.size() ); compress_vector( rowmap, ntried ); Message::debug( this, "compress was called, compressed ntried vector from " "size {} to size {}\n", rowmap.size(), ntried.size() ); } bool initialize( const Problem& problem, const PresolveOptions& presolveOptions ) override { ntried.clear(); ntried.resize( problem.getNRows(), 0 ); Message::debug( this, "initialized ntried vector to size {}\n", ntried.size() ); return true; } PresolveStatus execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) override; bool is_divisible( const Num& num, int length, const REAL* row_values, REAL min_abs_int_value ) const; }; #ifdef PAPILO_USE_EXTERN_TEMPLATES extern template class Substitution; extern template class Substitution; extern template class Substitution; #endif template PresolveStatus Substitution::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& activities = problem.getRowActivities(); 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& ncols = constMatrix.getNCols(); const auto& rowperm = problemUpdate.getRandomRowPerm(); PresolveStatus result = PresolveStatus::kUnchanged; using Equality = std::tuple, int>; Vec equalities; equalities.reserve( nrows ); for( int i = 0; i < nrows; ++i ) { if( rflags[i].test( RowFlag::kRedundant ) || !rflags[i].test( RowFlag::kEquation ) || constMatrix.getRowSizes()[i] <= 1 ) continue; assert( !rflags[i].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) && lhs_values[i] == rhs_values[i] ); equalities.emplace_back( constMatrix.getRowCoefficients( i ), i ); } pdqsort( equalities.begin(), equalities.end(), [this, &rowperm]( const Equality& a, const Equality& b ) { return std::make_tuple( ntried[std::get<1>( a )], std::get<0>( a ).getLength(), rowperm[std::get<1>( a )] ) < std::make_tuple( ntried[std::get<1>( b )], std::get<0>( b ).getLength(), rowperm[std::get<1>( b )] ); } ); boost::dynamic_bitset<> colUnusable( ncols ); boost::dynamic_bitset<> touchedRows( nrows ); Vec column_candidates; column_candidates.reserve( ncols ); for( auto equality : equalities ) { int row = std::get<1>( equality ); const int length = std::get<0>( equality ).getLength(); const int* rowindices = std::get<0>( equality ).getIndices(); const REAL* rowvalues = std::get<0>( equality ).getValues(); REAL maxabsvalue = std::get<0>( equality ).getMaxAbsValue(); REAL minabsintvalue = maxabsvalue; bool containsContinuous = false; bool containsNonBinInt = false; for( int i = 0; i != length; ++i ) { if( !cflags[rowindices[i]].test( ColFlag::kIntegral ) ) { containsContinuous = true; break; } if( !problemUpdate.getPresolveOptions().substitutebinarieswithints && !domains.isBinary( rowindices[i] ) ) containsNonBinInt = true; if( abs( rowvalues[i] ) < minabsintvalue ) minabsintvalue = abs( rowvalues[i] ); } // If the row contains no continuous variables it might be suitable for // substituting an integer variable. We can only substitute those // integer variables whose absolute coefficient value in the row is // equal to the smallest absolute coefficient in the row. Additional we // need to ensure that all coefficients are integral if divided by the // smallest absolute coefficient. if( !containsContinuous && !is_divisible( num, length, rowvalues, minabsintvalue ) ) continue; column_candidates.clear(); for( int i = 0; i < length; ++i ) { int col = rowindices[i]; // if the column has been already used for a substitution or known // to be not implied free we can skip it if( colUnusable[col] ) continue; // check if integrality condition is guaranteed for this column if( cflags[col].test( ColFlag::kIntegral ) ) { // if the row contains continuous variables we cannot use it for // substituting an integral column if( containsContinuous ) continue; // TODO: why not saving the index of the value also? this would // reduce the if statement to just comparing indices // the divisibility of the coefficients has been checked // above and we know that we can use it only if its coefficient has // the smallest magnitude of the coefficients within the row if( !num.isEq( abs( rowvalues[i] ), minabsintvalue ) ) continue; // do not substitute a binary variable with integer variables if the // option is set if( !problemUpdate.getPresolveOptions() .substitutebinarieswithints && containsNonBinInt && domains.isBinary( rowindices[i] ) ) continue; } column_candidates.push_back( i ); } pdqsort( column_candidates.begin(), column_candidates.end(), [&]( int i1, int i2 ) { int col1 = rowindices[i1]; int col2 = rowindices[i2]; return problemUpdate.isColBetterForSubstitution( col1, col2 ); } ); for( int i : column_candidates ) { int col = rowindices[i]; auto colvec = constMatrix.getColumnCoefficients( col ); // check the numerics conditions if( ( abs( rowvalues[i] ) < REAL(problemUpdate.getPresolveOptions().markowitz_tolerance) * maxabsvalue && abs( rowvalues[i] ) < REAL(problemUpdate.getPresolveOptions().markowitz_tolerance) * colvec.getMaxAbsValue() ) ) continue; int lbrowlock = -1; int ubrowlock = -1; // mark the column to not be used in later substitutions, because // it's either not free or used for this substitution colUnusable[col] = true; bool upperboundImplied = row_implies_UB( num, lhs_values[row], rhs_values[row], rflags[row], activities[row], rowvalues[i], lower_bounds[col], upper_bounds[col], cflags[col] ); bool lowerboundImplied = row_implies_LB( num, lhs_values[row], rhs_values[row], rflags[row], activities[row], rowvalues[i], lower_bounds[col], upper_bounds[col], cflags[col] ); const REAL* colvalues = colvec.getValues(); const int* colindices = colvec.getIndices(); const int collength = colvec.getLength(); auto checkIfImpliedFree = [&]( bool checkTouchedRows ) { int j = 0; while( ( !upperboundImplied || !lowerboundImplied ) && j != collength ) { int colrow = colindices[j]; REAL colval = colvalues[j]; ++j; if( colrow == row ) continue; bool isTouched = touchedRows.test( colrow ); if( ( !checkTouchedRows && isTouched ) || ( checkTouchedRows && !isTouched ) ) continue; if( !upperboundImplied ) { upperboundImplied = row_implies_UB( num, lhs_values[colrow], rhs_values[colrow], rflags[colrow], activities[colrow], colval, lower_bounds[col], upper_bounds[col], cflags[col] ); if( upperboundImplied && colrow != row && colrow != lbrowlock ) ubrowlock = colrow; } if( !lowerboundImplied ) { lowerboundImplied = row_implies_LB( num, lhs_values[colrow], rhs_values[colrow], rflags[colrow], activities[colrow], colval, lower_bounds[col], upper_bounds[col], cflags[col] ); if( lowerboundImplied && colrow != row && colrow != ubrowlock ) lbrowlock = colrow; } } }; checkIfImpliedFree( false ); checkIfImpliedFree( true ); if( upperboundImplied && lowerboundImplied ) { // reductions result = PresolveStatus::kReduced; ++ntried[row]; TransactionGuard guard{ reductions }; reductions.lockRow( row ); if( lbrowlock != -1 ) reductions.lockRow( lbrowlock ); if( ubrowlock != -1 ) reductions.lockRow( ubrowlock ); reductions.lockColBounds( col ); reductions.aggregateFreeCol( col, row ); const int* indices = colvec.getIndices(); const int len = colvec.getLength(); for( int j = 0; j != len; ++j ) touchedRows.set( indices[j] ); break; } } } return result; } template bool Substitution::is_divisible( const Num& num, const int length, const REAL* row_values, REAL min_abs_int_value ) const { for( int i = 0; i != length; ++i ) { if( !num.isIntegral( row_values[i] / min_abs_int_value ) ) { return false; } } return true; } } // namespace papilo #endif