/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* */
/* 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_OPTIONS_PARSER_HPP_
#define _PAPILO_MISC_OPTIONS_PARSER_HPP_
#include "papilo/misc/fmt.hpp"
#include
#include
#include
#include
#include
#include
namespace papilo
{
using namespace boost::program_options;
enum class Command
{
kNone,
kPresolve,
kSolve,
kPostsolve
};
struct ArithmeticType
{
enum
{
kDouble = 'd',
kQuad = 'q',
kRational = 'r'
};
};
struct OptionsInfo
{
Command command = Command::kNone;
std::string instance_file;
std::string reduced_problem_file;
std::string postsolve_archive_file;
std::string reduced_solution_file;
std::string orig_solution_file;
std::string orig_dual_solution_file;
std::string orig_reduced_costs_file;
std::string orig_basis_file;
std::string scip_settings_file;
std::string optimal_solution_file;
std::string soplex_settings_file;
std::string param_settings_file;
std::string objective_reference;
std::vector unparsed_options;
double tlim = std::numeric_limits::max();
char arithmetic_type = ArithmeticType::kDouble;
int nthreads;
bool print_stats;
bool print_params;
bool is_complete;
bool
checkFiles()
{
if( existsFile(instance_file))
{
fmt::print( "file {} is not valid\n", instance_file );
return false;
}
if( command == Command::kPostsolve && existsFile(postsolve_archive_file) )
{
fmt::print( "file {} is not valid\n", postsolve_archive_file );
return false;
}
if( command == Command::kPostsolve && existsFile(reduced_solution_file) )
{
fmt::print( "file {} is not valid\n", reduced_solution_file );
return false;
}
if( existsFile( scip_settings_file ) )
{
fmt::print( "file {} is not valid\n", scip_settings_file );
return false;
}
if( existsFile( optimal_solution_file ) )
{
fmt::print( "file {} is not valid\n", optimal_solution_file );
return false;
}
if( existsFile( orig_reduced_costs_file ) )
{
fmt::print( "file {} is not valid\n", orig_reduced_costs_file );
return false;
}
if( existsFile( orig_basis_file ) )
{
fmt::print( "file {} is not valid\n", orig_basis_file );
return false;
}
if( existsFile( orig_dual_solution_file ) )
{
fmt::print( "file {} is not valid\n", orig_dual_solution_file );
return false;
}
if( existsFile( optimal_solution_file ) )
{
fmt::print( "file {} is not valid\n", optimal_solution_file );
return false;
}
if( existsFile( soplex_settings_file ))
{
fmt::print( "file {} is not valid\n", soplex_settings_file );
return false;
}
if( !print_params && existsFile( param_settings_file ))
{
fmt::print( "file {} is not valid\n", param_settings_file );
return false;
}
return true;
}
bool
existsFile( std::string& filename ) const
{
return !filename.empty() && !std::ifstream( filename );
}
void
parse( const std::string& commandString,
const std::vector& opts = std::vector() )
{
is_complete = false;
if( commandString == "presolve" )
command = Command::kPresolve;
else if( commandString == "solve" )
command = Command::kSolve;
else if( commandString == "postsolve" )
command = Command::kPostsolve;
else
{
fmt::print( "unknown command: {}\n", commandString );
return;
}
std::string arithmetic_type_message = fmt::format(
"'{}' for double precision, '{}' for quad precision, and '{}' "
"for exact rational arithmetic",
(char)ArithmeticType::kDouble, (char)ArithmeticType::kQuad,
(char)ArithmeticType::kRational );
options_description desc( fmt::format( "{} command", commandString ) );
desc.add_options()( "file,f", value( &instance_file ), "instance file" );
desc.add_options()(
"arithmetic-type,a",
value( &arithmetic_type )->default_value( ArithmeticType::kDouble ),
arithmetic_type_message.c_str() );
desc.add_options()( "postsolve-archive,v",
value( &postsolve_archive_file ),
"filename for postsolve archive" );
if( command == Command::kPresolve )
{
desc.add_options()( "validate-solution,b",
value( &optimal_solution_file ),
"optimal solution for validation" );
}
if( command != Command::kPostsolve )
{
desc.add_options()( "reduced-problem,r",
value( &reduced_problem_file ),
"filename for reduced problem" );
desc.add_options()( "parameter-settings,p",
value( ¶m_settings_file ),
"filename for presolve parameter settings" );
desc.add_options()(
"print-params",
bool_switch( &print_params )->default_value( false ),
"print possible parameters presolving" );
desc.add_options()( "threads,t",
value( &nthreads )->default_value( 0 ) );
}
if( command != Command::kPresolve )
{
desc.add_options()( "reduced-solution,u",
value( &reduced_solution_file ),
"filename for solution of reduced problem" );
desc.add_options()( "solution,l", value( &orig_solution_file ),
"filename for solution" );
desc.add_options()( "dualsolution", value( &orig_dual_solution_file ),
"filename for dual solution" );
desc.add_options()( "reducedcosts,c", value( &orig_reduced_costs_file ),
"filename for reduced costs" );
desc.add_options()( "basis,w", value( &orig_basis_file ),
"filename for basis information" );
desc.add_options()( "reference-objective,o",
value( &objective_reference ),
"correct objective value for validation" );
desc.add_options()( "validate-solution,b",
value( &optimal_solution_file ),
"optimal solution for validation" );
}
if( command == Command::kSolve )
{
desc.add_options()( "scip-settings,s", value( &scip_settings_file ),
"SCIP settings file" );
desc.add_options()( "soplex-settings,x",
value( &soplex_settings_file ),
"SoPlex settings file" );
desc.add_options()(
"tlim", value( &tlim )->default_value( tlim ),
"time limit for solver (including presolve time)" );
desc.add_options()(
"print-stats", bool_switch( &print_stats )->default_value( false ),
"print detailed solver statistics" );
}
if( opts.empty() )
{
fmt::print( "\n{}\n", desc );
return;
}
variables_map vm;
parsed_options parsed = command_line_parser( opts )
.options( desc )
.allow_unregistered()
.run();
store( parsed, vm );
notify( vm );
if( !checkFiles() )
return;
if( arithmetic_type != ArithmeticType::kDouble &&
arithmetic_type != ArithmeticType::kQuad &&
arithmetic_type != ArithmeticType::kRational )
fmt::print( "invalid arithmetic type '{}'\nvalid options are {}\n",
(char)arithmetic_type, arithmetic_type_message );
switch( command )
{
case Command::kSolve:
case Command::kPresolve:
if( instance_file.empty() )
{
fmt::print( "{} requires an instance file\n", commandString );
return;
}
break;
case Command::kPostsolve:
if( postsolve_archive_file.empty() || reduced_solution_file.empty() )
{
fmt::print(
"{} requires a postsolve archive and a reduced solution\n",
commandString );
return;
}
break;
case Command::kNone:
assert( false );
}
unparsed_options =
collect_unrecognized( parsed.options, exclude_positional );
is_complete = true;
}
};
OptionsInfo
parseOptions( int argc, char* argv[] )
{
OptionsInfo optionsInfo;
using namespace boost::program_options;
using boost::none;
using boost::optional;
std::string usage =
fmt::format( "usage:\n {} [COMMAND] [ARGUMENTS]\n", argv[0] );
// global description.
// will capture the command and arguments as unrecognised
options_description global{};
global.add_options()( "help,h", "produce help message" );
global.add_options()( "command", value(),
"command: {presolve, solve, postsolve}." );
global.add_options()( "args", value>(),
"arguments for the command" );
positional_options_description pos;
pos.add( "command", 1 );
pos.add( "args", -1 );
parsed_options parsed = command_line_parser( argc, argv )
.options( global )
.positional( pos )
.allow_unregistered()
.run();
variables_map vm;
store( parsed, vm );
if( vm.count( "help" ) || vm.empty() )
{
fmt::print( "{}\n{}", usage, global );
optionsInfo.parse( "presolve" );
optionsInfo.parse( "solve" );
optionsInfo.parse( "postsolve" );
return optionsInfo;
}
// we branch on each command
// and parse the arguments passed with the command
if( vm.count( "command" ) )
{
auto command = vm["command"].as();
std::vector opts =
collect_unrecognized( parsed.options, include_positional );
opts.erase( opts.begin() );
optionsInfo.parse( command, opts );
}
return optionsInfo;
}
} // namespace papilo
#endif