/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* */
/* 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_MISC_DEPENDENT_ROWS_HPP_
#define _PAPILO_MISC_DEPENDENT_ROWS_HPP_
#include "papilo/Config.hpp"
#ifdef PAPILO_HAVE_LUSOL
extern "C"
{
#include "papilo/external/lusol/clusol.h"
}
#endif
#include "papilo/core/ConstraintMatrix.hpp"
#include "papilo/core/SparseStorage.hpp"
#include "papilo/misc/Vec.hpp"
#include
#include
#include
namespace papilo
{
template
class DependentRows
{
public:
#ifdef PAPILO_HAVE_LUSOL
constexpr static bool Enabled = true;
#else
constexpr static bool Enabled = false;
#endif
DependentRows( int64_t nrows_, int64_t ncols_, int64_t maxnnz_ )
{
this->nrows = nrows_;
this->ncols = ncols_ + 1;
mat.reserve( maxnnz_ );
}
void
reset( int64_t nrows_, int64_t ncols_, int64_t maxnnz )
{
this->nrows = nrows_;
this->ncols = ncols_ + 1;
mat.clear();
mat.reserve( maxnnz );
}
void
addRow( int rowIndex, SparseVectorView rowValues, REAL side )
{
int len = rowValues.getLength();
const int* inds = rowValues.getIndices();
const REAL* vals = rowValues.getValues();
mat.startBadge();
for( int i = 0; i != len; ++i )
mat.addBadgeEntry( rowIndex, inds[i], vals[i] );
if( side != 0 )
mat.addBadgeEntry( rowIndex, this->ncols - 1, side );
mat.finishBadge();
}
struct PivotCandidate
{
int idx;
int colsize;
int rowsize;
bool
operator<( const PivotCandidate& other ) const
{
int trivial = colsize == 1 || rowsize == 1;
int othertrivial = other.colsize == 1 || other.rowsize == 1;
return std::make_tuple( trivial, colsize, rowsize ) >
std::make_tuple( othertrivial, other.colsize, other.rowsize );
}
};
struct LUSOL_Input
{
int64_t nrows;
int64_t ncols;
Vec A;
Vec indc;
Vec indr;
void
setSize( int64_t _nrows, int64_t _ncols, int64_t nnz )
{
this->nrows = _nrows;
this->ncols = _ncols;
// preallocate enough storage for LUSOL to work
int64_t alloc =
std::max( { 8 * nnz, 32 * nrows, 32 * ncols, int64_t{ 10000 } } );
A.reserve( alloc );
indc.reserve( alloc );
indr.reserve( alloc );
}
void
addNnz( int64_t i, int64_t j, double val )
{
A.push_back( double( val ) );
indc.push_back( i );
indr.push_back( j );
}
void
applyScaling()
{
// apply equilibrium scaling algorithm for remaining matrix
Vec rowmax( nrows );
Vec rowmin( nrows );
Vec colmax( ncols );
Vec colmin( ncols );
for( int i = 0; i < (int) A.size(); ++i )
{
double absai = abs( A[i] );
rowmax[indc[i] - 1] = std::max( rowmax[indc[i] - 1], absai );
colmax[indr[i] - 1] = std::max( colmax[indr[i] - 1], absai );
if( rowmin[indc[i] - 1] == 0 || absai < rowmin[indc[i] - 1] )
rowmin[indc[i] - 1] = absai;
if( colmin[indr[i] - 1] == 0 || absai < colmin[indr[i] - 1] )
colmin[indr[i] - 1] = absai;
}
double maxrowratio = 1;
for( int i = 0; i < (int) rowmax.size(); ++i )
maxrowratio = std::max( maxrowratio, rowmax[i] / rowmin[i] );
double maxcolratio = 1;
for( int i = 0; i < (int) colmax.size(); ++i )
maxcolratio = std::max( maxcolratio, colmax[i] / colmin[i] );
if( maxrowratio < maxcolratio )
{
for( int i = 0; i < (int) A.size(); ++i )
A[i] = ( A[i] / rowmax[indc[i] - 1] );
for( int i = 0; i < (int) colmax.size(); ++i )
colmax[i] = 0;
for( int i = 0; i < (int) A.size(); ++i )
colmax[indr[i] - 1] =
std::max( colmax[indr[i] - 1], abs( A[i] ) );
for( int i = 0; i < (int) A.size(); ++i )
A[i] = ( A[i] / colmax[indr[i] - 1] );
}
else
{
for( int i = 0; i < (int) A.size(); ++i )
A[i] = ( A[i] / colmax[indr[i] - 1] );
for( int i = 0; i < (int) rowmax.size(); ++i )
rowmax[i] = 0;
for( int i = 0; i < (int) A.size(); ++i )
rowmax[indc[i] - 1] =
std::max( rowmax[indc[i] - 1], abs( A[i] ) );
for( int i = 0; i < (int) A.size(); ++i )
A[i] = ( A[i] / rowmax[indc[i] - 1] );
}
}
void
computeDependentColumns( Vec& colmapping )
{
#ifdef PAPILO_HAVE_LUSOL
// is passed in : int64_t nelem;
std::array luparm;
std::array parmlu;
Vec p( nrows ); // allocate for nrows (=m)
Vec lenr( nrows ); // allocate for nrows
Vec locr( nrows ); // allocate for nrows
Vec iqloc( nrows ); // allocate for nrows
Vec ipinv( nrows ); // allocate for nrows
Vec q( ncols ); // allocate for ncols (=n)
Vec lenc( ncols ); // allocate for ncols
Vec locc( ncols ); // allocate for ncols
Vec iploc( ncols ); // allocate for ncols
Vec iqinv( ncols ); // allocate for ncols
Vec w( ncols );
int64_t inform;
double factol = 2.5; // Stability tolerance
luparm[0] = 6; // File number for printed messages
luparm[1] = -1; // Print level. >= 0 to get singularity info.
// >=10 to get more LU statistics.
// >=50 to get info on each pivot.
luparm[2] = 5; // maxcol
luparm[5] = 1; // Threshold Pivoting: 0 = TPP, 1 = TRP, 2 = TCP
luparm[7] = 0; // keepLU
parmlu[0] = factol; // Ltol1: max |Lij| during Factor
parmlu[1] = factol; // Ltol2: max |Lij| during Update
parmlu[2] = 3.0e-13; // small: drop tolerance
parmlu[3] = 3.7e-11; // Utol1: absolute tol for small Uii
parmlu[4] = 3.7e-11; // Utol2: relative tol for small Uii
parmlu[5] = 3.0; // Uspace:
parmlu[6] = 0.3; // dens1
parmlu[7] = 0.5; // dens2
int64_t nelem = A.size();
int64_t lena = A.capacity();
clu1fac( &nrows, &ncols, &nelem, &lena, luparm.data(), parmlu.data(),
A.data(), indc.data(), indr.data(), p.data(), q.data(),
lenc.data(), lenr.data(), locc.data(), locr.data(),
iploc.data(), iqloc.data(), ipinv.data(), iqinv.data(),
w.data(), &inform );
if( ( inform == 0 || inform == 1 ) && luparm[10] > 0 )
{
for( int i = 0; i < ncols; ++i )
{
if( w[i] > 0 )
colmapping[i] = -1;
}
colmapping.erase(
std::remove( colmapping.begin(), colmapping.end(), -1 ),
colmapping.end() );
assert( colmapping.size() == luparm[10] );
return;
}
#endif
colmapping.clear();
}
};
int64_t
preprocessLUFac( const Message& msg, const Num& num,
LUSOL_Input& lusolInput, Vec& rowmapping )
{
SmallVec stack;
SmallVec stack2;
Vec rowsize( nrows );
Vec colsize( ncols );
for( int i = 1; i != (int) mat.entries.size(); ++i )
{
assert( mat.entries[i].row < nrows );
assert( mat.entries[i].col < ncols );
assert( mat.entries[i].val != 0 );
++rowsize[mat.entries[i].row];
++colsize[mat.entries[i].col];
}
#ifndef NDEBUG
for( int i = 0; i != (int) colsize.size(); ++i )
{
int tmpcolsize = 0;
for( auto coliter = mat.template beginStart( stack, -1, i );
coliter->col == i; coliter = mat.template next( stack ) )
++tmpcolsize;
assert( tmpcolsize == colsize[i] );
}
for( int i = 0; i != (int) rowsize.size(); ++i )
{
int tmprowsize = 0;
for( auto rowiter = mat.template beginStart( stack, i, -1 );
rowiter->row == i; rowiter = mat.template next( stack ) )
++tmprowsize;
assert( tmprowsize == rowsize[i] );
}
#endif
boost::heap::d_ary_heap,
boost::heap::arity<4>>
heap;
heap.reserve( 2 * mat.getNnz() );
for( int i = 1; i != (int) mat.entries.size(); ++i )
{
PivotCandidate p{ i, colsize[mat.entries[i].col],
rowsize[mat.entries[i].row] };
heap.push( p );
}
int nremoved = 0;
REAL minpivot = num.getFeasTol() * 1e4;
while( !heap.empty() )
{
PivotCandidate pivot = heap.top();
heap.pop();
MatrixEntry* pivotentry = &mat.entries[pivot.idx];
// if pivot is zero we skip it
if( pivotentry->val == 0 )
continue;
bool trivialpivot =
colsize[pivotentry->col] == 1 || rowsize[pivotentry->row] == 1;
// always accept trivial pivots, but if the pivot is non-trivial do
// some additional checks
if( !trivialpivot )
{
if( abs( pivotentry->val ) < minpivot )
continue;
// if the pivot was altered while in the heap and is not as good as
// it was when added, then we add it to the heap for later
if( pivot.colsize < colsize[pivotentry->col] ||
pivot.rowsize < rowsize[pivotentry->row] )
{
pivot.colsize = colsize[pivotentry->col];
pivot.rowsize = rowsize[pivotentry->row];
heap.push( pivot );
continue;
}
// only treat columns of at most size 2
if( pivot.colsize > 2 )
break;
}
assert( colsize[pivotentry->col] > 0 );
assert( rowsize[pivotentry->row] > 0 );
nremoved++;
int col = pivotentry->col;
int pivotrow = pivotentry->row;
REAL pivotrowscale = -1.0 / pivotentry->val;
if( !trivialpivot )
{
for( const MatrixEntry* rowiter =
mat.template beginStart( stack, pivotrow, -1 );
rowiter->row == pivotrow;
rowiter = mat.template next( stack ) )
{
if( rowiter->col == col || rowiter->val == 0 )
continue;
REAL pivrowval = rowiter->val * pivotrowscale;
for( const MatrixEntry* coliter =
mat.template beginStart( stack2, -1, col );
coliter->col == col;
coliter = mat.template next( stack2 ) )
{
if( coliter->row == pivotrow || coliter->val == 0 )
continue;
const MatrixEntry* rowentry =
mat.template findEntry( coliter->row,
rowiter->col );
REAL delta = pivrowval * coliter->val;
if( rowentry != nullptr )
{
REAL newval = rowentry->val + delta;
if( rowentry->val == 0 )
{
++colsize[rowentry->col];
++rowsize[rowentry->row];
}
assert( rowiter->col != col ||
( num.isZero( newval ) && rowentry->val != 0 ) );
if( num.isZero( newval ) )
{
const_cast*>( rowentry )->val = 0;
--colsize[rowentry->col];
--rowsize[rowentry->row];
}
else
{
const_cast*>( rowentry )->val =
newval;
int rowentryidx = ( rowentry - &mat.entries[0] );
assert( rowentryidx > 0 &&
rowentryidx < (int) mat.entries.size() &&
rowentry == &mat.entries[rowentryidx] );
heap.push( PivotCandidate{ rowentryidx,
colsize[rowentry->col],
rowsize[rowentry->row] } );
}
}
else
{
++colsize[rowiter->col];
++rowsize[coliter->row];
int rowentryidx = int( mat.entries.size() );
heap.push( PivotCandidate{ rowentryidx,
colsize[rowiter->col],
rowsize[coliter->row] } );
int tmpcoliteridx = ( coliter - &mat.entries[0] );
int tmprowiteridx = ( rowiter - &mat.entries[0] );
mat.addEntry( coliter->row, rowiter->col, delta );
coliter = &mat.entries[tmpcoliteridx];
rowiter = &mat.entries[tmprowiteridx];
}
}
}
}
for( const MatrixEntry* coliter =
mat.template beginStart( stack, -1, col );
coliter->col == col; coliter = mat.template next( stack ) )
{
if( coliter->val == 0 )
continue;
--rowsize[coliter->row];
--colsize[coliter->col];
const_cast*>( coliter )->val = 0;
}
for( const MatrixEntry* rowiter =
mat.template beginStart( stack, pivotrow, -1 );
rowiter->row == pivotrow;
rowiter = mat.template next( stack ) )
{
if( rowiter->val == 0 )
continue;
--rowsize[rowiter->row];
--colsize[rowiter->col];
const_cast*>( rowiter )->val = 0;
}
assert( rowsize[pivotrow] == 0 );
assert( colsize[col] == 0 );
rowsize[pivotrow] = -1;
colsize[col] = -1;
}
int64_t remainingnnz = 0;
for( int i = 0; i < (int) rowsize.size(); ++i )
{
if( rowsize[i] != -1 )
{
rowmapping.push_back( i );
remainingnnz += rowsize[i];
rowsize[i] = rowmapping.size();
}
}
msg.info( "preprocessed LU factor has {} nonzeros, preprocessing removed "
"{} rows/cols\n",
remainingnnz, nremoved );
if( remainingnnz == 0 )
return remainingnnz;
nrows = rowmapping.size();
ncols = 0;
for( int i = 0; i < (int) colsize.size(); ++i )
{
if( colsize[i] != -1 )
{
++ncols;
colsize[i] = ncols;
}
}
// add data to lusol transposed
lusolInput.setSize( ncols, nrows, remainingnnz );
for( int i = 1; i != (int) mat.entries.size(); ++i )
{
if( mat.entries[i].val == 0 )
continue;
lusolInput.addNnz( colsize[mat.entries[i].col],
rowsize[mat.entries[i].row],
double( mat.entries[i].val ) );
}
assert( remainingnnz == (int) lusolInput.A.size() );
return remainingnnz;
}
Vec
getDependentRows( const Message& msg, const Num& num )
{
Vec rowmapping;
LUSOL_Input lusolInput;
if( !DependentRows::Enabled )
return rowmapping;
int64_t nelem = preprocessLUFac( msg, num, lusolInput, rowmapping );
// no remaining nonzeros means all remaining rows are redundant
if( nelem > 0 )
{
lusolInput.applyScaling();
msg.info( "calling LUSOL on remaining factor\n" );
lusolInput.computeDependentColumns( rowmapping );
}
return rowmapping;
}
private:
int64_t nrows;
int64_t ncols;
MatrixBuffer mat;
};
} // namespace papilo
#endif