delpi  0.0.1
DElta-complete LP solver
Loading...
Searching...
No Matches
ArgParser.cpp
1
6// IWYU pragma: no_include "argparse/argparse.hpp" // Already included in the header
7#include "delpi/util/ArgParser.h"
8
9#include <cstdlib>
10#include <filesystem>
11#include <iostream>
12#include <stdexcept>
13#include <string>
14
15#ifdef DELPI_ENABLED_QSOPTEX
16#include "delpi/libs/qsopt_ex.h"
17#endif
18#ifdef DELPI_ENABLED_SOPLEX
19#include "delpi/libs/soplex.h"
20#endif
21
22#include "delpi/util/OptionValue.hpp"
23#include "delpi/util/error.h"
24#include "delpi/util/filesystem.h"
25#include "delpi/util/logging.h"
26#include "delpi/version.h"
27
28namespace delpi {
29
30#define DELPI_PARSE_PARAM_BOOL(parser, name, ...) \
31 do { \
32 parser.add_argument(__VA_ARGS__) \
33 .help(delpi::Config::help_##name) \
34 .default_value(delpi::Config::default_##name) \
35 .implicit_value(!delpi::Config::default_##name); \
36 } while (false)
37
38#define DELPI_PARSE_PARAM_SCAN(parser, name, scan_char, scan_type, ...) \
39 do { \
40 parser.add_argument(__VA_ARGS__) \
41 .help(delpi::Config::help_##name) \
42 .default_value(delpi::Config::default_##name) \
43 .nargs(1) \
44 .scan<scan_char, scan_type>(); \
45 } while (false)
46
47#define DELPI_PARSE_PARAM_ENUM(parser, name, cmd_name, invalid_prompt, ...) \
48 do { \
49 parser.add_argument(cmd_name) \
50 .help(delpi::Config::help_##name) \
51 .default_value(delpi::Config::default_##name) \
52 .action([](const std::string &value) { \
53 __VA_ARGS__ \
54 DELPI_INVALID_ARGUMENT_EXPECTED(cmd_name, value, invalid_prompt); \
55 }) \
56 .nargs(1); \
57 } while (false)
58
59#define DELPI_PARAM_TO_CONFIG(param_name, config_name, type) \
60 do { \
61 if (parser_.is_used(param_name)) config.m_##config_name().SetFromCommandLine(parser_.get<type>(param_name)); \
62 } while (false)
63
64ArgParser::ArgParser()
65 : parser_{DELPI_PROGRAM_NAME, DELPI_VERSION_STRING},
66#ifdef DELPI_ENABLED_QSOPTEX
67 qsoptex_hash_{QSopt_ex_repository_status()},
68#endif
69#ifdef DELPI_ENABLED_SOPLEX
70 soplex_hash_{soplex::getGitHash()},
71#endif
72 verbosity_{Config::default_verbose_delpi} {
73 DELPI_TRACE("ArgParser::ArgParser");
74 AddOptions();
75}
76
77void ArgParser::Parse(int argc, const char **argv) {
78 try {
79 parser_.parse_args(argc, argv);
80 DELPI_LOG_INIT_VERBOSITY((parser_.get<bool>("silent") ? 0 : verbosity_));
82 DELPI_TRACE("ArgParser::parse: parsed args");
83 } catch (const std::runtime_error &err) {
84 std::cerr << err.what() << "\n" << parser_ << std::endl;
85 std::exit(EXIT_FAILURE);
86 } catch (const std::invalid_argument &err) {
87 std::cerr << err.what() << "\n\n" << parser_.usage() << std::endl;
88 std::exit(EXIT_FAILURE);
89 }
90}
91
93 DELPI_TRACE("ArgParser::AddOptions: adding options");
94 parser_.add_description(prompt());
95 parser_.add_argument("file").help("input file").default_value("");
96
97 DELPI_PARSE_PARAM_BOOL(parser_, csv, "--csv");
98 DELPI_PARSE_PARAM_BOOL(parser_, continuous_output, "--continuous-output");
99 DELPI_PARSE_PARAM_BOOL(parser_, debug_parsing, "--debug-parsing");
100 DELPI_PARSE_PARAM_BOOL(parser_, dry_run, "--dry-run");
101 DELPI_PARSE_PARAM_BOOL(parser_, skip_optimise, "--skip-optimise");
102 DELPI_PARSE_PARAM_BOOL(parser_, produce_models, "-m", "--produce-models");
103 DELPI_PARSE_PARAM_BOOL(parser_, silent, "-s", "--silent");
104 DELPI_PARSE_PARAM_BOOL(parser_, with_timings, "-t", "--timings");
105 DELPI_PARSE_PARAM_BOOL(parser_, read_from_stdin, "--in");
106 DELPI_PARSE_PARAM_BOOL(parser_, verify, "--verify");
107
108 // DELPI_PARSE_PARAM_SCAN(parser_, number_of_jobs, 'i', unsigned int, "-j", "--jobs");
109 DELPI_PARSE_PARAM_SCAN(parser_, delta, 'g', double, "-d", "--delta");
110 DELPI_PARSE_PARAM_SCAN(parser_, random_seed, 'i', unsigned int, "-r", "--random-seed");
111 DELPI_PARSE_PARAM_SCAN(parser_, timeout, 'i', unsigned int, "--timeout");
112 DELPI_PARSE_PARAM_SCAN(parser_, verbose_simplex, 'i', int, "--verbose-simplex");
113
114 parser_.add_argument("-V", "--verbose")
115 .help("increase verbosity level. Can be used multiple times. Maximum verbosity level is 5 and default is 2")
116 .action([this](const auto &) {
117 if (verbosity_ < 5) ++verbosity_;
118 })
119 .append()
120 .nargs(0);
121 parser_.add_argument("-q", "--quiet")
122 .help("decrease verbosity level. Can be used multiple times. Minimum verbosity level is 0 and default is 2")
123 .action([this](const auto &) {
124 if (verbosity_ > 0) --verbosity_;
125 })
126 .append()
127 .nargs(0);
128
129 DELPI_PARSE_PARAM_ENUM(
130 parser_, lp_mode, "--lp-mode",
131 "[ auto | pure-precision-boosting | pure-iterative-refinement | hybrid ] or [ 1 | 2 | 3 | 4 ]",
132 if (value == "auto" || value == "1") return Config::LpMode::AUTO;
133 if (value == "pure-precision-boosting" || value == "2") return Config::LpMode::PURE_PRECISION_BOOSTING;
134 if (value == "pure-iterative-refinement" || value == "3") return Config::LpMode::PURE_ITERATIVE_REFINEMENT;
135 if (value == "hybrid" || value == "4") return Config::LpMode::HYBRID;); // NOLINT(readability/braces)
136 DELPI_PARSE_PARAM_ENUM(
137 parser_, format, "--format", "[ auto | mps ] or [ 1 | 2 ]",
138 if (value == "auto" || value == "1") return Config::Format::AUTO;
139 if (value == "mps" || value == "2") return Config::Format::MPS;); // NOLINT(readability/braces)
140 DELPI_PARSE_PARAM_ENUM(
141 parser_, lp_solver, "--lp-solver", "[ soplex | qsoptex | delpi ] or [ 1 | 2 | 3 ]",
142 if (value == "soplex" || value == "1") return Config::LpSolver::SOPLEX;
143 if (value == "qsoptex" || value == "2") return Config::LpSolver::QSOPTEX;
144 if (value == "delpi" || value == "3") return Config::LpSolver::DELPI;); // NOLINT(readability/braces)
145 DELPI_TRACE("ArgParser::ArgParser: added all arguments");
146}
147
149 DELPI_TRACE("ArgParser::ToConfig: converting to Config");
150 Config config{};
151
152 DELPI_PARAM_TO_CONFIG("csv", csv, bool);
153 DELPI_PARAM_TO_CONFIG("continuous-output", continuous_output, bool);
154 DELPI_PARAM_TO_CONFIG("debug-parsing", debug_parsing, bool);
155 DELPI_PARAM_TO_CONFIG("dry-run", dry_run, bool);
156 DELPI_PARAM_TO_CONFIG("delta", delta, double);
157 config.m_filename().SetFromCommandLine(parser_.is_used("file") ? parser_.get<std::string>("file") : "");
158 DELPI_PARAM_TO_CONFIG("format", format, Config::Format);
159 DELPI_PARAM_TO_CONFIG("lp-mode", lp_mode, Config::LpMode);
160 DELPI_PARAM_TO_CONFIG("lp-solver", lp_solver, Config::LpSolver);
161 // DELPI_PARAM_TO_CONFIG("jobs", number_of_jobs, unsigned int);
162 DELPI_PARAM_TO_CONFIG("skip-optimise", skip_optimise, bool);
163 DELPI_PARAM_TO_CONFIG("produce-models", produce_models, bool);
164 DELPI_PARAM_TO_CONFIG("random-seed", random_seed, unsigned int);
165 DELPI_PARAM_TO_CONFIG("in", read_from_stdin, bool);
166 DELPI_PARAM_TO_CONFIG("silent", silent, bool);
167 DELPI_PARAM_TO_CONFIG("timeout", timeout, unsigned int);
168 config.m_verbose_delpi().SetFromCommandLine(verbosity_);
169 DELPI_PARAM_TO_CONFIG("verbose-simplex", verbose_simplex, int);
170 DELPI_PARAM_TO_CONFIG("verify", verify, bool);
171 DELPI_PARAM_TO_CONFIG("timings", with_timings, bool);
172
173 DELPI_TRACE_FMT("ArgParser::ToConfig: {}", config);
174 return config;
175}
176
178 DELPI_TRACE("ArgParser::ValidateOptions: validating options");
179 if (parser_.is_used("in") && parser_.is_used("file"))
180 DELPI_INVALID_ARGUMENT("--in", "--in and file are mutually exclusive");
181 if (!parser_.is_used("in") && !parser_.is_used("file"))
182 DELPI_INVALID_ARGUMENT("file", "must be specified unless --in is used");
183 if (parser_.is_used("in") && (parser_.get<Config::Format>("format") == Config::Format::AUTO))
184 DELPI_INVALID_ARGUMENT("--in", "a format must be specified with --format");
185 // Check file extension if a file is provided
186 if (parser_.is_used("file")) {
187 const Config::Format format = parser_.get<Config::Format>("format");
188 const std::string extension{GetExtension(parser_.get<std::string>("file"))};
189 if (format == Config::Format::AUTO && extension != "mps") {
190 DELPI_INVALID_ARGUMENT("file", "file must be .mps if --format is auto");
191 }
192 if (!std::filesystem::is_regular_file(parser_.get<std::string>("file")))
193 DELPI_INVALID_ARGUMENT("file", "cannot find file or the file is not a regular file");
194 }
195 if (parser_.get<double>("delta") < 0) DELPI_INVALID_ARGUMENT("--delta", "cannot be negative");
196 if (parser_.is_used("verbose") && parser_.is_used("silent"))
197 DELPI_INVALID_ARGUMENT("--verbose", "verbosity is forcefully set to 0 if --silent is provided");
198 if (parser_.is_used("quiet") && parser_.is_used("silent"))
199 DELPI_INVALID_ARGUMENT("--quiet", "verbosity is already set to 0 if --silent is provided");
200 if (parser_.get<Config::LpSolver>("lp-solver") == Config::LpSolver::QSOPTEX)
201 if (parser_.get<Config::LpMode>("lp-mode") != Config::LpMode::AUTO &&
203 DELPI_INVALID_ARGUMENT("--lp-solver", "QSopt_ex only supports 'auto' and 'pure-precision-boosting' modes");
204}
205
206std::string ArgParser::version() { return DELPI_VERSION_STRING; }
207
208std::string ArgParser::repository_status() { return DELPI_VERSION_REPOSTAT; }
209
210std::string ArgParser::prompt() const {
211#ifndef NDEBUG
212 const std::string build_type{"Debug"};
213#else
214 const std::string build_type{"Release"};
215#endif
216 std::string repo_stat = repository_status();
217 if (!repo_stat.empty()) {
218 repo_stat = " (repository: " + repo_stat + ")";
219 }
220
221 std::string vstr = fmt::format("{} (v{}): {} ({} Build) {}", DELPI_PROGRAM_NAME, version(), DELPI_DESCRIPTION,
222 build_type, repo_stat);
223 if (!qsoptex_hash_.empty()) vstr += fmt::format(" (qsopt-ex: {})", qsoptex_hash_);
224 if (!soplex_hash_.empty()) vstr += fmt::format(" (soplex: {})", soplex_hash_);
225 return vstr;
226}
227
228std::ostream &operator<<(std::ostream &os, const ArgParser &parser) { return os << parser.parser_ << std::endl; }
229
230} // namespace delpi
void Parse(int argc, const char **argv)
Parse the command line arguments.
Definition ArgParser.cpp:77
int verbosity_
Verbosity level of the program.
Definition ArgParser.h:80
Config ToConfig() const
Convert the parser to a Config.
void ValidateOptions()
Validate the options, ensuring the correctness of the parameters and the consistency of the options.
argparse::ArgumentParser parser_
The parser object.
Definition ArgParser.h:77
void AddOptions()
Add all the options, positional arguments and flags to the parser.
Definition ArgParser.cpp:92
Simple dataclass used to store the configuration of the program.
Definition Config.h:36
LpSolver
Underlying LP solver used by the theory solver.
Definition Config.h:39
@ DELPI
Delpi Solver.
Definition Config.h:42
@ QSOPTEX
Qsoptex Solver.
Definition Config.h:41
@ SOPLEX
Soplex Solver. Default option.
Definition Config.h:40
LpMode
LP mode used by the LP solver.
Definition Config.h:51
@ HYBRID
Use both modes, if available.
Definition Config.h:55
@ PURE_PRECISION_BOOSTING
Use the precision boosting mode, if available.
Definition Config.h:53
@ AUTO
Let the LP solver choose the mode. Default option.
Definition Config.h:52
@ PURE_ITERATIVE_REFINEMENT
Use the iterative refinement mode, if available.
Definition Config.h:54
Format
Format of the input file.
Definition Config.h:45
@ AUTO
Automatically detect the input format based on the file extension. Default option.
Definition Config.h:46
@ MPS
MPS format.
Definition Config.h:47
void SetFromCommandLine(const T &value)
Sets the value to value which is given by a command-line argument.
Global namespace for the delpi library.
std::string GetExtension(const std::string &name)
Get the extension of the file.