/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* */
/* 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_SINGLE_ROW_HPP_
#define _PAPILO_CORE_SINGLE_ROW_HPP_
#include "papilo/core/RowFlags.hpp"
#include "papilo/core/VariableDomains.hpp"
#include "papilo/misc/Flags.hpp"
#include "papilo/misc/Num.hpp"
#include "papilo/misc/Vec.hpp"
#include
namespace papilo
{
enum class BoundChange
{
kLower,
kUpper
};
enum class ActivityChange
{
kMin,
kMax
};
enum class RowStatus
{
kInfeasible,
kRedundant,
kRedundantLhs,
kRedundantRhs,
kUnknown,
};
template
struct RowActivity
{
/// minimal activity of the row
REAL min;
/// maximal activity of the row
REAL max;
/// number of variables that contribute with an infinite bound to the minimal
/// activity of this row
int ninfmin;
/// number of variables that contribute with an infinite bound to the maximal
/// activity of this row
int ninfmax;
/// last presolving round where this activity changed
int lastchange;
bool
repropagate( ActivityChange actChange, RowFlags rflags )
{
if( actChange == ActivityChange::kMin &&
!rflags.test( RowFlag::kRhsInf ) && ninfmin <= 1 )
return true;
if( actChange == ActivityChange::kMax &&
!rflags.test( RowFlag::kLhsInf ) && ninfmax <= 1 )
return true;
return false;
}
RowStatus
checkStatus( const Num& num, RowFlags rflags, const REAL& lhs,
const REAL& rhs ) const
{
RowStatus status = RowStatus::kRedundant;
if( !rflags.test( RowFlag::kLhsInf ) )
{
if( ninfmax == 0 && num.isFeasLT( max, lhs ) &&
num.isSafeLT( max, lhs ) )
return RowStatus::kInfeasible;
if( ninfmin == 0 && num.isFeasGE( min, lhs ) )
status = RowStatus::kRedundantLhs;
else
status = RowStatus::kUnknown;
}
if( !rflags.test( RowFlag::kRhsInf ) )
{
if( ninfmin == 0 && num.isFeasGT( min, rhs ) &&
num.isSafeGT( min, rhs ) )
return RowStatus::kInfeasible;
if( ninfmax == 0 && num.isFeasLE( max, rhs ) )
{
if( status == RowStatus::kUnknown )
status = RowStatus::kRedundantRhs;
else
status = RowStatus::kRedundant;
}
else if( status == RowStatus::kRedundant )
status = RowStatus::kUnknown;
}
else if( status == RowStatus::kRedundantLhs )
status = RowStatus::kRedundant;
return status;
}
template
void
serialize( Archive& ar, const unsigned int version )
{
ar& min;
ar& max;
ar& ninfmin;
ar& ninfmax;
ar& lastchange;
}
RowActivity() = default;
};
/// counts the locks for the given row entry
template
void
count_locks( const REAL& val, RowFlags rflags, int& ndownlocks, int& nuplocks )
{
assert( val != 0 );
if( val < 0 )
{
if( !rflags.test( RowFlag::kLhsInf ) )
++nuplocks;
if( !rflags.test( RowFlag::kRhsInf ) )
++ndownlocks;
}
else
{
if( !rflags.test( RowFlag::kLhsInf ) )
++ndownlocks;
if( !rflags.test( RowFlag::kRhsInf ) )
++nuplocks;
}
}
template
RowActivity
compute_row_activity( const REAL* rowvals, const int* colindices, int rowlen,
const Vec& lower_bounds,
const Vec& upper_bounds, const Vec& flags,
int presolveround = -1 )
{
RowActivity activity;
activity.min = 0.0;
activity.max = 0.0;
activity.ninfmin = 0;
activity.ninfmax = 0;
activity.lastchange = presolveround;
for( int j = 0; j < rowlen; ++j )
{
int col = colindices[j];
if( !flags[col].test( ColFlag::kUbUseless ) )
{
if( rowvals[j] < 0 )
activity.min += rowvals[j] * upper_bounds[col];
else
activity.max += rowvals[j] * upper_bounds[col];
}
else
{
assert( flags[col].test( ColFlag::kUbUseless ) );
if( rowvals[j] < 0 )
++activity.ninfmin;
else
++activity.ninfmax;
}
if( !flags[col].test( ColFlag::kLbUseless ) )
{
if( rowvals[j] < 0 )
activity.max += rowvals[j] * lower_bounds[col];
else
activity.min += rowvals[j] * lower_bounds[col];
}
else
{
assert( flags[col].test( ColFlag::kLbUseless ) );
if( rowvals[j] < 0 )
++activity.ninfmax;
else
++activity.ninfmin;
}
}
return activity;
}
template
REAL
compute_minimal_row_activity( const REAL* rowvals, const int* colindices, int rowlen,
const Vec& lower_bounds,
const Vec& upper_bounds, const Vec& flags)
{
REAL min = 0.0;
for( int j = 0; j < rowlen; ++j )
{
int col = colindices[j];
if( !flags[col].test( ColFlag::kUbUseless ) && rowvals[j] < 0 )
min += rowvals[j] * upper_bounds[col];
if( !flags[col].test( ColFlag::kLbUseless ) && rowvals[j] > 0 )
min += rowvals[j] * lower_bounds[col];
}
return min ;
}
template
REAL
compute_maximal_row_activity( const REAL* rowvals, const int* colindices, int rowlen,
const Vec& lower_bounds,
const Vec& upper_bounds, const Vec& flags)
{
REAL max = 0.0;
for( int j = 0; j < rowlen; ++j )
{
int col = colindices[j];
if( !flags[col].test( ColFlag::kUbUseless ) && rowvals[j] > 0 )
max += rowvals[j] * upper_bounds[col];
if( !flags[col].test( ColFlag::kLbUseless ) && rowvals[j] < 0 )
max += rowvals[j] * lower_bounds[col];
}
return max;
}
/// update the vector of row activities after lower or upper bounds of a column
/// changed. The last argument must be callable with arguments (ActivityChange,
/// rowid, RowActivity) and is called to inform about row activities that
/// changed
template
ActivityChange
update_activity_after_boundchange( const REAL& colval, BoundChange type,
const REAL& oldbound, const REAL& newbound,
bool oldbound_inf,
RowActivity& activity )
{
assert( oldbound_inf ||
( type == BoundChange::kLower && newbound != oldbound ) ||
( type == BoundChange::kUpper && newbound != oldbound ) );
if( type == BoundChange::kLower )
{
if( colval < REAL{ 0.0 } )
{
if( oldbound_inf )
{
assert( activity.ninfmax > 0 );
--activity.ninfmax;
activity.max += newbound * colval;
}
else
{
activity.max += ( newbound - oldbound ) * colval;
}
return ActivityChange::kMax;
}
else
{
if( oldbound_inf )
{
assert( activity.ninfmin > 0 );
--activity.ninfmin;
activity.min += newbound * colval;
}
else
{
activity.min += ( newbound - oldbound ) * colval;
}
return ActivityChange::kMin;
}
}
else
{
if( colval < REAL{ 0.0 } )
{
if( oldbound_inf )
{
assert( activity.ninfmin > 0 );
--activity.ninfmin;
activity.min += newbound * colval;
}
else
{
activity.min += ( newbound - oldbound ) * colval;
}
return ActivityChange::kMin;
}
else
{
if( oldbound_inf )
{
assert( activity.ninfmax > 0 );
--activity.ninfmax;
activity.max += newbound * colval;
}
else
{
activity.max += ( newbound - oldbound ) * colval;
}
return ActivityChange::kMax;
}
}
}
/// update the vector of row activities after removing a finite lower or upper
/// bound of a column
template
void
update_activities_remove_finite_bound( const int* colinds, const REAL* colvals,
int collen, BoundChange type,
const REAL& oldbound,
Vec>& activities )
{
if( type == BoundChange::kLower )
{
for( int i = 0; i != collen; ++i )
{
const REAL& colval = colvals[i];
RowActivity& activity = activities[colinds[i]];
if( colval < REAL{ 0.0 } )
{
activity.max -= oldbound * colval;
++activity.ninfmax;
}
else
{
activity.min -= oldbound * colval;
++activity.ninfmin;
}
}
}
else
{
for( int i = 0; i != collen; ++i )
{
const REAL& colval = colvals[i];
RowActivity& activity = activities[colinds[i]];
if( colval < REAL{ 0.0 } )
{
activity.min -= oldbound * colval;
++activity.ninfmin;
}
else
{
activity.max -= oldbound * colval;
++activity.ninfmax;
}
}
}
}
/// update the vector of row activities after lower or upper bounds of a column
/// changed. The last argument must be callable with arguments (ActivityChange,
/// rowid, RowActivity) and is called to inform about row activities that
/// changed
template
void
update_activities_after_boundchange( const REAL* colvals, const int* colrows,
int collen, BoundChange type,
REAL oldbound, REAL newbound,
bool oldbound_inf,
Vec>& activities,
ACTIVITYCHANGE&& activityChange,
bool watchInfiniteActivities = false )
{
assert( oldbound_inf ||
( type == BoundChange::kLower && newbound != oldbound ) ||
( type == BoundChange::kUpper && newbound != oldbound ) );
for( int i = 0; i < collen; ++i )
{
RowActivity& activity = activities[colrows[i]];
ActivityChange actChange = update_activity_after_boundchange(
colvals[i], type, oldbound, newbound, oldbound_inf, activity );
if( actChange == ActivityChange::kMin &&
( activity.ninfmin == 0 || watchInfiniteActivities ) )
activityChange( ActivityChange::kMin, colrows[i], activity );
if( actChange == ActivityChange::kMax &&
( activity.ninfmax == 0 || watchInfiniteActivities ) )
activityChange( ActivityChange::kMax, colrows[i], activity );
}
}
/**
* updates the row activity for a changed coefficient in the matrix.
* In case that the difference between the old and new coefficient is large,
* the activity is recalculated entirely to prevent numerical difficulties.
* The last argument must be callable with arguments (ActivityChange,
* RowActivity) and is called to inform about row activities that changed.
* @tparam REAL
* @tparam ACTIVITYCHANGE
* @param collb
* @param colub
* @param cflags
* @param oldcolcoef
* @param newcolcoef
* @param activity
* @param rowLength
* @param colindices
* @param rowvals
* @param domains
* @param num
* @param activityChange
*/
template
void
update_activity_after_coeffchange( REAL collb, REAL colub, ColFlags cflags,
REAL oldcolcoef, REAL newcolcoef,
RowActivity& activity,
int rowLength, const int* colindices,
const REAL* rowvals,
const VariableDomains& domains,
const Num num,
ACTIVITYCHANGE&& activityChange )
{
assert( oldcolcoef != newcolcoef );
if( oldcolcoef * newcolcoef <= 0.0 )
{ // the sign of the coefficient flipped, so the column bounds now contribute
// to the opposite activity bound
// remember old activity
RowActivity oldactivity = activity;
if( oldcolcoef != 0.0 )
{ // if the old coefficient was not 0.0 we remove its contributions to the
// minimum and maximum activity
// remove old contributions of the lower bound
if( cflags.test( ColFlag::kLbUseless ) )
{
if( oldcolcoef < 0.0 )
--activity.ninfmax;
else
--activity.ninfmin;
}
else
{
if( oldcolcoef < 0.0 )
activity.max -= oldcolcoef * collb;
else
activity.min -= oldcolcoef * collb;
}
// remove old contributions of the upper bound
if( cflags.test( ColFlag::kUbUseless ) )
{
if( oldcolcoef < 0.0 )
--activity.ninfmin;
else
--activity.ninfmax;
}
else
{
if( oldcolcoef < 0.0 )
activity.min -= oldcolcoef * colub;
else
activity.max -= oldcolcoef * colub;
}
}
if( newcolcoef != 0.0 )
{ // if the new coefficient is not 0.0 we add its contributions to the
// minimum and maximum activity
// add new contributions of the lower bound
if( cflags.test( ColFlag::kLbUseless ) )
{
if( newcolcoef < 0.0 )
++activity.ninfmax;
else
++activity.ninfmin;
}
else
{
if( newcolcoef < 0.0 )
activity.max += newcolcoef * collb;
else
activity.min += newcolcoef * collb;
}
// addnewold contributions of the upper bound
if( cflags.test( ColFlag::kUbUseless ) )
{
if( newcolcoef < 0.0 )
++activity.ninfmin;
else
++activity.ninfmax;
}
else
{
if( newcolcoef < 0.0 )
activity.min += newcolcoef * colub;
else
activity.max += newcolcoef * colub;
}
}
if( ( oldactivity.ninfmin != 0 && activity.ninfmin == 0 ) ||
( oldactivity.ninfmin == 0 && activity.ninfmin == 0 &&
oldactivity.min != activity.min ) )
activityChange( ActivityChange::kMin, activity );
if( ( oldactivity.ninfmax != 0 && activity.ninfmax == 0 ) ||
( oldactivity.ninfmax == 0 && activity.ninfmax == 0 &&
oldactivity.max != activity.max ) )
activityChange( ActivityChange::kMax, activity );
}
else
{ // the sign of the coefficient did not flip, so the column bounds still
// contribute to the same activity bound
bool isDifferenceHugeVal = num.isHugeVal( newcolcoef - oldcolcoef );
if( !cflags.test( ColFlag::kLbUseless ) && collb != 0.0 )
{
if( newcolcoef < REAL{ 0.0 } )
{
if( isDifferenceHugeVal )
activity.max = compute_maximal_row_activity(
rowvals, colindices, rowLength, domains.lower_bounds,
domains.upper_bounds, domains.flags );
else
activity.max += collb * ( newcolcoef - oldcolcoef );
if( activity.ninfmax == 0 )
activityChange( ActivityChange::kMax, activity );
}
else
{
if( isDifferenceHugeVal )
activity.min = compute_minimal_row_activity(
rowvals, colindices, rowLength, domains.lower_bounds,
domains.upper_bounds, domains.flags );
else
activity.min += collb * ( newcolcoef - oldcolcoef );
if( activity.ninfmin == 0 )
activityChange( ActivityChange::kMin, activity );
}
}
if( !cflags.test( ColFlag::kUbUseless ) && colub != 0.0 )
{
if( newcolcoef < REAL{ 0.0 } )
{
if( isDifferenceHugeVal )
activity.min = compute_minimal_row_activity(
rowvals, colindices, rowLength, domains.lower_bounds,
domains.upper_bounds, domains.flags );
else
activity.min += colub * ( newcolcoef - oldcolcoef );
if( activity.ninfmin == 0 )
activityChange( ActivityChange::kMin, activity );
}
else
{
if( isDifferenceHugeVal )
activity.max = compute_maximal_row_activity(
rowvals, colindices, rowLength, domains.lower_bounds,
domains.upper_bounds, domains.flags );
else
activity.max += colub * ( newcolcoef - oldcolcoef );
if( activity.ninfmax == 0 )
activityChange( ActivityChange::kMax, activity );
}
}
}
}
/// propagate domains of variables using the given a row and its activity. The
/// last argument must be callable with arguments (BoundChange, colid, newbound, row)
/// and is called to inform about column bounds that changed.
template
void
propagate_row( int row, const REAL* rowvals, const int* colindices, int rowlen,
const RowActivity& activity, REAL lhs, REAL rhs,
RowFlags rflags, const Vec& lower_bounds,
const Vec& upper_bounds, const Vec& domainFlags,
BOUNDCHANGE&& boundchange )
{
bool adj_rhs = false;
if( activity.ninfmin == 1 && activity.ninfmax == 0 &&
rflags.test( RowFlag::kRhsInf ) )
{
adj_rhs = true;
rhs = activity.max;
}
if( ( !rflags.test( RowFlag::kRhsInf ) && activity.ninfmin <= 1 ) ||
adj_rhs )
{
for( int j = 0; j < rowlen; ++j )
{
int col = colindices[j];
REAL lb = lower_bounds[col];
REAL ub = upper_bounds[col];
REAL minresact = activity.min;
REAL val = rowvals[j];
if( val < REAL{ 0.0 } )
{
if( activity.ninfmin == 1 )
{
if( !domainFlags[col].test( ColFlag::kUbUseless ) )
continue;
j = rowlen;
}
else
{
assert( !domainFlags[col].test( ColFlag::kUbUseless ) );
minresact -= val * ub;
}
REAL newlb = ( rhs - minresact ) / val;
if( domainFlags[col].test( ColFlag::kLbInf ) || newlb > lb )
boundchange( BoundChange::kLower, col, newlb, row );
}
else
{
if( activity.ninfmin == 1 )
{
if( !domainFlags[col].test( ColFlag::kLbUseless ) )
continue;
j = rowlen;
}
else
{
assert( !domainFlags[col].test( ColFlag::kLbUseless ) );
minresact -= val * lb;
}
REAL newub = ( rhs - minresact ) / val;
if( domainFlags[col].test( ColFlag::kUbInf ) || newub < ub )
boundchange( BoundChange::kUpper, col, newub, row );
}
}
}
bool adj_lhs = false;
if( activity.ninfmax == 1 && activity.ninfmin == 0 &&
rflags.test( RowFlag::kLhsInf ) )
{
adj_lhs = true;
lhs = activity.min;
}
if( ( !rflags.test( RowFlag::kLhsInf ) && activity.ninfmax <= 1 ) ||
adj_lhs )
{
for( int j = 0; j < rowlen; ++j )
{
int col = colindices[j];
REAL lb = lower_bounds[col];
REAL ub = upper_bounds[col];
REAL maxresact = activity.max;
REAL val = rowvals[j];
if( val < REAL{ 0.0 } )
{
if( activity.ninfmax == 1 )
{
if( !domainFlags[col].test( ColFlag::kLbUseless ) )
continue;
j = rowlen;
}
else
{
assert( !domainFlags[col].test( ColFlag::kLbUseless ) );
maxresact -= val * lb;
}
REAL newub = ( lhs - maxresact ) / val;
if( domainFlags[col].test( ColFlag::kUbInf ) || newub < ub )
boundchange( BoundChange::kUpper, col, newub, row );
}
else
{
if( activity.ninfmax == 1 )
{
if( !domainFlags[col].test( ColFlag::kUbUseless ) )
continue;
j = rowlen;
}
else
{
assert( !domainFlags[col].test( ColFlag::kUbUseless ) );
maxresact -= val * ub;
}
REAL newlb = ( lhs - maxresact ) / val;
if( domainFlags[col].test( ColFlag::kLbInf ) || newlb > lb )
boundchange( BoundChange::kLower, col, newlb, row );
}
}
}
}
template
bool
row_implies_LB( const Num& num, REAL lhs, REAL rhs, RowFlags rflags,
const RowActivity& activity, REAL colcoef, REAL collb,
REAL colub, ColFlags cflags )
{
if( cflags.test( ColFlag::kLbInf ) )
return true;
REAL resact;
REAL side;
if( colcoef > 0.0 && !rflags.test( RowFlag::kLhsInf ) )
{
if( activity.ninfmax == 0 )
{
assert( !cflags.test( ColFlag::kUbUseless ) );
resact = activity.max - colub * colcoef;
}
else if( activity.ninfmax == 1 && cflags.test( ColFlag::kUbUseless ) )
resact = activity.max;
else
return false;
side = lhs;
}
else if( colcoef < 0.0 && !rflags.test( RowFlag::kRhsInf ) )
{
if( activity.ninfmin == 0 )
{
assert( !cflags.test( ColFlag::kUbUseless ) );
resact = activity.min - colub * colcoef;
}
else if( activity.ninfmin == 1 && cflags.test( ColFlag::kUbUseless ) )
resact = activity.min;
else
return false;
side = rhs;
}
else
return false;
return num.isFeasGE( ( side - resact ) / colcoef, collb );
}
template
bool
row_implies_UB( const Num& num, REAL lhs, REAL rhs, RowFlags rflags,
const RowActivity& activity, REAL colcoef, REAL collb,
REAL colub, ColFlags cflags )
{
if( cflags.test( ColFlag::kUbInf ) )
return true;
REAL resact;
REAL side;
if( colcoef > 0.0 && !rflags.test( RowFlag::kRhsInf ) )
{
if( activity.ninfmin == 0 )
{
assert( !cflags.test( ColFlag::kLbUseless ) );
resact = activity.min - collb * colcoef;
}
else if( activity.ninfmin == 1 && cflags.test( ColFlag::kLbUseless ) )
resact = activity.min;
else
return false;
side = rhs;
}
else if( colcoef < 0.0 && !rflags.test( RowFlag::kLhsInf ) )
{
if( activity.ninfmax == 0 )
{
assert( !cflags.test( ColFlag::kLbUseless ) );
resact = activity.max - collb * colcoef;
}
else if( activity.ninfmax == 1 && cflags.test( ColFlag::kLbUseless ) )
resact = activity.max;
else
return false;
side = lhs;
}
else
return false;
return num.isFeasLE( ( side - resact ) / colcoef, colub );
}
} // namespace papilo
#endif