/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* */
/* 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_CONSTRAINT_MATRIX_HPP_
#define _PAPILO_CORE_CONSTRAINT_MATRIX_HPP_
#define UNUSED(expr) do { (void)(expr); } while (0)
#include "papilo/core/MatrixBuffer.hpp"
#include "papilo/core/Objective.hpp"
#include "papilo/core/PresolveMethod.hpp"
#include "papilo/core/RowFlags.hpp"
#include "papilo/core/SingleRow.hpp"
#include "papilo/core/SparseStorage.hpp"
#include "papilo/core/VariableDomains.hpp"
#include "papilo/misc/MultiPrecision.hpp"
#include "papilo/misc/Num.hpp"
#include "papilo/misc/Vec.hpp"
#include "papilo/misc/compress_vector.hpp"
#include "papilo/misc/fmt.hpp"
#ifdef PAPILO_TBB
#include "papilo/misc/tbb.hpp"
#endif
#include
namespace papilo
{
/// non-owning type representing a sparse vector
template
class SparseVectorView
{
public:
SparseVectorView() : vals( nullptr ), indices( nullptr ), len( 0 ) {}
SparseVectorView( const REAL* _vals, const int* _inds, int _len )
: vals( _vals ), indices( _inds ), len( _len )
{
}
const REAL*
getValues() const
{
return vals;
}
const int*
getIndices() const
{
return indices;
}
int
getLength() const
{
return len;
}
REAL
getMaxAbsValue() const
{
REAL maxabsval = 0.0;
for( int i = 0; i != len; ++i )
maxabsval = std::max( REAL( abs( vals[i] ) ), maxabsval );
return maxabsval;
}
std::pair
getMinMaxAbsValue() const
{
if( len != 0 )
{
REAL maxabsval = abs( vals[0] );
REAL minabsval = maxabsval;
for( int i = 1; i != len; ++i )
{
maxabsval = std::max( REAL( abs( vals[i] ) ), maxabsval );
minabsval = std::min( REAL( abs( vals[i] ) ), minabsval );
}
return std::make_pair( minabsval, maxabsval );
}
return std::make_pair( 0, 0 );
}
REAL
getDynamism() const
{
if( len != 0 )
{
REAL maxabsval = abs( vals[0] );
REAL minabsval = maxabsval;
for( int i = 1; i != len; ++i )
{
maxabsval = Num::max( abs( vals[i] ), maxabsval );
minabsval = Num::min( abs( vals[i] ), minabsval );
}
return maxabsval / minabsval;
}
return 0;
}
private:
const REAL* vals;
const int* indices;
int len;
};
/// type representing the constraint matrix including the left and right hand
/// sides
template
class ConstraintMatrix
{
public:
ConstraintMatrix() = default;
/// construct the constraint matrix from the given values
ConstraintMatrix( SparseStorage cons_matrix_init,
SparseStorage cons_matrix_transp_init,
Vec lhs_values_init, Vec rhs_values_init,
Vec row_flags_init )
: cons_matrix( std::move( cons_matrix_init ) ),
cons_matrix_transp( std::move( cons_matrix_transp_init ) ),
lhs_values( std::move( lhs_values_init ) ),
rhs_values( std::move( rhs_values_init ) ),
flags( std::move( row_flags_init ) )
{
rowsize.reserve( cons_matrix.getNRows() );
colsize.reserve( cons_matrix.getNCols() );
auto rowranges = cons_matrix.getRowRanges();
assert( (int) flags.size() == cons_matrix.getNRows() );
for( int i = 0; i < cons_matrix.getNRows(); ++i )
rowsize.push_back( rowranges[i].end - rowranges[i].start );
auto colranges = cons_matrix_transp.getRowRanges();
for( int i = 0; i < cons_matrix.getNCols(); ++i )
colsize.push_back( colranges[i].end - colranges[i].start );
}
/// returns number of rows in the constraint matrix
int
getNRows() const
{
return cons_matrix.getNRows();
}
/// returns number of columns in the constraint matrix
int
getNCols() const
{
return cons_matrix.getNCols();
}
/// returns number of non-zeros in the constraint matrix
int
getNnz() const
{
assert( cons_matrix.getNnz() == cons_matrix_transp.getNnz() );
return cons_matrix.getNnz();
}
std::pair
getRangeInfo() const
{
return std::make_pair( cons_matrix.getRowRanges(),
cons_matrix.getNRows() );
}
const int*
getColumns() const
{
return cons_matrix.getColumns();
}
/// returns a sparse vector view on the row coefficients and their column
/// indices
SparseVectorView
getRowCoefficients( int r ) const
{
assert( r >= 0 && r < getNRows() );
auto index_range = cons_matrix.getRowRanges()[r];
return SparseVectorView{
cons_matrix.getValues() + index_range.start,
cons_matrix.getColumns() + index_range.start,
index_range.end - index_range.start };
}
/// returns a sparse vector view on the column coefficients and their row
/// indices
SparseVectorView
getColumnCoefficients( int c ) const
{
assert( c >= 0 && c < getNCols() );
auto index_range = cons_matrix_transp.getRowRanges()[c];
return SparseVectorView{
cons_matrix_transp.getValues() + index_range.start,
cons_matrix_transp.getColumns() + index_range.start,
index_range.end - index_range.start };
}
/// returns maximal change of constraint feasibility for the given change of
/// value in the given column
REAL
getMaxFeasChange( int col, const REAL& val ) const
{
return abs( val * getColumnCoefficients( col ).getMaxAbsValue() );
}
/// returns dense vector with left hand side values of each row
Vec&
getLeftHandSides()
{
return lhs_values;
}
/// returns dense vector with right hand side values of each row
Vec&
getRightHandSides()
{
return rhs_values;
}
/// returns dense vector with left hand side values of each row
const Vec&
getLeftHandSides() const
{
return lhs_values;
}
/// returns dense vector with right hand side values of each row
const Vec&
getRightHandSides() const
{
return rhs_values;
}
const Vec&
getRowFlags() const
{
return flags;
}
Vec&
getRowFlags()
{
return flags;
}
/// modify the value of an element from the left hand side
template
void
modifyLeftHandSide( const int index, const Num& num,
const REAL& value = 0)
{
assert( index >= 0 );
assert( index < getNRows() );
if( !infval )
{
flags[index].unset( RowFlag::kLhsInf );
if( num.isEq( value, rhs_values[index] ) )
lhs_values[index] = rhs_values[index];
else
lhs_values[index] = value;
if( !flags[index].test( RowFlag::kRhsInf ) &&
lhs_values[index] == rhs_values[index] )
flags[index].set( RowFlag::kEquation );
else
flags[index].unset( RowFlag::kEquation );
}
else
{
flags[index].unset( RowFlag::kEquation );
flags[index].set( RowFlag::kLhsInf );
}
}
/// modify the value of an element from the right hand side
template
void
modifyRightHandSide( const int index, const Num& num,
const REAL& value = 0)
{
assert( index >= 0 );
assert( index < getNRows() );
if( !infval )
{
flags[index].unset( RowFlag::kRhsInf );
if( num.isEq( value, lhs_values[index] ) )
rhs_values[index] = lhs_values[index];
else
rhs_values[index] = value;
if( !flags[index].test( RowFlag::kLhsInf ) &&
lhs_values[index] == rhs_values[index] )
flags[index].set( RowFlag::kEquation );
else
flags[index].unset( RowFlag::kEquation );
}
else
{
flags[index].unset( RowFlag::kEquation );
flags[index].set( RowFlag::kRhsInf );
}
}
/// is given row redundant
bool
isRowRedundant( const int row ) const
{
return flags[row].test( RowFlag::kRedundant );
}
/// Compress the storage and the indices by removing empty rows and columns
/// from the system. Returns a pair of vectors that store for each index used
/// previously the new index or -1 if the corresponding row/column has been
/// removed.
/// The first vector of the pair stores the mapping for the rows, the second
/// vector stores the mapping of the columns.
std::pair, Vec>
compress( bool full = false );
void
deleteRowsAndCols( Vec& deletedRows, Vec& deletedCols,
Vec>& activities,
Vec& singletonRows, Vec& singletonCols,
Vec& emptyCols );
const Vec&
getRowSizes() const
{
return rowsize;
}
Vec&
getRowSizes()
{
return rowsize;
}
const Vec&
getColSizes() const
{
return colsize;
}
Vec&
getColSizes()
{
return colsize;
}
template
void
changeCoefficients( const MatrixBuffer& matrixBuffer,
Vec& singletonRows, Vec& singletonCols,
Vec& emptyCols, Vec>& activities,
CoeffChanged&& coeffChanged )
{
if( matrixBuffer.empty() )
return;
// update row major storage, and pass down the coeffChanged callback
#ifdef PAPILO_TBB
tbb::parallel_invoke(
[&]() {
#endif
SmallVec buffer;
const MatrixEntry* iter =
matrixBuffer.template begin( buffer );
while( iter != matrixBuffer.end() )
{
int row = iter->row;
int newsize = cons_matrix.changeRowInplace(
row,
[&]() {
return iter != matrixBuffer.end() && iter->row == row;
},
[&]() {
auto nextval = std::make_pair( iter->col, iter->val );
iter = matrixBuffer.template next( buffer );
return nextval;
},
coeffChanged );
if( newsize != rowsize[row] )
{
switch( newsize )
{
case 0:
activities[row].min = 0;
activities[row].max = 0;
break;
case 1:
singletonRows.push_back( row );
default:
break;
}
rowsize[row] = newsize;
}
}
#ifdef PAPILO_TBB
},
[&]() {
#endif
SmallVec buffer2;
// update col major storage, do not pass down the coeffChanged
// callback so that it
/// is only called once
const MatrixEntry* iter2 =
matrixBuffer.template begin( buffer2 );
while( iter2 != matrixBuffer.end() )
{
int col = iter2->col;
int newsize = cons_matrix_transp.changeRowInplace(
col,
[&]() {
return iter2 != matrixBuffer.end() && iter2->col == col;
},
[&]() {
auto nextval = std::make_pair( iter2->row, iter2->val );
iter2 = matrixBuffer.template next( buffer2 );
return nextval;
},
[]( int, int, REAL, REAL ) {} );
if( newsize != colsize[col] )
{
switch( newsize )
{
case 0:
emptyCols.push_back( col );
break;
case 1:
singletonCols.push_back( col );
default:
break;
}
// in case that a singleton var is aggregated and has 2
// appearances and then is reduced again immediately it may
// appear two times in the list -> causes no bug but some
// unneccessary overhead
colsize[col] = newsize;
}
}
#ifdef PAPILO_TBB
} );
#endif
}
/// get the index of the column in the sparse array of row coefficients
template
int
getSparseIndex( int col, int row ) const
{
if( !transpose )
{
auto rowCoef = getRowCoefficients( row );
const int* indices = rowCoef.getIndices();
const int len = rowCoef.getLength();
int index = std::lower_bound( indices, indices + len, col ) - indices;
if( index != len && indices[index] == col )
return index;
return -1;
}
else
{
auto colCoef = getColumnCoefficients( col );
const int* indices = colCoef.getIndices();
const int len = colCoef.getLength();
int index = std::lower_bound( indices, indices + len, row ) - indices;
if( index != len && indices[index] == row )
return index;
return -1;
}
}
/// checks in there is enough sapce in the sparse storage to performe an
/// aggregation, and remove rows from the input relevantRows where the
/// sparsity condition is not verified, returns true if the condition is
/// verified on all relevantRows
bool
checkAggregationSparsityCondition( int col,
const SparseVectorView& equalityLHS,
int maxfillin, int maxshiftperrow,
Vec& indbuffer );
int
sparsify( const Num& num, int eqrow, const REAL& scale, int targetrow,
Vec& intbuffer, Vec& valbuffer,
const VariableDomains& domains, Vec& changedActivities,
Vec>& activities, Vec& singletonRows,
Vec& singletonCols, Vec& emptyCols, int presolveround );
/// perform the substitution of a column using an equality, and updates
/// the sides
/// @param substituted_col the columns to substituted
/// @param equalityLHS the right hand side of the equality i.e sum{
/// a_j*x_i }, (needs to be sorted)
/// @param equalityRHS the left hand side of the equality such that sum{
/// a_j*x_i } = equalityRHS
void
aggregate( const Num& num, int substituted_col, SparseVectorView equalityLHS,
REAL equalityRHS, const VariableDomains& domains,
Vec& indbuffer, Vec& valbuffer,
Vec>& tripletbuffer, Vec& changedActivities,
Vec>& activities, Vec& singletonRows,
Vec& singletonCols, Vec& emptyCols, int presolveround );
const SparseStorage&
getMatrixTranspose() const
{
return cons_matrix_transp;
}
SparseStorage&
getMatrixTranspose()
{
return cons_matrix_transp;
}
const SparseStorage&
getConstraintMatrix() const
{
return cons_matrix;
}
SparseStorage&
getConstraintMatrix()
{
return cons_matrix;
}
template
void
serialize( Archive& ar, const unsigned int version )
{
ar& cons_matrix;
if( Archive::is_loading::value )
cons_matrix_transp = cons_matrix.getTranspose();
ar& lhs_values;
ar& rhs_values;
ar& flags;
ar& rowsize;
ar& colsize;
}
private:
/// row-major compressed sparse storage (CSR) of the constraint matrix
SparseStorage cons_matrix;
/// column-major compressed sparse storage (CSC) of the
/// constraint matrix
SparseStorage cons_matrix_transp;
/// left hand side values for each row in the constraint
/// matrix
Vec lhs_values;
/// right hand side values for each row in the constraint
/// matrix
Vec rhs_values;
Vec flags;
/// additional vector storing the number of non-zeros
/// within each row of the constraint matrix
Vec rowsize;
/// additional vector storing the number of non-zeros
/// within each column of the constraint matrix
Vec colsize;
};
#ifdef PAPILO_USE_EXTERN_TEMPLATES
extern template class ConstraintMatrix;
extern template class ConstraintMatrix;
extern template class ConstraintMatrix;
#endif
template
std::pair, Vec>
ConstraintMatrix::compress( bool full )
{
std::pair, Vec> mappings;
#ifdef PAPILO_TBB
tbb::parallel_invoke(
[this, &mappings, full]() {
mappings.first =
cons_matrix_transp.compress( colsize, rowsize, full );
},
[this, &mappings, full]() {
mappings.second = cons_matrix.compress( rowsize, colsize, full );
} );
#else
mappings.first =
cons_matrix_transp.compress( colsize, rowsize, full );
mappings.second = cons_matrix.compress( rowsize, colsize, full );
#endif
#ifdef PAPILO_TBB
tbb::parallel_invoke(
[this, &mappings, full]() {
compress_vector( mappings.second, colsize );
if( full )
colsize.shrink_to_fit();
},
[this, &mappings, full]() {
compress_vector( mappings.first, rowsize );
if( full )
rowsize.shrink_to_fit();
},
[this, &mappings, full]() {
compress_vector( mappings.first, lhs_values );
if( full )
lhs_values.shrink_to_fit();
},
[this, &mappings, full]() {
compress_vector( mappings.first, rhs_values );
if( full )
rhs_values.shrink_to_fit();
},
[this, &mappings, full]() {
compress_vector( mappings.first, flags );
if( full )
flags.shrink_to_fit();
} );
#else
compress_vector( mappings.second, colsize );
compress_vector( mappings.first, rowsize );
compress_vector( mappings.first, lhs_values );
compress_vector( mappings.first, rhs_values );
compress_vector( mappings.first, flags );
if( full )
{
colsize.shrink_to_fit();
rowsize.shrink_to_fit();
rhs_values.shrink_to_fit();
lhs_values.shrink_to_fit();
flags.shrink_to_fit();
}
#endif
return mappings;
}
template
void
ConstraintMatrix::deleteRowsAndCols( Vec& deletedRows,
Vec& deletedCols,
Vec>& activities,
Vec& singletonRows,
Vec& singletonCols,
Vec& emptyCols )
{
if( deletedRows.empty() && deletedCols.empty() )
return;
// CSR storage
int* rowcols = cons_matrix.getColumns();
IndexRange* rowranges = cons_matrix.getRowRanges();
REAL* rowvalues = cons_matrix.getValues();
// CSC storage
int* colrows = cons_matrix_transp.getColumns();
IndexRange* colranges = cons_matrix_transp.getRowRanges();
REAL* colvalues = cons_matrix_transp.getValues();
#ifdef PAPILO_TBB
tbb::parallel_invoke(
[this, &deletedRows]() {
for( int row : deletedRows )
{
cons_matrix.getNnz() -= rowsize[row];
rowsize[row] = -1;
}
},
[this, &deletedCols]() {
for( int col : deletedCols )
colsize[col] = -1;
} );
#else
for( int row : deletedRows )
{
cons_matrix.getNnz() -= rowsize[row];
rowsize[row] = -1;
}
for( int col : deletedCols )
colsize[col] = -1;
#endif
// delete rows from row storage and update column sizes
#ifdef PAPILO_TBB
tbb::parallel_invoke(
[this, &deletedRows, rowranges, rowcols, &activities]() {
#endif
for( int row : deletedRows )
{
assert( flags[row].test( RowFlag::kRedundant ) );
for( int i = rowranges[row].start; i != rowranges[row].end; ++i )
{
int col = rowcols[i];
if( colsize[col] == -1 )
continue;
--colsize[col];
assert( colsize[col] >= 0 );
}
rowranges[row].start = rowranges[row + 1].start;
rowranges[row].end = rowranges[row + 1].start;
lhs_values[row] = 0.0;
rhs_values[row] = 0.0;
activities[row].ninfmax = 0;
activities[row].ninfmin = 0;
activities[row].min = 0;
activities[row].max = 0;
}
#ifdef PAPILO_TBB
},
// delete cols from col storage and update rowsizes
[this, &deletedCols, colranges, colrows] {
#endif
for( int col : deletedCols )
{
for( int i = colranges[col].start; i != colranges[col].end; ++i )
{
int row = colrows[i];
if( rowsize[row] == -1 )
continue;
--rowsize[row];
assert( rowsize[row] >= 0 );
}
colranges[col].start = colranges[col + 1].start;
colranges[col].end = colranges[col + 1].start;
}
#ifdef PAPILO_TBB
} );
#endif
#ifdef PAPILO_TBB
tbb::parallel_invoke(
[this, colranges, &singletonCols, &emptyCols, colrows, colvalues]() {
#endif
for( int col = 0; col != getNCols(); ++col )
{
// if the size did not change, skip column
if( colsize[col] == -1 ||
colsize[col] == colranges[col].end - colranges[col].start )
continue;
// if the size is now 1, add to singleton column vector
switch( colsize[col] )
{
case 0:
emptyCols.push_back( col );
colranges[col].start = colranges[col + 1].start;
colranges[col].end = colranges[col + 1].start;
break;
case 1:
singletonCols.push_back( col );
}
if( colsize[col] >= 1 )
{
// now move contents of column to occupy free spaces
int j = 0;
for( int i = colranges[col].start; i != colranges[col].end;
++i )
{
int row = colrows[i];
if( rowsize[row] == -1 )
++j;
else if( j > 0 )
{
colvalues[i - j] = colvalues[i];
colrows[i - j] = colrows[i];
}
}
assert( colsize[col] >= 0 );
assert( colsize[col] + colranges[col].start ==
colranges[col].end - j );
colranges[col].end = colranges[col].start + colsize[col];
}
}
#ifdef PAPILO_TBB
},
[this, rowranges, &singletonRows, &activities, rowcols, rowvalues]() {
#endif
for( int row = 0; row != getNRows(); ++row )
{
// if the size did not change, skip row
if( rowsize[row] == -1 ||
rowsize[row] == rowranges[row].end - rowranges[row].start )
continue;
// if the size is now 1, add to singleton row vector
switch( rowsize[row] )
{
case 0:
activities[row].min = 0;
activities[row].max = 0;
break;
case 1:
singletonRows.push_back( row );
}
// now move contents of row to occupy free spaces
int j = 0;
for( int i = rowranges[row].start; i != rowranges[row].end; ++i )
{
int col = rowcols[i];
if( colsize[col] == -1 )
++j;
else if( j > 0 )
{
rowvalues[i - j] = rowvalues[i];
rowcols[i - j] = rowcols[i];
}
}
assert( rowsize[row] >= 0 );
assert( rowsize[row] + rowranges[row].start ==
rowranges[row].end - j );
cons_matrix.getNnz() -= j;
rowranges[row].end = rowranges[row].start + rowsize[row];
}
#ifdef PAPILO_TBB
} );
#endif
cons_matrix_transp.getNnz() = cons_matrix.getNnz();
deletedRows.clear();
deletedCols.clear();
}
template
bool
ConstraintMatrix::checkAggregationSparsityCondition(
int col, const SparseVectorView& equalityLHS, int maxfillin,
int maxshiftperrow, Vec& indbuffer )
{
const int* indices = equalityLHS.getIndices();
const int len = equalityLHS.getLength();
const auto& freecolcoef = getColumnCoefficients( col );
const int* freecolindices = freecolcoef.getIndices();
const int length = freecolcoef.getLength();
auto rowranges = cons_matrix.getRowRanges();
auto colranges = cons_matrix_transp.getRowRanges();
int totalfillin = 0;
bool eqinmatrix = false;
bool shift = true;
indbuffer.clear();
indbuffer.reserve( std::max( length, len ) );
for( int k = 0; k < length; ++k )
{
int row = freecolindices[k];
auto currentrow = getRowCoefficients( row );
const int* rowindices = currentrow.getIndices();
const int currentrowlen = currentrow.getLength();
if( rowindices == indices )
{
totalfillin -= len;
eqinmatrix = true;
indbuffer.push_back( 0 );
continue;
}
int i = 0;
int j = 0;
int fillin = -1;
while( i < len && j < currentrowlen )
{
if( indices[i] == rowindices[j] )
{
++i;
++j;
}
else if( indices[i] < rowindices[j] )
{
++i;
++fillin;
}
else
{
++j;
}
}
fillin += ( len - i );
totalfillin += fillin;
int sparespace = rowranges[row + 1].start - rowranges[row].end;
if( sparespace < fillin )
shift = true;
indbuffer.push_back( fillin );
}
if( totalfillin > maxfillin )
{
indbuffer.clear();
return false;
}
if( shift && !cons_matrix.shiftRows( freecolindices, length, maxshiftperrow,
indbuffer ) )
{
indbuffer.clear();
return false;
}
indbuffer.clear();
shift = false;
for( int k = 0; k < len; ++k )
{
int currentcolind = indices[k];
if( currentcolind == col )
{
indbuffer.push_back( 0 );
continue;
}
auto currentcol = getColumnCoefficients( currentcolind );
const int* colindices = currentcol.getIndices();
const int currentcollen = currentcol.getLength();
int i = 0;
int j = 0;
int fillin = eqinmatrix ? -1 : 0;
while( i < length && j < currentcollen )
{
if( freecolindices[i] == colindices[j] )
{
++i;
++j;
}
else if( freecolindices[i] < colindices[j] )
{
++i;
++fillin;
}
else
{
++j;
}
}
fillin += ( length - i );
int sparespace =
colranges[currentcolind + 1].start - colranges[currentcolind].end;
if( sparespace < fillin )
shift = true;
indbuffer.push_back( fillin );
}
if( shift && !cons_matrix_transp.shiftRows( indices, len, maxshiftperrow,
indbuffer ) )
{
indbuffer.clear();
return false;
}
indbuffer.clear();
return true;
}
template
int
ConstraintMatrix::sparsify(
const Num& num, int eqrow, const REAL& scale, int targetrow,
Vec& intbuffer, Vec& valbuffer,
const VariableDomains& domains, Vec& changedActivities,
Vec>& activities, Vec& singletonRows,
Vec& singletonCols, Vec& emptyCols, int presolveround )
{
int ncancel = 0;
int fillincol = -1;
REAL fillinval = 0;
IndexRange* colranges = cons_matrix_transp.getRowRanges();
const IndexRange& eqrange = cons_matrix.getRowRanges()[eqrow];
IndexRange& targetrange = cons_matrix.getRowRanges()[targetrow];
int* rowcols = cons_matrix.getColumns();
REAL* rowvals = cons_matrix.getValues();
int j = eqrange.start;
int k = targetrange.start;
while( j != eqrange.end && k != targetrange.end )
{
if( rowcols[j] == rowcols[k] )
{
REAL newval = rowvals[j] * scale + rowvals[k];
if( num.isZero( newval ) )
++ncancel;
else if( num.isFeasZero( newval ) )
return 0;
++j;
++k;
}
else if( rowcols[j] < rowcols[k] )
{
if( fillincol != -1 )
return 0;
fillincol = rowcols[j];
if( colranges[fillincol + 1].start - colranges[fillincol].start ==
colsize[fillincol] )
return 0;
fillinval = scale * rowvals[j];
--ncancel;
++j;
}
else
{
++k;
}
}
int remainingfillin = eqrange.end - j;
if( remainingfillin != 0 )
{
if( remainingfillin != 1 )
return 0;
if( fillincol != -1 )
return 0;
fillincol = rowcols[j];
if( colranges[fillincol + 1].start - colranges[fillincol].start ==
colsize[fillincol] )
return 0;
fillinval = scale * rowvals[j];
--ncancel;
}
if( ncancel <= 0 )
return 0;
// change coefficients for column where fillin occurs (we allow at most one
// such column, otherwise we return from this function before we reach this
// part)
if( fillincol != -1 )
{
assert( colsize[fillincol] <
colranges[fillincol + 1].start - colranges[fillincol].start );
colsize[fillincol] = cons_matrix_transp.changeRow(
fillincol, 0, 1,
[&]( int i ) {
assert( i == 0 );
return targetrow;
},
[&]( int i ) {
assert( i == 0 );
return fillinval;
},
[]( const REAL& curr, const REAL& val ) {
assert( curr == 0 );
return val;
},
[]( int, int, REAL, REAL ) {}, valbuffer, intbuffer );
}
// change coefficients in column storage for other columns
j = eqrange.start;
k = targetrange.start;
while( j != eqrange.end && k != targetrange.end )
{
if( rowcols[j] == rowcols[k] )
{
int col = rowcols[k];
assert( col != fillincol );
REAL newval = rowvals[k] + scale * rowvals[j];
if( num.isZero( newval ) )
{
--colsize[col];
switch( colsize[col] )
{
case 0:
emptyCols.push_back( col );
break;
case 1:
singletonCols.push_back( col );
}
newval = 0;
}
int count = 0;
int newsize = cons_matrix_transp.changeRowInplace(
col, [&]() { return ( count++ ) == 0; },
[&]() {
assert( count == 1 );
return std::make_pair( targetrow, newval );
},
[]( int, int, REAL, REAL ) {} );
UNUSED(newsize);
assert( newsize == colsize[col] );
++j;
++k;
}
else if( rowcols[j] < rowcols[k] )
{
assert( rowcols[j] == fillincol );
++j;
}
else
{
assert( rowcols[k] < rowcols[j] );
++k;
}
}
assert( j == eqrange.end ||
( j + 1 == eqrange.end && rowcols[j] == fillincol ) );
// update sides if necessary
if( rhs_values[eqrow] != 0 )
{
if( !flags[targetrow].test( RowFlag::kLhsInf ) )
lhs_values[targetrow] += scale * rhs_values[eqrow];
if( !flags[targetrow].test( RowFlag::kRhsInf ) )
rhs_values[targetrow] += scale * rhs_values[eqrow];
// due to numerics the row can become an equation
if( !flags[targetrow].test( RowFlag::kLhsInf, RowFlag::kRhsInf,
RowFlag::kEquation ) &&
lhs_values[targetrow] == rhs_values[targetrow] )
flags[targetrow].set( RowFlag::kEquation );
}
// finally update the row
auto updateActivity = [&]( int row, int col, REAL oldval, REAL newval ) {
auto activityChange = [row, presolveround, &changedActivities](
ActivityChange actChange,
RowActivity& activity ) {
if( activity.lastchange == presolveround )
return;
if( actChange == ActivityChange::kMin && activity.ninfmin > 1 )
return;
if( actChange == ActivityChange::kMax && activity.ninfmax > 1 )
return;
activity.lastchange = presolveround;
changedActivities.push_back( row );
};
const SparseVectorView& rowvec = getRowCoefficients( row );
update_activity_after_coeffchange(
domains.lower_bounds[col], domains.upper_bounds[col],
domains.flags[col], oldval, newval, activities[row], rowvec.getLength(),
rowvec.getIndices(), rowvec.getValues(), domains, num,
activityChange );
};
int newsize = cons_matrix.changeRow(
targetrow, eqrange.start, eqrange.end,
[&]( int i ) { return rowcols[i]; },
[&]( int i ) { return scale * rowvals[i]; },
[&]( const REAL& a, const REAL& b ) {
REAL val = a + b;
if( num.isZero( val ) )
val = 0;
return val;
},
updateActivity, valbuffer, intbuffer );
assert( rowsize[targetrow] - ncancel == newsize );
rowsize[targetrow] = newsize;
switch( rowsize[targetrow] )
{
case 0:
activities[targetrow].min = 0;
activities[targetrow].max = 0;
break;
case 1:
singletonRows.push_back( targetrow );
}
assert( cons_matrix.getNnz() == cons_matrix_transp.getNnz() );
return ncancel;
}
template
void
ConstraintMatrix::aggregate(
const Num& num, int substituted_col, SparseVectorView equalityLHS,
REAL equalityRHS, const VariableDomains& domains, Vec& indbuffer,
Vec& valbuffer, Vec>& tripletbuffer,
Vec& changedActivities, Vec>& activities,
Vec& singletonRows, Vec& singletonCols, Vec& emptyCols,
int presolveround )
{
const int equalitylen = equalityLHS.getLength();
const REAL* equalityvalues = equalityLHS.getValues();
const int* equalityindices = equalityLHS.getIndices();
assert( std::is_sorted( equalityindices, equalityindices + equalitylen ) );
int freeColPos;
for( freeColPos = 0; freeColPos < equalitylen; ++freeColPos )
{
if( equalityindices[freeColPos] == substituted_col )
break;
}
assert( freeColPos != equalitylen );
REAL eqbasescale = REAL{ -1 } / equalityvalues[freeColPos];
assert( tripletbuffer.empty() );
tripletbuffer.reserve( equalitylen * colsize[substituted_col] );
auto updateActivity = [presolveround, &changedActivities, &domains,
&activities, &tripletbuffer, this, num](
int row, int col, REAL oldval, REAL newval ) {
if( oldval == newval )
return;
auto activityChange = [row, presolveround, &changedActivities](
ActivityChange actChange,
RowActivity& activity ) {
if( activity.lastchange == presolveround )
return;
if( actChange == ActivityChange::kMin && activity.ninfmin > 1 )
return;
if( actChange == ActivityChange::kMax && activity.ninfmax > 1 )
return;
activity.lastchange = presolveround;
changedActivities.push_back( row );
};
tripletbuffer.emplace_back( col, row, newval );
const SparseVectorView& rowVec = getRowCoefficients( row );
update_activity_after_coeffchange(
domains.lower_bounds[col], domains.upper_bounds[col],
domains.flags[col], oldval, newval, activities[row],
rowVec.getLength(), rowVec.getIndices(), rowVec.getValues(), domains,
num, activityChange );
};
auto mergeVal = [&]( const REAL& oldval, const REAL& addition ) {
REAL val = oldval + addition;
if( num.isZero( val ) )
return REAL{ 0 };
return val;
};
const auto& freecol = getColumnCoefficients( substituted_col );
const REAL* freecolcoef = freecol.getValues();
const int* freecolindices = freecol.getIndices();
const int freecollength = freecol.getLength();
// make the changes in the matrix and update the constraints' sides
for( int i = 0; i < freecollength; ++i )
{
int row = freecolindices[i];
assert( flags[row].test( RowFlag::kRedundant ) ||
( !flags[row].test( RowFlag::kEquation ) &&
( flags[row].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) ||
lhs_values[row] != rhs_values[row] ) ) ||
( flags[row].test( RowFlag::kEquation ) &&
!flags[row].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) &&
lhs_values[row] == rhs_values[row] ) );
// do not modify the equations content while it is still used,
// just set the size and the sides to zero since they are copied in
// local variables
if( cons_matrix.getColumns() + cons_matrix.rowranges[row].start ==
equalityindices )
{
for( int k = 0; k < equalitylen; ++k )
tripletbuffer.emplace_back( equalityindices[k], row, 0 );
flags[row].set( RowFlag::kRedundant );
cons_matrix.rowranges[row].start =
cons_matrix.rowranges[row + 1].start;
cons_matrix.rowranges[row].end = cons_matrix.rowranges[row + 1].start;
lhs_values[row] = 0.0;
rhs_values[row] = 0.0;
cons_matrix.getNnz() -= rowsize[row];
rowsize[row] = -1;
continue;
}
REAL eqscale = eqbasescale * freecolcoef[i];
int newsize = cons_matrix.changeRow(
row, int{ 0 }, equalitylen,
[&]( int k ) { return equalityindices[k]; },
[&]( int k ) {
return k == freeColPos ? REAL( -freecolcoef[i] )
: REAL( equalityvalues[k] * eqscale );
},
mergeVal, updateActivity, valbuffer, indbuffer );
if( newsize != rowsize[row] )
{
switch( newsize )
{
case 0:
activities[row].min = 0;
activities[row].max = 0;
break;
case 1:
singletonRows.push_back( row );
default:
break;
}
rowsize[row] = newsize;
}
assert(
std::all_of( getRowCoefficients( row ).getIndices(),
getRowCoefficients( row ).getIndices() + rowsize[row],
[&]( int rowcol ) { return rowcol != substituted_col; } ) );
// change the bounds
if( equalityRHS != 0 )
{
if( !flags[row].test( RowFlag::kLhsInf ) )
lhs_values[row] += eqscale * equalityRHS;
if( !flags[row].test( RowFlag::kRhsInf ) )
rhs_values[row] += eqscale * equalityRHS;
// due to numerics the row can become an equation
if( !flags[row].test( RowFlag::kLhsInf, RowFlag::kRhsInf,
RowFlag::kEquation ) &&
lhs_values[row] == rhs_values[row] )
flags[row].set( RowFlag::kEquation );
}
assert( flags[row].test( RowFlag::kRedundant ) ||
( !flags[row].test( RowFlag::kEquation ) &&
( flags[row].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) ||
lhs_values[row] != rhs_values[row] ) ) ||
( flags[row].test( RowFlag::kEquation ) &&
!flags[row].test( RowFlag::kLhsInf, RowFlag::kRhsInf ) &&
lhs_values[row] == rhs_values[row] ) );
}
if( !tripletbuffer.empty() )
{
pdqsort( tripletbuffer.begin(), tripletbuffer.end() );
auto handleCol = [&]( int col, int start, int end ) {
int newsize = cons_matrix_transp.changeRow(
col, start, end,
[&]( int k ) { return std::get<1>( tripletbuffer[k] ); },
[&]( int k ) { return std::get<2>( tripletbuffer[k] ); },
[]( const REAL& oldval, const REAL& newval ) { return newval; },
[]( int, int, REAL, REAL ) {}, valbuffer, indbuffer );
if( newsize != colsize[col] )
{
switch( newsize )
{
case 0:
emptyCols.push_back( col );
break;
case 1:
singletonCols.push_back( col );
default:
break;
}
//TODO: remove column from singleton columns if necessary
colsize[col] = newsize;
}
};
int start = 0;
int currcol = std::get<0>( tripletbuffer[0] );
int nchgs = tripletbuffer.size();
for( int i = 1; i != nchgs; ++i )
{
if( std::get<0>( tripletbuffer[i] ) != currcol )
{
handleCol( currcol, start, i );
currcol = std::get<0>( tripletbuffer[i] );
start = i;
}
}
handleCol( currcol, start, nchgs );
tripletbuffer.clear();
}
// set column size to zero
cons_matrix_transp.rowranges[substituted_col].start =
cons_matrix_transp.rowranges[substituted_col + 1].start;
cons_matrix_transp.rowranges[substituted_col].end =
cons_matrix_transp.rowranges[substituted_col + 1].start;
cons_matrix_transp.getNnz() -= colsize[substituted_col];
colsize[substituted_col] = -1;
assert( cons_matrix_transp.getNnz() == cons_matrix.getNnz() );
}
} // namespace papilo
#endif