#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 <algorithm>
namespace papilo
{
template <typename REAL>
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<REAL, REAL>
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<REAL>::max( abs( vals[i] ), maxabsval );
minabsval = Num<REAL>::min( abs( vals[i] ), minabsval );
}
return maxabsval / minabsval;
}
return 0;
}
private:
const REAL* vals;
const int* indices;
int len;
};
template <typename REAL>
class ConstraintMatrix
{
public:
ConstraintMatrix() = default;
ConstraintMatrix( SparseStorage<REAL> cons_matrix_init,
SparseStorage<REAL> cons_matrix_transp_init,
Vec<REAL> lhs_values_init, Vec<REAL> rhs_values_init,
Vec<RowFlags> 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 );
}
int
getNRows() const
{
return cons_matrix.getNRows();
}
int
getNCols() const
{
return cons_matrix.getNCols();
}
int
getNnz() const
{
assert( cons_matrix.getNnz() == cons_matrix_transp.getNnz() );
return cons_matrix.getNnz();
}
std::pair<const IndexRange*, int>
getRangeInfo() const
{
return std::make_pair( cons_matrix.getRowRanges(),
cons_matrix.getNRows() );
}
const int*
getColumns() const
{
return cons_matrix.getColumns();
}
SparseVectorView<REAL>
getRowCoefficients( int r ) const
{
assert( r >= 0 && r < getNRows() );
auto index_range = cons_matrix.getRowRanges()[r];
return SparseVectorView<REAL>{
cons_matrix.getValues() + index_range.start,
cons_matrix.getColumns() + index_range.start,
index_range.end - index_range.start };
}
SparseVectorView<REAL>
getColumnCoefficients( int c ) const
{
assert( c >= 0 && c < getNCols() );
auto index_range = cons_matrix_transp.getRowRanges()[c];
return SparseVectorView<REAL>{
cons_matrix_transp.getValues() + index_range.start,
cons_matrix_transp.getColumns() + index_range.start,
index_range.end - index_range.start };
}
REAL
getMaxFeasChange( int col, const REAL& val ) const
{
return abs( val * getColumnCoefficients( col ).getMaxAbsValue() );
}
Vec<REAL>&
getLeftHandSides()
{
return lhs_values;
}
Vec<REAL>&
getRightHandSides()
{
return rhs_values;
}
const Vec<REAL>&
getLeftHandSides() const
{
return lhs_values;
}
const Vec<REAL>&
getRightHandSides() const
{
return rhs_values;
}
const Vec<RowFlags>&
getRowFlags() const
{
return flags;
}
Vec<RowFlags>&
getRowFlags()
{
return flags;
}
template <bool infval = false>
void
modifyLeftHandSide( const int index, const Num<REAL>& 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 );
}
}
template <bool infval = false>
void
modifyRightHandSide( const int index, const Num<REAL>& 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 );
}
}
bool
isRowRedundant( const int row ) const
{
return flags[row].test( RowFlag::kRedundant );
}
std::pair<Vec<int>, Vec<int>>
compress( bool full = false );
void
deleteRowsAndCols( Vec<int>& deletedRows, Vec<int>& deletedCols,
Vec<RowActivity<REAL>>& activities,
Vec<int>& singletonRows, Vec<int>& singletonCols,
Vec<int>& emptyCols );
const Vec<int>&
getRowSizes() const
{
return rowsize;
}
Vec<int>&
getRowSizes()
{
return rowsize;
}
const Vec<int>&
getColSizes() const
{
return colsize;
}
Vec<int>&
getColSizes()
{
return colsize;
}
template <typename CoeffChanged>
void
changeCoefficients( const MatrixBuffer<REAL>& matrixBuffer,
Vec<int>& singletonRows, Vec<int>& singletonCols,
Vec<int>& emptyCols, Vec<RowActivity<REAL>>& activities,
CoeffChanged&& coeffChanged )
{
if( matrixBuffer.empty() )
return;
#ifdef PAPILO_TBB
tbb::parallel_invoke(
[&]() {
#endif
SmallVec<int, 32> buffer;
const MatrixEntry<REAL>* iter =
matrixBuffer.template begin<true>( 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<true>( 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<int, 32> buffer2;
const MatrixEntry<REAL>* iter2 =
matrixBuffer.template begin<false>( 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<false>( 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;
}
colsize[col] = newsize;
}
}
#ifdef PAPILO_TBB
} );
#endif
}
template <bool transpose = false>
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;
}
}
bool
checkAggregationSparsityCondition( int col,
const SparseVectorView<REAL>& equalityLHS,
int maxfillin, int maxshiftperrow,
Vec<int>& indbuffer );
int
sparsify( const Num<REAL>& num, int eqrow, const REAL& scale, int targetrow,
Vec<int>& intbuffer, Vec<REAL>& valbuffer,
const VariableDomains<REAL>& domains, Vec<int>& changedActivities,
Vec<RowActivity<REAL>>& activities, Vec<int>& singletonRows,
Vec<int>& singletonCols, Vec<int>& emptyCols, int presolveround );
void
aggregate( const Num<REAL>& num, int substituted_col, SparseVectorView<REAL> equalityLHS,
REAL equalityRHS, const VariableDomains<REAL>& domains,
Vec<int>& indbuffer, Vec<REAL>& valbuffer,
Vec<Triplet<REAL>>& tripletbuffer, Vec<int>& changedActivities,
Vec<RowActivity<REAL>>& activities, Vec<int>& singletonRows,
Vec<int>& singletonCols, Vec<int>& emptyCols, int presolveround );
const SparseStorage<REAL>&
getMatrixTranspose() const
{
return cons_matrix_transp;
}
SparseStorage<REAL>&
getMatrixTranspose()
{
return cons_matrix_transp;
}
const SparseStorage<REAL>&
getConstraintMatrix() const
{
return cons_matrix;
}
SparseStorage<REAL>&
getConstraintMatrix()
{
return cons_matrix;
}
template <typename Archive>
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:
SparseStorage<REAL> cons_matrix;
SparseStorage<REAL> cons_matrix_transp;
Vec<REAL> lhs_values;
Vec<REAL> rhs_values;
Vec<RowFlags> flags;
Vec<int> rowsize;
Vec<int> colsize;
};
#ifdef PAPILO_USE_EXTERN_TEMPLATES
extern template class ConstraintMatrix<double>;
extern template class ConstraintMatrix<Quad>;
extern template class ConstraintMatrix<Rational>;
#endif
template <typename REAL>
std::pair<Vec<int>, Vec<int>>
ConstraintMatrix<REAL>::compress( bool full )
{
std::pair<Vec<int>, Vec<int>> 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 <typename REAL>
void
ConstraintMatrix<REAL>::deleteRowsAndCols( Vec<int>& deletedRows,
Vec<int>& deletedCols,
Vec<RowActivity<REAL>>& activities,
Vec<int>& singletonRows,
Vec<int>& singletonCols,
Vec<int>& emptyCols )
{
if( deletedRows.empty() && deletedCols.empty() )
return;
int* rowcols = cons_matrix.getColumns();
IndexRange* rowranges = cons_matrix.getRowRanges();
REAL* rowvalues = cons_matrix.getValues();
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
#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
},
[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( colsize[col] == -1 ||
colsize[col] == colranges[col].end - colranges[col].start )
continue;
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 )
{
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( rowsize[row] == -1 ||
rowsize[row] == rowranges[row].end - rowranges[row].start )
continue;
switch( rowsize[row] )
{
case 0:
activities[row].min = 0;
activities[row].max = 0;
break;
case 1:
singletonRows.push_back( row );
}
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 <typename REAL>
bool
ConstraintMatrix<REAL>::checkAggregationSparsityCondition(
int col, const SparseVectorView<REAL>& equalityLHS, int maxfillin,
int maxshiftperrow, Vec<int>& 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 <typename REAL>
int
ConstraintMatrix<REAL>::sparsify(
const Num<REAL>& num, int eqrow, const REAL& scale, int targetrow,
Vec<int>& intbuffer, Vec<REAL>& valbuffer,
const VariableDomains<REAL>& domains, Vec<int>& changedActivities,
Vec<RowActivity<REAL>>& activities, Vec<int>& singletonRows,
Vec<int>& singletonCols, Vec<int>& 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;
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 );
}
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 ) );
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];
if( !flags[targetrow].test( RowFlag::kLhsInf, RowFlag::kRhsInf,
RowFlag::kEquation ) &&
lhs_values[targetrow] == rhs_values[targetrow] )
flags[targetrow].set( RowFlag::kEquation );
}
auto updateActivity = [&]( int row, int col, REAL oldval, REAL newval ) {
auto activityChange = [row, presolveround, &changedActivities](
ActivityChange actChange,
RowActivity<REAL>& 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<REAL>& 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 <typename REAL>
void
ConstraintMatrix<REAL>::aggregate(
const Num<REAL>& num, int substituted_col, SparseVectorView<REAL> equalityLHS,
REAL equalityRHS, const VariableDomains<REAL>& domains, Vec<int>& indbuffer,
Vec<REAL>& valbuffer, Vec<Triplet<REAL>>& tripletbuffer,
Vec<int>& changedActivities, Vec<RowActivity<REAL>>& activities,
Vec<int>& singletonRows, Vec<int>& singletonCols, Vec<int>& 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<REAL>& 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<REAL>& 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();
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] ) );
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; } ) );
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;
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;
}
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();
}
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() );
}
}
#endif