/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* 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