/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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_PRIMAL_DUAL_SOL_VALIDATION_HPP_ #define _PAPILO_CORE_PRIMAL_DUAL_SOL_VALIDATION_HPP_ #include "papilo/core/Solution.hpp" #include "papilo/core/postsolve/PostsolveStatus.hpp" namespace papilo { template class PrimalDualSolValidation { public: PrimalDualSolValidation( const Message msg, const Num num_ ) : num( num_ ), message( msg ) { } private: Num num; Message message{}; private: bool checkLength( const Solution& solution, const Problem& problem ) { const int nCols = problem.getNCols(); bool primal_check = (int) solution.primal.size() != nCols; if( solution.type == SolutionType::kPrimalDual ) return primal_check || (int) solution.reducedCosts.size() != nCols || (int) solution.dual.size() != problem.getNRows(); return primal_check; } bool checkPrimalBounds( const Vec& primalSolution, const Problem& problem ) { bool failure = false; const Vec ub = problem.getUpperBounds(); const Vec lb = problem.getLowerBounds(); for( int col = 0; col < problem.getNCols(); col++ ) { if( problem.getColFlags()[col].test( ColFlag::kInactive ) ) continue; if( ( ! problem.getColFlags()[col].test( ColFlag::kLbInf ) ) && num.isFeasLT( primalSolution[col], lb[col] ) ) { message.info( "Column {:<3} violates lower column bound () ({} ! >= {}).\n", col, (double) primalSolution[col], (double) lb[col] ); failure = true; } if( ( ! problem.getColFlags()[col].test( ColFlag::kUbInf ) ) && num.isFeasGT( primalSolution[col], ub[col] ) ) { message.info( "Column {:<3} violates upper column bound ({} ! <= {}).\n", col, (double) primalSolution[col], (double) ub[col] ); failure = true; } } return failure; } bool checkPrimalConstraintAndUpdateSlack( Solution& solution, const Problem& problem ) const { const Vec rhs = problem.getConstraintMatrix().getRightHandSides(); const Vec lhs = problem.getConstraintMatrix().getLeftHandSides(); if(solution.type == SolutionType::kPrimalDual) { solution.slack.clear(); solution.slack.resize(problem.getNRows()); } for( int row = 0; row < problem.getNRows(); row++ ) { if( problem.getRowFlags()[row].test( RowFlag::kRedundant ) ) continue; REAL rowValue = 0; auto entries = problem.getConstraintMatrix().getRowCoefficients( row ); for( int j = 0; j < entries.getLength(); j++ ) { int col = entries.getIndices()[j]; if( problem.getColFlags()[col].test( ColFlag::kInactive ) ) continue; REAL x = entries.getValues()[j]; REAL primal = solution.primal[col]; rowValue += x * primal; } bool lhs_inf = problem.getRowFlags()[row].test( RowFlag::kLhsInf ); if( ( ! lhs_inf ) && num.isFeasLT( rowValue, lhs[row] ) ) { message.info( "Row {:<3} violates row bounds ({:<3} < {:<3}).\n", row, (double) lhs[row], (double) rowValue ); return true; } bool rhs_inf = problem.getRowFlags()[row].test( RowFlag::kRhsInf ); if( ( ! rhs_inf ) && num.isFeasGT( rowValue, rhs[row] ) ) { message.info( "Row {:<3} violates row bounds ({:<3} < {:<3}).\n", row, (double) rowValue, (double) rhs[row] ); return true; } if(solution.type == SolutionType::kPrimalDual) solution.slack[row] = num.isFeasZero( rowValue ) ? 0 : rowValue; } return false; } bool checkPrimalFeasibilityAndUpdateSlack( Solution& solution, const Problem& problem ) { bool primalBounds = checkPrimalBounds( solution.primal, problem ); bool primalConstraint = checkPrimalConstraintAndUpdateSlack( solution, problem ); return primalBounds || primalConstraint; } bool checkDualFeasibility( const Vec& primalSolution, const Vec& dualSolution, const Vec& reducedCosts, const Vec& basis, const Problem& problem ) { const Vec rhs = problem.getConstraintMatrix().getRightHandSides(); const Vec lhs = problem.getConstraintMatrix().getLeftHandSides(); for( int variable = 0; variable < problem.getNCols(); variable++ ) { if( problem.getColFlags()[variable].test( ColFlag::kInactive ) ) continue; StableSum colValue; auto coeff = problem.getConstraintMatrix().getColumnCoefficients( variable ); for( int counter = 0; counter < coeff.getLength(); counter++ ) { REAL value = coeff.getValues()[counter]; int rowIndex = coeff.getIndices()[counter]; colValue.add(dualSolution[rowIndex] * value); } if( ! num.isFeasEq( colValue.get() + reducedCosts[variable], problem.getObjective().coefficients[variable] ) ) { message.info( "Dual row {:<3} violates dual row bounds ({:<3} != {:<3}).\n", variable, (double)( colValue.get() + reducedCosts[variable] ), (double)problem.getObjective().coefficients[variable] ); return true; } } return false; } bool checkComplementarySlackness( const Vec& primalSolution, const Vec& dualSolution, const Vec& reducedCosts, const Problem& problem ) { const Vec lb = problem.getLowerBounds(); const Vec ub = problem.getUpperBounds(); const Vec rhs = problem.getConstraintMatrix().getRightHandSides(); const Vec lhs = problem.getConstraintMatrix().getLeftHandSides(); for( int row = 0; row < problem.getNRows(); row++ ) { if( problem.getRowFlags()[row].test( RowFlag::kRedundant ) ) continue; REAL rowValue = 0; auto entries = problem.getConstraintMatrix().getRowCoefficients( row ); for( int j = 0; j < entries.getLength(); j++ ) { int col = entries.getIndices()[j]; if( problem.getColFlags()[col].test( ColFlag::kFixed ) ) continue; rowValue += entries.getValues()[j] * primalSolution[col]; } if( ! problem.getRowFlags()[row].test( RowFlag::kLhsInf ) && ! problem.getRowFlags()[row].test( RowFlag::kRhsInf ) ) { if( num.isFeasGT( lhs[row], rowValue ) && num.isFeasLT( rhs[row], rowValue ) && ! num.isFeasZero( dualSolution[row] ) ) return true; } else if( ! problem.getRowFlags()[row].test( RowFlag::kLhsInf ) ) { assert( problem.getRowFlags()[row].test( RowFlag::kRhsInf ) ); if( num.isFeasGT( lhs[row], rowValue ) && ! num.isFeasZero( dualSolution[row] ) ) return true; } else if( ( ! problem.getRowFlags()[row].test( RowFlag::kLhsInf ) ) && num.isFeasGT( rowValue, lhs[row] ) ) { assert( problem.getRowFlags()[row].test( RowFlag::kRhsInf ) ); if( num.isFeasLT( rhs[row], rowValue ) && ! num.isFeasZero( dualSolution[row] ) ) return true; } } for( int col = 0; col < problem.getNCols(); col++ ) { if( problem.getColFlags()[col].test( ColFlag::kInactive ) ) continue; bool isLbInf = problem.getColFlags()[col].test( ColFlag::kLbInf ); bool isUbInf = problem.getColFlags()[col].test( ColFlag::kUbInf ); REAL upperBound = ub[col]; REAL lowerBound = lb[col]; REAL reducedCost = reducedCosts[col]; REAL sol = primalSolution[col]; if( num.isFeasEq( upperBound, lowerBound ) && ! isLbInf && ! isUbInf ) continue; if( ! isLbInf && ! isUbInf ) { if( num.isFeasGT( sol, lowerBound ) && num.isFeasLT( sol, upperBound ) && ! num.isFeasZero( reducedCost ) ) return true; } else if( ! isLbInf ) { assert( isUbInf ); if( num.isFeasGT( sol, lowerBound ) && ! num.isFeasZero( reducedCost ) ) return true; } else if( ! isUbInf ) { assert( isLbInf ); if( num.isFeasLT( sol, upperBound ) && ! num.isFeasZero( reducedCost ) ) return true; } } return false; } bool checkBasis( const Solution& solution, const Problem& problem ) { if(! solution.basisAvailabe) return false; int number_basic_variable = 0; int number_rows = 0; for( int variable = 0; variable < problem.getNCols(); variable++ ) { if( problem.getColFlags()[variable].test( ColFlag::kInactive ) ) continue; bool ub_infinity = problem.getColFlags()[variable].test( ColFlag::kUbInf ); bool lb_infinity = problem.getColFlags()[variable].test( ColFlag::kLbInf ); REAL lb = problem.getLowerBounds()[variable]; REAL ub = problem.getUpperBounds()[variable]; REAL sol = solution.primal[variable]; assert( ub_infinity || lb_infinity || num.isFeasGE( ub, lb ) ); switch( solution.varBasisStatus[variable] ) { case VarBasisStatus::BASIC: if( ! num.isFeasZero( solution.reducedCosts[variable] ) ) return true; number_basic_variable++; break; case VarBasisStatus::FIXED: if( ub_infinity || lb_infinity || ! num.isFeasEq( lb, ub ) || ! num.isFeasEq( sol, ub ) ) return true; break; case VarBasisStatus::ON_LOWER: if( lb_infinity || ! num.isFeasEq( sol, lb ) ) return true; break; case VarBasisStatus::ON_UPPER: if( ub_infinity || ! num.isFeasEq( sol, ub ) ) return true; break; case VarBasisStatus::ZERO: if( ! lb_infinity || ! ub_infinity || ! num.isFeasZero( sol ) ) return true; break; case VarBasisStatus::UNDEFINED: return true; } } for( int row = 0; row < problem.getNRows(); row++ ) { if( problem.getRowFlags()[row].test( RowFlag::kRedundant ) ) continue; number_rows++; bool lhs_infinity = problem.getRowFlags()[row].test( RowFlag::kLhsInf ); bool rhs_infinity = problem.getRowFlags()[row].test( RowFlag::kRhsInf ); REAL lhs = problem.getConstraintMatrix().getLeftHandSides()[row]; REAL rhs = problem.getConstraintMatrix().getRightHandSides()[row]; REAL slack = solution.slack[row]; assert( lhs_infinity || rhs_infinity || num.isFeasGE( rhs, lhs ) ); switch( solution.rowBasisStatus[row] ) { case VarBasisStatus::BASIC: if( ! num.isFeasZero( solution.dual[row] ) ) return true; number_basic_variable++; break; case VarBasisStatus::FIXED: if( lhs_infinity || rhs_infinity || ! num.isFeasEq( lhs, rhs ) || ! num.isFeasEq( slack, rhs ) ) return true; assert( problem.getRowFlags()[row].test( RowFlag::kEquation ) ); break; case VarBasisStatus::ON_LOWER: if( lhs_infinity || ! num.isFeasEq( slack, lhs ) ) return true; break; case VarBasisStatus::ON_UPPER: if( rhs_infinity || ! num.isFeasEq( slack, rhs ) ) return true; break; case VarBasisStatus::ZERO: if( ! rhs_infinity || ! lhs_infinity || ! num.isFeasZero( slack ) ) return true; break; case VarBasisStatus::UNDEFINED: return true; } } return number_basic_variable != number_rows; } public: bool checkObjectiveFunction( const Vec& primalSolution, const Vec& dualSolution, const Vec& reducedCosts, const Problem& problem ) { REAL duality_gap = getDualityGap( primalSolution, dualSolution, reducedCosts, problem ); return ! num.isFeasZero( duality_gap ) ; } PostsolveStatus verifySolutionAndUpdateSlack( Solution& solution, const Problem& problem ) { bool failure = checkLength( solution, problem ); if( failure ) { message.info( "Solution vector length check FAILED.\n" ); return PostsolveStatus::kFailed; } failure = checkPrimalFeasibilityAndUpdateSlack( solution, problem ); if( failure ) { message.info( "Primal feasibility check FAILED.\n" ); return PostsolveStatus::kFailed; } if( solution.type == SolutionType::kPrimalDual ) { if( checkDualFeasibility( solution.primal, solution.dual, solution.reducedCosts, solution.varBasisStatus, problem ) ) { message.info( "Dual feasibility check FAILED.\n" ); failure = true; } if( checkComplementarySlackness( solution.primal, solution.dual, solution.reducedCosts, problem ) ) { message.info( "Complementary slack check FAILED.\n" ); failure = true; } if( checkBasis( solution, problem ) ) { message.info( "Basis check FAILED.\n" ); failure = true; } if( checkObjectiveFunction( solution.primal, solution.dual, solution.reducedCosts, problem ) ) { message.info( "Objective function failed.\n" ); } if( failure ) return PostsolveStatus::kFailed; } message.info( "Solution passed validation\n" ); return PostsolveStatus::kOk; } REAL getDualityGap( const Vec& primalSolution, const Vec& dualSolution, const Vec& reducedCosts, const Problem& problem ) { StableSum primal_objective; for( int i = 0; i < problem.getNCols(); i++ ) { primal_objective.add( primalSolution[i] * problem.getObjective().coefficients[i] ); } StableSum dual_objective; for( int i = 0; i < problem.getNRows(); i++ ) { REAL dual = dualSolution[i]; REAL side; if( dual < 0 ) side = problem.getConstraintMatrix().getRightHandSides()[i]; else side = problem.getConstraintMatrix().getLeftHandSides()[i]; dual_objective.add( dual * side ); } for( int i = 0; i < problem.getNCols(); i++ ) { REAL reducedCost = reducedCosts[i]; REAL side; if( reducedCost < 0 ) side = problem.getUpperBounds()[i]; else side = problem.getLowerBounds()[i]; dual_objective.add( reducedCost * side ); } return primal_objective.get() - dual_objective.get(); } }; } // namespace papilo #endif