/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* */
/* 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_WRAPPERS_HPP_
#define _PAPILO_MISC_WRAPPERS_HPP_
#include "papilo/core/Presolve.hpp"
#include "papilo/core/postsolve/Postsolve.hpp"
#include "papilo/io/MpsParser.hpp"
#include "papilo/io/MpsWriter.hpp"
#include "papilo/io/SolParser.hpp"
#include "papilo/io/SolWriter.hpp"
#include "papilo/misc/NumericalStatistics.hpp"
#include "papilo/misc/OptionsParser.hpp"
#include "papilo/misc/Validation.hpp"
#ifdef PAPILO_TBB
#include "papilo/misc/tbb.hpp"
#else
#include
#endif
#include
#include
#include
#include
#include
#include
namespace papilo
{
enum class ResultStatus
{
kOk = 0,
kUnbndOrInfeas,
kError
};
template
ResultStatus
presolve_and_solve(
const OptionsInfo& opts,
std::unique_ptr> lpSolverFactory = nullptr,
std::unique_ptr> mipSolverFactory = nullptr )
{
try
{
double readtime = 0;
Problem problem;
boost::optional> prob;
{
Timer t( readtime );
prob = MpsParser::loadProblem( opts.instance_file );
}
// Check whether reading was successful or not
if( !prob )
{
fmt::print( "error loading problem {}\n", opts.instance_file );
return ResultStatus::kError;
}
problem = *prob;
fmt::print( "reading took {:.3} seconds\n", readtime );
NumericalStatistics nstats( problem );
nstats.printStatistics();
Presolve presolve;
presolve.addDefaultPresolvers();
presolve.getPresolveOptions().threads = std::max( 0, opts.nthreads );
if( !opts.param_settings_file.empty() || !opts.unparsed_options.empty() ||
opts.print_params )
{
ParameterSet paramSet = presolve.getParameters();
if(lpSolverFactory != nullptr)
lpSolverFactory->add_parameters(paramSet);
if(mipSolverFactory != nullptr)
mipSolverFactory->add_parameters(paramSet);
if( !opts.param_settings_file.empty() && !opts.print_params )
{
std::ifstream input( opts.param_settings_file );
if( input )
{
String theoptionstr;
String thevaluestr;
for( String line; getline( input, line ); )
{
std::size_t pos = line.find_first_of( '#' );
if( pos != String::npos )
line = line.substr( 0, pos );
pos = line.find_first_of( '=' );
if( pos == String::npos )
continue;
theoptionstr = line.substr( 0, pos - 1 );
thevaluestr = line.substr( pos + 1 );
boost::algorithm::trim( theoptionstr );
boost::algorithm::trim( thevaluestr );
try
{
paramSet.parseParameter( theoptionstr.c_str(),
thevaluestr.c_str() );
fmt::print( "set {} = {}\n", theoptionstr, thevaluestr );
}
catch( const std::exception& e )
{
fmt::print( "parameter '{}' could not be set: {}\n", line,
e.what() );
}
}
}
else
{
fmt::print( "could not read parameter file '{}'\n",
opts.param_settings_file );
}
}
if( !opts.unparsed_options.empty() )
{
String theoptionstr;
String thevaluestr;
for( const auto& option : opts.unparsed_options )
{
std::size_t pos = option.find_first_of( '=' );
if( pos != String::npos && pos > 2 )
{
theoptionstr = option.substr( 2, pos - 2 );
thevaluestr = option.substr( pos + 1 );
try
{
paramSet.parseParameter( theoptionstr.c_str(),
thevaluestr.c_str() );
fmt::print( "set {} = {}\n", theoptionstr, thevaluestr );
}
catch( const std::exception& e )
{
fmt::print( "parameter '{}' could not be set: {}\n",
option, e.what() );
}
}
else
{
fmt::print(
"parameter '{}' could not be set: value expected\n",
option );
}
}
}
if( opts.print_params )
{
if( !opts.param_settings_file.empty() )
{
std::ofstream outfile( opts.param_settings_file );
if( outfile )
{
std::ostream_iterator out_it( outfile );
paramSet.printParams( out_it );
}
else
{
fmt::print( "could not write to parameter file '{}'\n",
opts.param_settings_file );
}
}
else
{
String paramDesc;
paramSet.printParams( std::back_inserter( paramDesc ) );
puts( paramDesc.c_str() );
}
}
}
presolve.setLPSolverFactory( std::move( lpSolverFactory ) );
presolve.setMIPSolverFactory( std::move( mipSolverFactory ) );
presolve.getPresolveOptions().tlim =
std::min( opts.tlim, presolve.getPresolveOptions().tlim );
bool store_dual_postsolve = false;
std::unique_ptr> solver;
if(opts.command == Command::kSolve)
{
if( problem.getNumIntegralCols() == 0 &&
presolve.getLPSolverFactory() )
{
solver = presolve.getLPSolverFactory()->newSolver(
presolve.getVerbosityLevel() );
store_dual_postsolve = solver->is_dual_solution_available();
}
else if( presolve.getMIPSolverFactory() )
solver = presolve.getMIPSolverFactory()->newSolver(
presolve.getVerbosityLevel() );
else
{
fmt::print( "no solver available for solving; aborting\n" );
return ResultStatus::kError;
}
}
auto result = presolve.apply( problem, store_dual_postsolve );
if( !opts.optimal_solution_file.empty() )
{
if( presolve.getPresolveOptions().dualreds != 0 )
{
fmt::print( "**WARNING: Enabling dual reductions might cut of "
"feasible or optimal solution\n" );
}
Validation::validateProblem( problem, result.postsolve,
opts.optimal_solution_file,
result.status );
}
switch( result.status )
{
case PresolveStatus::kInfeasible:
fmt::print(
"presolving detected infeasible problem after {:.3f} seconds\n",
presolve.getStatistics().presolvetime );
return ResultStatus::kUnbndOrInfeas;
case PresolveStatus::kUnbndOrInfeas:
fmt::print(
"presolving detected unbounded or infeasible problem after "
"{:.3f} seconds\n",
presolve.getStatistics().presolvetime );
return ResultStatus::kUnbndOrInfeas;
case PresolveStatus::kUnbounded:
fmt::print(
"presolving detected unbounded problem after {:.3f} seconds\n",
presolve.getStatistics().presolvetime );
return ResultStatus::kUnbndOrInfeas;
case PresolveStatus::kUnchanged:
case PresolveStatus::kReduced:
break;
}
fmt::print( "\npresolving finished after {:.3f} seconds\n\n",
presolve.getStatistics().presolvetime );
double writetime = 0;
if( !opts.reduced_problem_file.empty() )
{
Timer t( writetime );
MpsWriter::writeProb( opts.reduced_problem_file, problem,
result.postsolve.origrow_mapping,
result.postsolve.origcol_mapping );
fmt::print( "reduced problem written to {} in {:.3f} seconds\n\n",
opts.reduced_problem_file, t.getTime() );
}
if( !opts.postsolve_archive_file.empty() )
{
Timer t( writetime );
std::ofstream ofs( opts.postsolve_archive_file,
std::ios_base::binary );
boost::archive::binary_oarchive oa( ofs );
// write class instance to archive
oa << result.postsolve;
fmt::print( "postsolve archive written to {} in {:.3f} seconds\n\n",
opts.postsolve_archive_file, t.getTime() );
}
if( opts.command == Command::kPresolve )
return ResultStatus::kOk;
double solvetime = 0;
{
Timer t( solvetime );
solver->setUp( problem, result.postsolve.origrow_mapping,
result.postsolve.origcol_mapping );
if( opts.tlim != std::numeric_limits::max() )
{
double tlim =
opts.tlim - presolve.getStatistics().presolvetime - writetime;
if( tlim <= 0 )
{
fmt::print( "time limit reached in presolving\n" );
return ResultStatus::kOk;
}
solver->setTimeLimit( tlim );
}
solver->solve();
SolverStatus status = solver->getStatus();
if( opts.print_stats )
solver->printDetails();
Solution solution;
solution.type = SolutionType::kPrimal;
if( result.postsolve.getOriginalProblem().getNumIntegralCols() == 0 &&
store_dual_postsolve )
solution.type = SolutionType::kPrimalDual;
if( ( status == SolverStatus::kOptimal ||
status == SolverStatus::kInterrupted ) &&
solver->getSolution( solution ) )
postsolve( result.postsolve, solution, opts.objective_reference,
opts.orig_solution_file, opts.orig_dual_solution_file,
opts.orig_reduced_costs_file, opts.orig_basis_file );
if( status == SolverStatus::kInfeasible )
fmt::print(
"\nsolving detected infeasible problem after {:.3f} seconds\n",
presolve.getStatistics().presolvetime + solvetime + writetime );
else if( status == SolverStatus::kUnbounded )
fmt::print(
"\nsolving detected unbounded problem after {:.3f} seconds\n",
presolve.getStatistics().presolvetime + solvetime + writetime );
else if( status == SolverStatus::kUnbndOrInfeas )
fmt::print(
"\nsolving detected unbounded or infeasible problem after "
"{:.3f} seconds\n",
presolve.getStatistics().presolvetime + solvetime + writetime );
else
fmt::print( "\nsolving finished after {:.3f} seconds\n",
presolve.getStatistics().presolvetime + solvetime +
writetime );
}
}
catch( std::bad_alloc& ex )
{
fmt::print( "Memory out exception occured! Please assign more memory\n" );
return ResultStatus::kError;
}
return ResultStatus::kOk;
}
template
void
postsolve( PostsolveStorage& postsolveStorage,
const Solution& reduced_sol,
const std::string& objective_reference = "",
const std::string& primal_solution_output = "",
const std::string& dual_solution_output = "",
const std::string& reduced_solution_output = "",
const std::string& basis_output = "" )
{
Solution original_sol;
#ifdef PAPILO_TBB
auto t0 = tbb::tick_count::now();
#else
auto t0 = std::chrono::steady_clock::now();
#endif
const Message msg{};
Postsolve postsolve{ msg, postsolveStorage.getNum() };
PostsolveStatus status =
postsolve.undo( reduced_sol, original_sol, postsolveStorage );
#ifdef PAPILO_TBB
auto t1 = tbb::tick_count::now();
double sec1 = ( t1 - t0 ).seconds();
#else
auto t1 = std::chrono::steady_clock::now();
double sec1 =
std::chrono::duration_cast( t1 - t0 )
.count() /
1000;
#endif
fmt::print( "\npostsolve finished after {:.3f} seconds\n", sec1 );
const Problem& origprob = postsolveStorage.getOriginalProblem();
REAL origobj = origprob.computeSolObjective( original_sol.primal );
REAL boundviol = 0;
REAL intviol = 0;
REAL rowviol = 0;
bool origfeas = origprob.computeSolViolations( postsolveStorage.getNum(),
original_sol.primal,
boundviol, rowviol, intviol );
fmt::print( "feasible: {}\nobjective value: {:.15}\n", origfeas,
double( origobj ) );
fmt::print( "\nviolations:\n" );
fmt::print( " bounds: {:.15}\n", double( boundviol ) );
fmt::print( " constraints: {:.15}\n", double( rowviol ) );
fmt::print( " integrality: {:.15}\n\n", double( intviol ) );
if( !primal_solution_output.empty() )
{
#ifdef PAPILO_TBB
auto t2 = tbb::tick_count::now();
#else
auto t2 = std::chrono::steady_clock::now();
#endif
SolWriter::writePrimalSol( primal_solution_output,
original_sol.primal,
origprob.getObjective().coefficients,
origobj, origprob.getVariableNames() );
#ifdef PAPILO_TBB
auto t3 = tbb::tick_count::now();
double sec3 = ( t3 - t2 ).seconds();
#else
auto t3 = std::chrono::steady_clock::now();
double sec3 =
std::chrono::duration_cast( t3 - t2 )
.count() /
1000;
#endif
fmt::print( "solution written to file {} in {:.3} seconds\n",
primal_solution_output, sec3 );
}
if( !dual_solution_output.empty() &&
original_sol.type == SolutionType::kPrimalDual )
{
#ifdef PAPILO_TBB
auto t2 = tbb::tick_count::now();
#else
auto t2 = std::chrono::steady_clock::now();
#endif
const ConstraintMatrix& constraintMatrix =
origprob.getConstraintMatrix();
SolWriter::writeDualSol( dual_solution_output, original_sol.dual,
constraintMatrix.getRightHandSides(),
constraintMatrix.getLeftHandSides(),
origobj, origprob.getConstraintNames() );
#ifdef PAPILO_TBB
auto t3 = tbb::tick_count::now();
double sec3 = ( t3 - t2 ).seconds();
#else
auto t3 = std::chrono::steady_clock::now();
double sec3 =
std::chrono::duration_cast( t3 - t2 )
.count() /
1000;
#endif
fmt::print( "dual solution written to file {} in {:.3} seconds\n",
dual_solution_output, sec3 );
}
if( !reduced_solution_output.empty() &&
original_sol.type == SolutionType::kPrimalDual )
{
#ifdef PAPILO_TBB
auto t2 = tbb::tick_count::now();
#else
auto t2 = std::chrono::steady_clock::now();
#endif
SolWriter::writeReducedCostsSol(
reduced_solution_output, original_sol.reducedCosts,
origprob.getUpperBounds(), origprob.getLowerBounds(), origobj,
origprob.getVariableNames() );
#ifdef PAPILO_TBB
auto t3 = tbb::tick_count::now();
double sec3 = ( t3 - t2 ).seconds();
#else
auto t3 = std::chrono::steady_clock::now();
double sec3 =
std::chrono::duration_cast( t3 - t2 )
.count() /
1000;
#endif
fmt::print( "reduced solution written to file {} in {:.3} seconds\n",
reduced_solution_output, sec3 );
}
if( !basis_output.empty() && original_sol.type == SolutionType::kPrimalDual )
{
#ifdef PAPILO_TBB
auto t2 = tbb::tick_count::now();
#else
auto t2 = std::chrono::steady_clock::now();
#endif
SolWriter::writeBasis( basis_output, original_sol.varBasisStatus,
original_sol.rowBasisStatus,
origprob.getVariableNames(),
origprob.getConstraintNames() );
#ifdef PAPILO_TBB
auto t3 = tbb::tick_count::now();
double sec3 = ( t3 - t2 ).seconds();
#else
auto t3 = std::chrono::steady_clock::now();
double sec3 =
std::chrono::duration_cast( t3 - t2 )
.count() /
1000;
#endif
fmt::print( "basis written to file {} in {:.3} seconds\n", basis_output,
sec3 );
}
if( !objective_reference.empty() )
{
if( origfeas && status == PostsolveStatus::kOk &&
postsolveStorage.num.isFeasEq(
boost::lexical_cast( objective_reference ),
double( origobj ) ) )
fmt::print( "validation: SUCCESS\n" );
else
fmt::print( "validation: FAILURE\n" );
}
}
template
void
postsolve( const OptionsInfo& opts )
{
PostsolveStorage ps;
std::ifstream inArchiveFile( opts.postsolve_archive_file,
std::ios_base::binary );
boost::archive::binary_iarchive inputArchive( inArchiveFile );
inputArchive >> ps;
inArchiveFile.close();
SolParser parser;
Solution reduced_solution;
bool success = parser.read( opts.reduced_solution_file, ps.origcol_mapping,
ps.getOriginalProblem().getVariableNames(),
reduced_solution );
if( success )
postsolve( ps, reduced_solution, opts.objective_reference,
opts.orig_solution_file, opts.orig_dual_solution_file,
opts.orig_reduced_costs_file, opts.orig_basis_file );
}
} // namespace papilo
#endif