/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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_PARALLEL_ROW_DETECTION_HPP_ #define _PAPILO_PARALLEL_ROW_DETECTION_HPP_ #include "papilo/core/PresolveMethod.hpp" #include "papilo/core/Problem.hpp" #include "papilo/core/ProblemUpdate.hpp" #include "papilo/misc/Hash.hpp" #ifdef PAPILO_TBB #include "papilo/misc/tbb.hpp" #endif #include "papilo/external/pdqsort/pdqsort.h" namespace papilo { template // Identical row reduction needs to be done before class ParallelRowDetection : public PresolveMethod { struct SupportHashCompare { SupportHashCompare() = default; static size_t hash( const std::pair& row ) { Hasher hasher( row.first ); const int* support = row.second; for( int i = 0; i != row.first; ++i ) { hasher.addValue( support[i] ); } return hasher.getHash(); } static bool equal( const std::pair& row1, const std::pair& row2 ) { int length = row1.first; if( length != row2.first ) return false; return memcmp( static_cast( row1.second ), static_cast( row2.second ), length * sizeof( int ) ) == 0; } }; struct SupportHash { std::size_t operator()( const std::pair& row ) const { return SupportHashCompare::hash( row ); } }; struct SupportEqual { bool operator()( const std::pair& row1, const std::pair& row2 ) const { return SupportHashCompare::equal( row1, row2 ); } }; void findParallelRows( const Num& num, const int* bucket, int bucketsize, const ConstraintMatrix& constMatrix, Vec& parallel_rows ); void computeRowHashes( const ConstraintMatrix& constMatrix, unsigned int* rowhashes ); void computeSupportId( const ConstraintMatrix& constMatrix, unsigned int* supporthashes ); int determineBucketSize( int nRows, std::unique_ptr& supportid, std::unique_ptr& coefhash, std::unique_ptr& row, int i ); public: ParallelRowDetection() : PresolveMethod() { this->setName( "parallelrows" ); this->setTiming( PresolverTiming::kMedium ); } 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 ParallelRowDetection; extern template class ParallelRowDetection; extern template class ParallelRowDetection; #endif template void ParallelRowDetection::findParallelRows( const Num& num, const int* bucket, int bucketsize, const ConstraintMatrix& constMatrix, Vec& parallel_rows ) { // TODO if bucketsize too large do gurobi trick auto row1 = constMatrix.getRowCoefficients( bucket[0] ); const int length = row1.getLength(); const REAL* coefs1 = row1.getValues(); if( length < 2 ) return; parallel_rows.push_back( bucket[0] ); for( int j = 1; j < bucketsize; ++j ) { auto row2 = constMatrix.getRowCoefficients( bucket[j] ); // support should already be checked assert( length == row2.getLength() ); assert( std::memcmp( static_cast( row1.getIndices() ), static_cast( row2.getIndices() ), length * sizeof( int ) ) == 0 ); const REAL* coefs2 = row2.getValues(); bool parallel = true; if( num.isGE( abs( coefs1[0] ), abs( coefs2[0] ) ) ) { REAL scale2 = coefs1[0] / coefs2[0]; for( int k = 1; k < length; ++k ) { if( !num.isEq( coefs1[k], scale2 * coefs2[k] ) ) { parallel = false; break; } } if( parallel ) parallel_rows.push_back( bucket[j] ); } else { REAL scale1 = coefs2[0] / coefs1[0]; for( int k = 1; k < length; ++k ) { if( !num.isEq( scale1 * coefs1[k], coefs2[k] ) ) { parallel = false; break; } } if( parallel ) parallel_rows.push_back( bucket[j] ); } } if( parallel_rows.size() == 1 ) parallel_rows.clear(); } template void ParallelRowDetection::computeRowHashes( const ConstraintMatrix& constMatrix, unsigned int* rowhashes ) { #ifdef PAPILO_TBB tbb::parallel_for( tbb::blocked_range( 0, constMatrix.getNRows() ), [&]( const tbb::blocked_range& r ) { for( int i = r.begin(); i != r.end(); ++i ) #else for( int i = 0; i != constMatrix.getNRows(); ++i ) #endif { // compute hash-value for coefficients auto rowcoefs = constMatrix.getRowCoefficients( i ); const REAL* rowvals = rowcoefs.getValues(); const int len = rowcoefs.getLength(); Hasher hasher( len ); // only makes sense for non-singleton rows // (should not occur after redundant rows are // already deleted) if( len > 1 ) { // compute scale such that the first coefficient is // positive 1/golden ratio. The choice of // the constant is arbitrary and is used to make cases // where two coefficients that are equal // within epsilon get different values are // more unlikely by choosing some irrational number REAL scale = REAL( 2.0 / ( 1.0 + sqrt( 5.0 ) ) ) / rowvals[0]; // add scaled coefficients of other row // entries to compute the hash for( int j = 1; j != len; ++j ) { hasher.addValue( Num::hashCode( rowvals[j] * scale ) ); } } rowhashes[i] = hasher.getHash(); } #ifdef PAPILO_TBB } ); #endif } template void ParallelRowDetection::computeSupportId( const ConstraintMatrix& constMatrix, unsigned int* supporthashes ) { using SupportMap = HashMap, int, SupportHash, SupportEqual>; SupportMap supportMap( static_cast( constMatrix.getNRows() * 1.1 ) ); for( int i = 0; i < constMatrix.getNRows(); ++i ) { auto row = constMatrix.getRowCoefficients( i ); int length = row.getLength(); const int* support = row.getIndices(); auto insResult = supportMap.emplace( std::make_pair( length, support ), i ); if( insResult.second ) supporthashes[i] = i; else // support already exists, use the previous support id supporthashes[i] = insResult.first->second; } } template PresolveStatus ParallelRowDetection::execute( const Problem& problem, const ProblemUpdate& problemUpdate, const Num& num, Reductions& reductions, const Timer& timer ) { const auto& constMatrix = problem.getConstraintMatrix(); const auto& lhs_values = constMatrix.getLeftHandSides(); const auto& rhs_values = constMatrix.getRightHandSides(); const auto& rflags = constMatrix.getRowFlags(); const int nRows = constMatrix.getNRows(); const Vec& rowperm = problemUpdate.getRandomRowPerm(); PresolveStatus result = PresolveStatus::kUnchanged; // get called less and less over time regardless of success since the // presolver can be expensive otherwise this->skipRounds( this->getNCalls() ); // lambda mark all reductions except one redundant and update rhs/lhs auto handleRows = [&reductions, &result, &lhs_values, &rhs_values, &rflags, &num, &constMatrix]( Vec parallel_rows ) { using std::swap; assert( parallel_rows.size() >= 2 ); int remaining_row = parallel_rows[0]; bool is_remaining_row_equality = rflags[remaining_row].test( RowFlag::kEquation ); REAL coefficient = constMatrix.getRowCoefficients( remaining_row ).getValues()[0]; bool rhs_infinity = rflags[remaining_row].test( RowFlag::kRhsInf ); bool lhs_infinity = rflags[remaining_row].test( RowFlag::kLhsInf ); REAL rhs_value = rhs_values[remaining_row]; int row_with_best_rhs_value = remaining_row; REAL lhs_value = lhs_values[remaining_row]; int row_with_best_lhs_value = remaining_row; //iterates over parallel_rows, stores the remaining row and updates rhs/lhs for( int i = 1; i < (int) parallel_rows.size(); i++ ) { int parallel_row = parallel_rows[i]; const REAL coefs2 = constMatrix.getRowCoefficients( parallel_row ).getValues()[0]; REAL ratio = coefficient / coefs2; REAL scaled_rhs = rhs_values[parallel_row] * ratio; REAL scaled_lhs = lhs_values[parallel_row] * ratio; if( ratio < REAL{ 0.0 } ) swap( scaled_lhs, scaled_rhs ); // CASE 1: 2 equalities if( rflags[parallel_row].test( RowFlag::kEquation ) && is_remaining_row_equality ) { if( !num.isFeasEq( rhs_value, scaled_rhs ) ) { result = PresolveStatus::kInfeasible; break; } if( !num.isGE( abs( coefficient ), abs( coefs2 ) ) ) { remaining_row = parallel_row; coefficient = coefs2; rhs_value = rhs_values[remaining_row]; lhs_value = lhs_values[remaining_row]; row_with_best_rhs_value = parallel_row; row_with_best_lhs_value = parallel_row; } } // CASE 2: new equation is equality else if( rflags[parallel_row].test( RowFlag::kEquation ) ) { if( ( ! rhs_infinity && num.isLT( rhs_value, scaled_rhs ) ) || ( ! lhs_infinity && num.isGT( lhs_value, scaled_lhs ) ) ) { result = PresolveStatus::kInfeasible; break; } remaining_row = parallel_row; is_remaining_row_equality = true; rhs_infinity = false; lhs_infinity = false; coefficient = coefs2; rhs_value = rhs_values[remaining_row]; lhs_value = lhs_values[remaining_row]; row_with_best_rhs_value = parallel_row; row_with_best_lhs_value = parallel_row; } // CASE 3: stored equation is equality else if( is_remaining_row_equality ) { bool scaled_lhs_inf = rflags[parallel_row].test( RowFlag::kLhsInf ); bool scaled_rhs_inf = rflags[parallel_row].test( RowFlag::kRhsInf ); if( ratio < REAL{ 0.0 } ) swap( scaled_lhs_inf, scaled_rhs_inf ); if( ( ! scaled_rhs_inf && num.isLT( scaled_rhs, rhs_value ) ) || ( ! scaled_lhs_inf && num.isGT( scaled_lhs, lhs_value ) ) ) { result = PresolveStatus::kInfeasible; break; } } // CASE 4: two inequalities else { bool scaled_lhs_inf = rflags[parallel_row].test( RowFlag::kLhsInf ); bool scaled_rhs_inf = rflags[parallel_row].test( RowFlag::kRhsInf ); if( ratio < REAL{ 0.0 } ) swap( scaled_lhs_inf, scaled_rhs_inf ); if( ( !rflags[remaining_row].test( RowFlag::kRhsInf ) && !scaled_lhs_inf && num.isLT( rhs_value, scaled_lhs ) ) || ( !rflags[remaining_row].test( RowFlag::kLhsInf ) && !scaled_rhs_inf && num.isLT( scaled_rhs, lhs_value ) ) ) { result = PresolveStatus::kInfeasible; break; } if( ! num.isGE( abs( coefficient ), abs( coefs2 ) ) ) { REAL new_ratio = coefs2 / coefficient; REAL new_adjusted_rhs = rhs_value * new_ratio; REAL new_adjusted_lhs = lhs_value * new_ratio; if( new_ratio < REAL{ 0.0 } ) { swap( new_adjusted_lhs, new_adjusted_rhs ); swap( lhs_infinity, rhs_infinity ); } remaining_row = parallel_row; coefficient = coefs2; if( !rhs_infinity && ( scaled_rhs_inf || num.isLT( new_adjusted_rhs, rhs_values[parallel_row] ) ) ) { rhs_value = new_adjusted_rhs; rhs_infinity = false; } else { rhs_value = rhs_values[parallel_row]; rhs_infinity = rflags[parallel_row].test( RowFlag::kRhsInf ); row_with_best_rhs_value = parallel_row; } if( !lhs_infinity && ( scaled_lhs_inf || num.isGT( new_adjusted_lhs, lhs_values[parallel_row] ) ) ) { lhs_value = new_adjusted_lhs; lhs_infinity = false; } else { lhs_value = lhs_values[parallel_row]; lhs_infinity = rflags[parallel_row].test( RowFlag::kLhsInf ); row_with_best_lhs_value = parallel_row; } } else { if( !scaled_rhs_inf && ( rhs_infinity || num.isLT( scaled_rhs, rhs_value ) ) ) { rhs_value = scaled_rhs; rhs_infinity = false; row_with_best_rhs_value = parallel_row; } if( !scaled_lhs_inf && ( lhs_infinity || num.isGT( scaled_lhs, lhs_value ) ) ) { lhs_value = scaled_lhs; lhs_infinity = false; row_with_best_lhs_value = parallel_row; } } } } TransactionGuard guard{ reductions }; reductions.lockRow( remaining_row ); for( int parallel_row : parallel_rows ) { if( parallel_row != remaining_row ) reductions.lockRow( parallel_row ); } if( lhs_infinity != rflags[remaining_row].test( RowFlag::kLhsInf ) || lhs_value != lhs_values[remaining_row] ) { reductions.bound_change_caused_by_row( remaining_row, row_with_best_lhs_value ); reductions.change_row_lhs_parallel( remaining_row, lhs_value ); } if( rhs_infinity != rflags[remaining_row].test( RowFlag::kRhsInf ) || rhs_value != rhs_values[remaining_row] ) { reductions.bound_change_caused_by_row( remaining_row, row_with_best_rhs_value ); reductions.change_row_rhs_parallel( remaining_row, rhs_value ); } for( int parallel_row : parallel_rows ) { if( parallel_row != remaining_row ) reductions.markRowRedundant( parallel_row ); } }; assert( nRows > 0 ); std::unique_ptr supportid{ new unsigned int[nRows] }; std::unique_ptr coefhash{ new unsigned int[nRows] }; std::unique_ptr row{ new int[nRows] }; #ifdef PAPILO_TBB tbb::parallel_invoke( [nRows, &row]() { for( int i = 0; i < nRows; ++i ) row[i] = i; }, [&constMatrix, &coefhash, this]() { computeRowHashes( constMatrix, coefhash.get() ); }, [&constMatrix, &supportid, this]() { computeSupportId( constMatrix, supportid.get() ); } ); #else for( int i = 0; i < nRows; ++i ) row[i] = i; computeRowHashes( constMatrix, coefhash.get() ); computeSupportId( constMatrix, supportid.get() ); #endif pdqsort( row.get(), row.get() + nRows, [&]( int a, int b ) { return supportid[a] < supportid[b] || ( supportid[a] == supportid[b] && coefhash[a] < coefhash[b] ) || ( supportid[a] == supportid[b] && coefhash[a] == coefhash[b] && rowperm[a] < rowperm[b] ); } ); Vec> stored_parallel_rows; for( int i = 0; i < nRows; ) { int bucketSize = determineBucketSize( nRows, supportid, coefhash, row, i ); // if more than one row is in the bucket try to find parallel rows if( bucketSize > 1 ) { Vec parallel_rows; parallel_rows.reserve( bucketSize ); findParallelRows( num, row.get() + i, bucketSize, constMatrix, parallel_rows ); if( !parallel_rows.empty() ) stored_parallel_rows.emplace_back( parallel_rows ); } i = bucketSize + i; } if( !stored_parallel_rows.empty() ) { result = PresolveStatus::kReduced; for( const Vec& parallel_rows : stored_parallel_rows ) { assert( !parallel_rows.empty() ); handleRows( parallel_rows ); if( result == PresolveStatus::kInfeasible ) break; } } return result; } template int ParallelRowDetection::determineBucketSize( int nRows, std::unique_ptr& supportid, std::unique_ptr& coefhash, std::unique_ptr& row, int i ) { int j; for( j = i + 1; j < nRows; ++j ) { if( coefhash[row[i]] != coefhash[row[j]] || supportid[row[i]] != supportid[row[j]] ) { break; } } assert( j > i ); return j - i; } } // namespace papilo #endif