934 lines
32 KiB
C++
934 lines
32 KiB
C++
/*------------------------------------------------------------------------*/
|
|
|
|
// Do include 'internal.hpp' but try to minimize internal dependencies.
|
|
|
|
#include "internal.hpp"
|
|
#include "signal.hpp" // Separate, only need for apps.
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
namespace CaDiCaL {
|
|
|
|
// A wrapper app which makes up the CaDiCaL stand alone solver. It in
|
|
// essence only consists of the 'App::main' function. So this class
|
|
// contains code, which is not required if only the library interface in
|
|
// the class 'Solver' is used (defined in 'cadical.hpp'). It further uses
|
|
// static data structures in order to have a signal handler catch signals.
|
|
//
|
|
// It is thus neither thread-safe nor reentrant. If you want to use
|
|
// multiple instances of the solver use the 'Solver' interface directly
|
|
// which is thread-safe and reentrant among different solver instances.
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
class App : public Handler, public Terminator {
|
|
|
|
Solver * solver; // Global solver.
|
|
|
|
#ifndef __WIN32
|
|
// Command line options.
|
|
//
|
|
int time_limit; // '-t <sec>'
|
|
#endif
|
|
|
|
// Strictness of (DIMACS) parsing:
|
|
//
|
|
// 0 = force parsing and completely ignore header
|
|
// 1 = relaxed header handling (default)
|
|
// 2 = strict header handling
|
|
//
|
|
// To use '0' use '-f' of '--force'.
|
|
// To use '2' use '--strict'.
|
|
//
|
|
int force_strict_parsing;
|
|
|
|
bool force_writing;
|
|
static bool most_likely_existing_cnf_file (const char * path);
|
|
|
|
// Internal variables.
|
|
//
|
|
int max_var; // Set after parsing.
|
|
volatile bool timesup; // Asynchronous termination.
|
|
|
|
// Printing.
|
|
//
|
|
void print_usage (bool all = false);
|
|
void print_witness (FILE *);
|
|
|
|
#ifndef QUIET
|
|
void signal_message (const char * msg, int sig);
|
|
#endif
|
|
|
|
// Option handling.
|
|
//
|
|
bool set (const char*);
|
|
bool set (const char*, int);
|
|
int get (const char*);
|
|
bool verbose () { return get ("verbose") && !get ("quiet"); }
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
// The actual initialization.
|
|
//
|
|
void init ();
|
|
|
|
// Terminator interface.
|
|
//
|
|
bool terminate () { return timesup; }
|
|
|
|
// Handler interface.
|
|
//
|
|
void catch_signal (int sig);
|
|
void catch_alarm ();
|
|
|
|
public:
|
|
|
|
App ();
|
|
~App ();
|
|
|
|
// Parse the arguments and run the solver.
|
|
//
|
|
int main (int arg, char ** argv);
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
void App::print_usage (bool all) {
|
|
printf (
|
|
"usage: cadical [ <option> ... ] [ <input> [ <proof> ] ]\n"
|
|
"\n"
|
|
"where '<option>' is one of the following common options:\n"
|
|
"\n");
|
|
|
|
if (!all) { // Print only a short list of common options.
|
|
printf (
|
|
" -h print this short list of common options\n"
|
|
" --help print complete list of all options\n"
|
|
" --version print version\n"
|
|
"\n"
|
|
" -n do not print witness\n"
|
|
#ifndef QUIET
|
|
" -v increase verbosity\n"
|
|
" -q be quiet\n"
|
|
#endif
|
|
"\n"
|
|
#ifndef __WIN32
|
|
" -t <sec> set wall clock time limit\n"
|
|
#endif
|
|
);
|
|
} else { // Print complete list of all options.
|
|
printf (
|
|
" -h print alternatively only a list of common options\n"
|
|
" --help print this complete list of all options\n"
|
|
" --version print version\n"
|
|
"\n"
|
|
" -n do not print witness (same as '--no-witness')\n"
|
|
#ifndef QUIET
|
|
" -v increase verbosity (see also '--verbose' below)\n"
|
|
" -q be quiet (same as '--quiet')\n"
|
|
#endif
|
|
#ifndef __WIN32
|
|
" -t <sec> set wall clock time limit\n"
|
|
#endif
|
|
"\n"
|
|
"Or '<option>' is one of the less common options\n"
|
|
"\n"
|
|
" -L<rounds> run local search initially (default '0' rounds)\n"
|
|
" -O<level> increase limits by '2^<level>' or '10^<level>'\n"
|
|
" -P<rounds> initial preprocessing (default '0' rounds)\n"
|
|
"\n"
|
|
"Note there is no separating space for the options above while the\n"
|
|
"following options require a space after the option name:\n"
|
|
"\n"
|
|
" -c <limit> limit the number of conflicts (default unlimited)\n"
|
|
" -d <limit> limit the number of decisions (default unlimited)\n"
|
|
"\n"
|
|
" -o <output> write simplified CNF in DIMACS format to file\n"
|
|
" -e <extend> write reconstruction/extension stack to file\n"
|
|
#ifdef LOGGING
|
|
" -l enable logging messages (same as '--log')\n"
|
|
#endif
|
|
"\n"
|
|
" --force | -f parsing broken DIMACS header and writing proofs\n"
|
|
" --strict strict parsing (no white space in header)\n"
|
|
"\n"
|
|
" -r <sol> read solution in competition output format\n"
|
|
" to check consistency of learned clauses\n"
|
|
" during testing and debugging\n"
|
|
"\n"
|
|
" -w <sol> write result including a potential witness\n"
|
|
" solution in competition format to the given file\n"
|
|
"\n"
|
|
" --colors force colored output\n"
|
|
" --no-colors disable colored output to terminal\n"
|
|
" --no-witness do not print witness (see also '-n' above)\n"
|
|
"\n"
|
|
" --build print build configuration\n"
|
|
" --copyright print copyright information\n"
|
|
);
|
|
|
|
printf (
|
|
"\n"
|
|
"There are pre-defined configurations of advanced internal options:\n"
|
|
"\n");
|
|
|
|
solver->configurations ();
|
|
|
|
printf (
|
|
"\n"
|
|
"Or '<option>' is one of the following advanced internal options:\n"
|
|
"\n");
|
|
solver->usage ();
|
|
|
|
fputs (
|
|
"\n"
|
|
"The internal options have their default value printed in brackets\n"
|
|
"after their description. They can also be used in the form\n"
|
|
"'--<name>' which is equivalent to '--<name>=1' and in the form\n"
|
|
"'--no-<name>' which is equivalent to '--<name>=0'. One can also\n"
|
|
"use 'true' instead of '1', 'false' instead of '0', as well as\n"
|
|
"numbers with positive exponent such as '1e3' instead of '1000'.\n"
|
|
"\n"
|
|
"Alternatively option values can also be specified in the header\n"
|
|
"of the DIMACS file, e.g., 'c --elim=false', or through environment\n"
|
|
"variables, such as 'CADICAL_ELIM=false'. The embedded options in\n"
|
|
"the DIMACS file have highest priority, followed by command line\n"
|
|
"options and then values specified through environment variables.\n",
|
|
stdout);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Common to both complete and common option usage.
|
|
|
|
fputs (
|
|
"\n"
|
|
"The input is read from '<input>' assumed to be in DIMACS format.\n"
|
|
"Incremental 'p inccnf' files are supported too with cubes at the end.\n"
|
|
"If '<proof>' is given then a DRAT proof is written to that file.\n",
|
|
stdout);
|
|
|
|
//------------------------------------------------------------------------
|
|
// More explanations for complete option usage.
|
|
|
|
if (all) {
|
|
fputs (
|
|
"\n"
|
|
"If '<input>' is missing then the solver reads from '<stdin>',\n"
|
|
"also if '-' is used as input path name '<input>'. Similarly,\n"
|
|
"\n"
|
|
"For incremental files each cube is solved in turn. The solver\n"
|
|
"stops at the first satisfied cube if there is one and uses that\n"
|
|
"one for the witness to print. Conflict and decision limits are\n"
|
|
"applied to each individual cube solving call while '-P', '-L'"
|
|
#ifdef __WIN32
|
|
"\n"
|
|
#else
|
|
" and\n"
|
|
"'-t' "
|
|
#endif
|
|
"remain global. Only if all cubes were unsatisfiable the solver\n"
|
|
"prints the standard unsatisfiable solution line ('s UNSATISFIABLE').\n"
|
|
"\n"
|
|
"By default the proof is stored in the binary DRAT format unless\n"
|
|
"the option '--no-binary' is specified or the proof is written\n"
|
|
"to '<stdout>' and '<stdout>' is connected to a terminal.\n"
|
|
"\n"
|
|
"The input is assumed to be compressed if it is given explicitly\n"
|
|
"and has a '.gz', '.bz2', '.xz' or '.7z' suffix. The same applies\n"
|
|
"to the output file. In order to use compression and decompression\n"
|
|
"the corresponding utilities 'gzip', 'bzip', 'xz', and '7z' (depending\n"
|
|
"on the format) are required and need to be installed on the system.\n"
|
|
"The solver checks file type signatures though and falls back to\n"
|
|
"non-compressed file reading if the signature does not match.\n",
|
|
stdout);
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Pretty print competition format witness with 'v' lines.
|
|
|
|
void App::print_witness (FILE * file) {
|
|
int c = 0, i = 0, tmp;
|
|
do {
|
|
if (!c) fputc ('v', file), c = 1;
|
|
if (i++ == max_var) tmp = 0;
|
|
else tmp = solver->val (i) < 0 ? -i : i;
|
|
char str[20];
|
|
sprintf (str, " %d", tmp);
|
|
int l = strlen (str);
|
|
if (c + l > 78) fputs ("\nv", file), c = 1;
|
|
fputs (str, file);
|
|
c += l;
|
|
} while (tmp);
|
|
if (c) fputc ('\n', file);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Wrapper around option setting.
|
|
|
|
int App::get (const char * o) { return solver->get (o); }
|
|
bool App::set (const char * o, int v) { return solver->set (o, v); }
|
|
bool App::set (const char * arg) { return solver->set_long_option (arg); }
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
bool App::most_likely_existing_cnf_file (const char * path)
|
|
{
|
|
if (!File::exists (path)) return false;
|
|
|
|
if (has_suffix (path, ".dimacs")) return true;
|
|
if (has_suffix (path, ".dimacs.gz")) return true;
|
|
if (has_suffix (path, ".dimacs.xz")) return true;
|
|
if (has_suffix (path, ".dimacs.bz2")) return true;
|
|
if (has_suffix (path, ".dimacs.7z")) return true;
|
|
if (has_suffix (path, ".dimacs.lzma")) return true;
|
|
|
|
if (has_suffix (path, ".cnf")) return true;
|
|
if (has_suffix (path, ".cnf.gz")) return true;
|
|
if (has_suffix (path, ".cnf.xz")) return true;
|
|
if (has_suffix (path, ".cnf.bz2")) return true;
|
|
if (has_suffix (path, ".cnf.7z")) return true;
|
|
if (has_suffix (path, ".cnf.lzma")) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// Short-cut for errors to avoid a hard 'exit'.
|
|
|
|
#define APPERR(...) \
|
|
do { solver->error (__VA_ARGS__); } while (0)
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
int App::main (int argc, char ** argv) {
|
|
|
|
// Handle options which lead to immediate exit first.
|
|
|
|
if (argc == 2) {
|
|
const char * arg = argv[1];
|
|
if (!strcmp (arg, "-h")) {
|
|
print_usage ();
|
|
return 0;
|
|
} else if (!strcmp (arg, "--help")) {
|
|
print_usage (true);
|
|
return 0;
|
|
} else if (!strcmp (arg, "--version")) {
|
|
printf ("%s\n", CaDiCaL::version ());
|
|
return 0;
|
|
} else if (!strcmp (arg, "--build")) {
|
|
tout.disable ();
|
|
Solver::build (stdout, "");
|
|
return 0;
|
|
} else if (!strcmp (arg, "--copyright")) {
|
|
printf ("%s\n", copyright ());
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Now initialize solver.
|
|
|
|
init ();
|
|
|
|
// Set all argument option values to not used yet.
|
|
|
|
const char * preprocessing_specified = 0, * optimization_specified = 0;
|
|
const char * read_solution_path = 0, * write_result_path = 0;
|
|
const char * dimacs_path = 0, * proof_path = 0;
|
|
bool proof_specified = false, dimacs_specified = false;
|
|
int optimize = 0, preprocessing = 0, localsearch = 0;
|
|
const char * output_path = 0, * extension_path = 0;
|
|
int conflict_limit = -1, decision_limit = -1;
|
|
const char * conflict_limit_specified = 0;
|
|
const char * decision_limit_specified = 0;
|
|
const char * localsearch_specified = 0;
|
|
const char * time_limit_specified = 0;
|
|
bool witness = true, less = false;
|
|
const char * dimacs_name, * err;
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
if (!strcmp (argv[i], "-h") ||
|
|
!strcmp (argv[i], "--help") ||
|
|
!strcmp (argv[i], "--build") ||
|
|
!strcmp (argv[i], "--version") ||
|
|
!strcmp (argv[i], "--copyright")) {
|
|
APPERR ("can only use '%s' as single first option", argv[i]);
|
|
} else if (!strcmp (argv[i], "-")) {
|
|
if (proof_specified) APPERR ("too many arguments");
|
|
else if (!dimacs_specified) dimacs_specified = true;
|
|
else proof_specified = true;
|
|
} else if (!strcmp (argv[i], "-r")) {
|
|
if (++i == argc) APPERR ("argument to '-r' missing");
|
|
else if (read_solution_path)
|
|
APPERR ("multiple read solution file options '-r %s' and '-r %s'",
|
|
read_solution_path, argv[i]);
|
|
else read_solution_path = argv[i];
|
|
} else if (!strcmp (argv[i], "-w")) {
|
|
if (++i == argc) APPERR ("argument to '-w' missing");
|
|
else if (write_result_path)
|
|
APPERR ("multiple solution file options '-w %s' and '-w %s'",
|
|
write_result_path, argv[i]);
|
|
else write_result_path = argv[i];
|
|
} else if (!strcmp (argv[i], "-o")) {
|
|
if (++i == argc) APPERR ("argument to '-o' missing");
|
|
else if (output_path)
|
|
APPERR ("multiple output file options '-o %s' and '-o %s'",
|
|
output_path, argv[i]);
|
|
else if (!force_writing &&
|
|
most_likely_existing_cnf_file (argv[i]))
|
|
APPERR ("output file '%s' most likely existing CNF (use '-f')",
|
|
argv[i]);
|
|
else if (!File::writable (argv[i]))
|
|
APPERR ("output file '%s' not writable", argv[i]);
|
|
else output_path = argv[i];
|
|
} else if (!strcmp (argv[i], "-e")) {
|
|
if (++i == argc) APPERR ("argument to '-e' missing");
|
|
else if (extension_path)
|
|
APPERR ("multiple extension file options '-e %s' and '-e %s'",
|
|
extension_path, argv[i]);
|
|
else if (!force_writing &&
|
|
most_likely_existing_cnf_file (argv[i]))
|
|
APPERR ("extension file '%s' most likely existing CNF (use '-f')",
|
|
argv[i]);
|
|
else if (!File::writable (argv[i]))
|
|
APPERR ("extension file '%s' not writable", argv[i]);
|
|
else extension_path = argv[i];
|
|
} else if (is_color_option (argv[i])) {
|
|
tout.force_colors ();
|
|
terr.force_colors ();
|
|
} else if (is_no_color_option (argv[i])) {
|
|
tout.force_no_colors ();
|
|
terr.force_no_colors ();
|
|
} else if (!strcmp (argv[i], "--witness") ||
|
|
!strcmp (argv[i], "--witness=true") ||
|
|
!strcmp (argv[i], "--witness=1"))
|
|
witness = true;
|
|
else if (!strcmp (argv[i], "-n") ||
|
|
!strcmp (argv[i], "--no-witness") ||
|
|
!strcmp (argv[i], "--witness=false") ||
|
|
!strcmp (argv[i], "--witness=0"))
|
|
witness = false;
|
|
else if (!strcmp (argv[i], "--less")) { // EXPERIMENTAL!
|
|
if (less) APPERR ("multiple '--less' options");
|
|
else if (!isatty (1))
|
|
APPERR ("'--less' without '<stdout>' connected to terminal");
|
|
else less = true;
|
|
} else if (!strcmp (argv[i], "-c")) {
|
|
if (++i == argc) APPERR ("argument to '-c' missing");
|
|
else if (conflict_limit_specified)
|
|
APPERR ("multiple conflict limits '-c %s' and '-c %s'",
|
|
conflict_limit_specified, argv[i]);
|
|
else if (!parse_int_str (argv[i], conflict_limit))
|
|
APPERR ("invalid argument in '-c %s'", argv[i]);
|
|
else if (conflict_limit < 0)
|
|
APPERR ("invalid conflict limit");
|
|
else conflict_limit_specified = argv[i];
|
|
} else if (!strcmp (argv[i], "-d")) {
|
|
if (++i == argc) APPERR ("argument to '-d' missing");
|
|
else if (decision_limit_specified)
|
|
APPERR ("multiple decision limits '-d %s' and '-d %s'",
|
|
decision_limit_specified, argv[i]);
|
|
else if (!parse_int_str (argv[i], decision_limit))
|
|
APPERR ("invalid argument in '-d %s'", argv[i]);
|
|
else if (decision_limit < 0)
|
|
APPERR ("invalid decision limit");
|
|
else decision_limit_specified = argv[i];
|
|
}
|
|
#ifndef __WIN32
|
|
else if (!strcmp (argv[i], "-t")) {
|
|
if (++i == argc) APPERR ("argument to '-t' missing");
|
|
else if (time_limit_specified)
|
|
APPERR ("multiple time limit '-t %s' and '-t %s'",
|
|
time_limit_specified, argv[i]);
|
|
else if (!parse_int_str (argv[i], time_limit))
|
|
APPERR ("invalid argument in '-d %s'", argv[i]);
|
|
else if (time_limit < 0)
|
|
APPERR ("invalid time limit");
|
|
else time_limit_specified = argv[i];
|
|
}
|
|
#endif
|
|
#ifndef QUIET
|
|
else if (!strcmp (argv[i], "-q")) set ("--quiet");
|
|
else if (!strcmp (argv[i], "-v"))
|
|
set ("verbose", get ("verbose") + 1);
|
|
#endif
|
|
#ifdef LOGGING
|
|
else if (!strcmp (argv[i], "-l")) set ("--log");
|
|
#endif
|
|
else if (!strcmp (argv[i], "-f") ||
|
|
!strcmp (argv[i], "--force") ||
|
|
!strcmp (argv[i], "--force=1") ||
|
|
!strcmp (argv[i], "--force=true"))
|
|
force_strict_parsing = 0,
|
|
force_writing = true;
|
|
else if (!strcmp (argv[i], "--strict") ||
|
|
!strcmp (argv[i], "--strict=1") ||
|
|
!strcmp (argv[i], "--strict=true"))
|
|
force_strict_parsing = 2;
|
|
else if (has_prefix (argv[i], "-O")) {
|
|
if (optimization_specified)
|
|
APPERR ("multiple optimization options '%s' and '%s'",
|
|
optimization_specified, argv[i]);
|
|
optimization_specified = argv[i];
|
|
if (!parse_int_str (argv[i] + 2, optimize))
|
|
APPERR ("invalid optimization option '%s'", argv[i]);
|
|
if (optimize < 0 || optimize > 31)
|
|
APPERR ("invalid argument in '%s' (expected '0..31')", argv[i]);
|
|
} else if (has_prefix (argv[i], "-P")) {
|
|
if (preprocessing_specified)
|
|
APPERR ("multiple preprocessing options '%s' and '%s'",
|
|
preprocessing_specified, argv[i]);
|
|
preprocessing_specified = argv[i];
|
|
if (!parse_int_str (argv[i] + 2, preprocessing))
|
|
APPERR ("invalid preprocessing option '%s'", argv[i]);
|
|
if (preprocessing < 0)
|
|
APPERR ("invalid argument in '%s' (expected non-negative number)",
|
|
argv[i]);
|
|
} else if (has_prefix (argv[i], "-L")) {
|
|
if (localsearch_specified)
|
|
APPERR ("multiple local search options '%s' and '%s'",
|
|
localsearch_specified, argv[i]);
|
|
localsearch_specified = argv[i];
|
|
if (!parse_int_str (argv[i] + 2, localsearch))
|
|
APPERR ("invalid local search option '%s'", argv[i]);
|
|
if (localsearch < 0)
|
|
APPERR ("invalid argument in '%s' (expected non-negative number)",
|
|
argv[i]);
|
|
} else if (has_prefix (argv[i], "--") &&
|
|
solver->is_valid_configuration (argv[i] + 2)) {
|
|
solver->configure (argv[i] + 2);
|
|
} else if (set (argv[i])) {
|
|
/* nothing do be done */
|
|
} else if (argv[i][0] == '-') APPERR ("invalid option '%s'", argv[i]);
|
|
else if (proof_specified) APPERR ("too many arguments");
|
|
else if (dimacs_specified) {
|
|
proof_path = argv[i];
|
|
proof_specified = true;
|
|
if (!force_writing &&
|
|
most_likely_existing_cnf_file (proof_path))
|
|
APPERR ("DRAT proof file '%s' most likely existing CNF (use '-f')",
|
|
proof_path);
|
|
else if (!File::writable (proof_path))
|
|
APPERR ("DRAT proof file '%s' not writable", proof_path);
|
|
} else dimacs_specified = true, dimacs_path = argv[i];
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
if (dimacs_specified && dimacs_path && !File::exists (dimacs_path))
|
|
APPERR ("DIMACS input file '%s' does not exist", dimacs_path);
|
|
if (read_solution_path && !File::exists (read_solution_path))
|
|
APPERR ("solution file '%s' does not exist", read_solution_path);
|
|
if (dimacs_specified && dimacs_path &&
|
|
proof_specified && proof_path &&
|
|
!strcmp (dimacs_path, proof_path) && strcmp (dimacs_path, "-"))
|
|
APPERR ("DIMACS input file '%s' also specified as DRAT proof file",
|
|
dimacs_path);
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
// The '--less' option is not fully functional yet (it is also not
|
|
// mentioned in the 'usage' message yet). It only works as expected if
|
|
// you let the solver run until it exits. The goal is to have a similar
|
|
// experience as with 'git diff' if the terminal is too small for the
|
|
// printed messages, but this needs substantial hacking.
|
|
//
|
|
// TODO: add proper forking, waiting, signal catching & propagating ...
|
|
//
|
|
FILE * less_pipe;
|
|
if (less) {
|
|
assert (isatty (1));
|
|
less_pipe = popen ("less -r", "w");
|
|
if (!less_pipe)
|
|
APPERR ("could not execute and open pipe to 'less -r' command");
|
|
dup2 (fileno (less_pipe), 1);
|
|
} else less_pipe = 0;
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
if (read_solution_path && !get ("check")) set ("--check");
|
|
#ifndef QUIET
|
|
if (!get ("quiet")) {
|
|
solver->section ("banner");
|
|
solver->message ("%sCaDiCaL Radically Simplified CDCL SAT Solver%s",
|
|
tout.bright_magenta_code (), tout.normal_code ());
|
|
solver->message ("%s%s%s",
|
|
tout.bright_magenta_code (), copyright (), tout.normal_code ());
|
|
solver->message ();
|
|
CaDiCaL::Solver::build (stdout, "c ");
|
|
}
|
|
#endif
|
|
if (preprocessing > 0 || localsearch > 0 ||
|
|
#ifndef __WIN32
|
|
time_limit >= 0 ||
|
|
#endif
|
|
conflict_limit >= 0 || decision_limit >= 0) {
|
|
solver->section ("limit");
|
|
if (preprocessing > 0) {
|
|
solver->message (
|
|
"enabling %d initial rounds of preprocessing (due to '%s')",
|
|
preprocessing, preprocessing_specified);
|
|
solver->limit ("preprocessing", preprocessing);
|
|
}
|
|
if (localsearch > 0) {
|
|
solver->message (
|
|
"enabling %d initial rounds of local search (due to '%s')",
|
|
localsearch, localsearch_specified);
|
|
solver->limit ("localsearch", localsearch);
|
|
}
|
|
#ifndef __WIN32
|
|
if (time_limit >= 0) {
|
|
solver->message (
|
|
"setting time limit to %d seconds real time (due to '-t %s')",
|
|
time_limit, time_limit_specified);
|
|
Signal::alarm (time_limit);
|
|
solver->connect_terminator (this);
|
|
}
|
|
#endif
|
|
if (conflict_limit >= 0) {
|
|
solver->message (
|
|
"setting conflict limit to %d conflicts (due to '%s')",
|
|
conflict_limit, conflict_limit_specified);
|
|
bool succeeded = solver->limit ("conflicts", conflict_limit);
|
|
assert (succeeded), (void) succeeded;
|
|
}
|
|
if (decision_limit >= 0) {
|
|
solver->message (
|
|
"setting decision limit to %d decisions (due to '%s')",
|
|
decision_limit, decision_limit_specified);
|
|
bool succeeded = solver->limit ("decisions", decision_limit);
|
|
assert (succeeded), (void) succeeded;
|
|
}
|
|
}
|
|
if (verbose () || proof_specified) solver->section ("proof tracing");
|
|
if (proof_specified) {
|
|
if (!proof_path) {
|
|
const bool force_binary = (isatty (1) && get ("binary"));
|
|
if (force_binary) set ("--no-binary");
|
|
solver->message ("writing %s proof trace to %s'<stdout>'%s",
|
|
(get ("binary") ? "binary" : "non-binary"),
|
|
tout.green_code (), tout.normal_code ());
|
|
if (force_binary)
|
|
solver->message (
|
|
"connected to terminal thus non-binary proof forced");
|
|
solver->trace_proof (stdout, "<stdout>");
|
|
} else if (!solver->trace_proof (proof_path))
|
|
APPERR ("can not open and write DRAT proof to '%s'", proof_path);
|
|
else
|
|
solver->message (
|
|
"writing %s proof trace to %s'%s'%s",
|
|
(get ("binary") ? "binary" : "non-binary"),
|
|
tout.green_code (), proof_path, tout.normal_code ());
|
|
} else solver->verbose (1, "will not generate nor write DRAT proof");
|
|
solver->section ("parsing input");
|
|
dimacs_name = dimacs_path ? dimacs_path : "<stdin>";
|
|
string help;
|
|
if (!dimacs_path) {
|
|
help += " ";
|
|
help += tout.magenta_code ();
|
|
help += "(use '-h' for a list of common options)";
|
|
help += tout.normal_code ();
|
|
}
|
|
solver->message ("reading DIMACS file from %s'%s'%s%s",
|
|
tout.green_code (), dimacs_name, tout.normal_code (), help.c_str ());
|
|
bool incremental;
|
|
vector<int> cube_literals;
|
|
if (dimacs_path)
|
|
err = solver->read_dimacs(dimacs_path, max_var, force_strict_parsing,
|
|
incremental, cube_literals);
|
|
else
|
|
err = solver->read_dimacs(stdin, dimacs_name, max_var, force_strict_parsing,
|
|
incremental, cube_literals);
|
|
if (err) APPERR ("%s", err);
|
|
if (read_solution_path) {
|
|
solver->section ("parsing solution");
|
|
solver->message ("reading solution file from '%s'", read_solution_path);
|
|
if ((err = solver->read_solution (read_solution_path)))
|
|
APPERR ("%s", err);
|
|
}
|
|
|
|
solver->section ("options");
|
|
if (optimize > 0) {
|
|
solver->optimize (optimize);
|
|
solver->message ();
|
|
}
|
|
solver->options ();
|
|
|
|
int res = 0;
|
|
|
|
if (incremental) {
|
|
bool reporting = get ("report") > 1 || get ("verbose") > 0;
|
|
if (!reporting) set ("report", 0);
|
|
if (!reporting) solver->section ("incremental solving");
|
|
size_t cubes = 0, solved = 0;
|
|
size_t satisfiable = 0, unsatisfiable = 0, inconclusive = 0;
|
|
#ifndef QUIET
|
|
bool quiet = get ("quiet");
|
|
struct { double start, delta, sum; } time = { 0, 0, 0 };
|
|
#endif
|
|
for (auto lit : cube_literals)
|
|
if (!lit)
|
|
cubes++;
|
|
if (!reporting) {
|
|
if (cubes)
|
|
solver->message ("starting to solve %zu cubes", cubes),
|
|
solver->message ();
|
|
else
|
|
solver->message ("no cube to solve");
|
|
}
|
|
vector<int> cube, failed;
|
|
for (auto lit : cube_literals) {
|
|
if (lit) cube.push_back (lit);
|
|
else {
|
|
reverse (cube.begin (), cube.end ());
|
|
for (auto other : cube)
|
|
solver->assume (other);
|
|
if (solved++) {
|
|
if (conflict_limit >= 0)
|
|
(void) solver->limit ("conflicts", conflict_limit);
|
|
if (decision_limit >= 0)
|
|
(void) solver->limit ("decisions", decision_limit);
|
|
}
|
|
#ifndef QUIET
|
|
char buffer[160];
|
|
if (!quiet) {
|
|
if (reporting) {
|
|
sprintf (buffer, "solving cube %zu / %zu %.0f%%",
|
|
solved, cubes, percent (solved, cubes));
|
|
solver->section (buffer);
|
|
}
|
|
time.start = absolute_process_time ();
|
|
}
|
|
#endif
|
|
res = solver->solve ();
|
|
#ifndef QUIET
|
|
if (!quiet) {
|
|
time.delta = absolute_process_time () - time.start;
|
|
time.sum += time.delta;
|
|
sprintf (buffer,
|
|
"%s"
|
|
"in %.3f sec "
|
|
"(%.0f%% after %.2f sec at %.0f ms/cube)"
|
|
"%s",
|
|
tout.magenta_code (),
|
|
time.delta,
|
|
percent (solved, cubes),
|
|
time.sum,
|
|
relative (1e3*time.sum, solved),
|
|
tout.normal_code ());
|
|
if (reporting)
|
|
solver->message ();
|
|
const char * cube_str, * status_str, * color_code;
|
|
if (res == 10) {
|
|
cube_str = "CUBE";
|
|
color_code = tout.green_code ();
|
|
status_str = "SATISFIABLE";
|
|
} else if (res == 20) {
|
|
cube_str = "CUBE";
|
|
color_code = tout.cyan_code ();
|
|
status_str = "UNSATISFIABLE";
|
|
} else {
|
|
cube_str = "cube";
|
|
color_code = tout.magenta_code ();
|
|
status_str = "inconclusive";
|
|
}
|
|
const char * fmt;
|
|
if (reporting) fmt = "%s%s %zu %s%s %s";
|
|
else fmt = "%s%s %zu %-13s%s %s";
|
|
solver->message (fmt,
|
|
color_code, cube_str, solved, status_str,
|
|
tout.normal_code (), buffer);
|
|
}
|
|
#endif
|
|
if (res == 10) {
|
|
satisfiable++;
|
|
break;
|
|
} else if (res == 20) {
|
|
unsatisfiable++;
|
|
for (auto other : cube)
|
|
if (solver->failed (other))
|
|
failed.push_back (other);
|
|
for (auto other : failed)
|
|
solver->add (-other);
|
|
solver->add (0);
|
|
failed.clear ();
|
|
} else {
|
|
assert (!res);
|
|
inconclusive++;
|
|
if (timesup)
|
|
break;
|
|
}
|
|
cube.clear ();
|
|
}
|
|
}
|
|
solver->section ("incremental summary");
|
|
solver->message ("%zu cubes solved %.0f%%",
|
|
solved, percent (solved, cubes));
|
|
solver->message ("%zu cubes inconclusive %.0f%%",
|
|
inconclusive, percent (inconclusive, solved));
|
|
solver->message ("%zu cubes unsatisfiable %.0f%%",
|
|
unsatisfiable, percent (unsatisfiable, solved));
|
|
solver->message ("%zu cubes satisfiable %.0f%%",
|
|
satisfiable, percent (satisfiable, solved));
|
|
|
|
if (inconclusive && res == 20)
|
|
res = 0;
|
|
} else {
|
|
solver->section ("solving");
|
|
res = solver->solve ();
|
|
}
|
|
|
|
if (proof_specified) {
|
|
solver->section ("closing proof");
|
|
solver->flush_proof_trace ();
|
|
solver->close_proof_trace ();
|
|
}
|
|
|
|
if (output_path) {
|
|
solver->section ("writing output");
|
|
solver->message ("writing simplified CNF to DIMACS file %s'%s'%s",
|
|
tout.green_code (), output_path, tout.normal_code ());
|
|
err = solver->write_dimacs (output_path, max_var);
|
|
if (err) APPERR ("%s", err);
|
|
}
|
|
|
|
if (extension_path) {
|
|
solver->section ("writing extension");
|
|
solver->message ("writing extension stack to %s'%s'%s",
|
|
tout.green_code (), extension_path, tout.normal_code ());
|
|
err = solver->write_extension (extension_path);
|
|
if (err) APPERR ("%s", err);
|
|
}
|
|
|
|
solver->section ("result");
|
|
|
|
FILE * write_result_file = stdout;
|
|
if (write_result_path)
|
|
{
|
|
write_result_file = fopen (write_result_path, "w");
|
|
if (!write_result_file)
|
|
APPERR ("could not write solution to '%s'", write_result_path);
|
|
solver->message ("writing result to '%s'", write_result_path);
|
|
}
|
|
|
|
if (res == 10) {
|
|
fputs ("s SATISFIABLE\n", write_result_file);
|
|
if (witness)
|
|
print_witness (write_result_file);
|
|
} else if (res == 20) fputs ("s UNSATISFIABLE\n", write_result_file);
|
|
else fputs ("c UNKNOWN\n", write_result_file);
|
|
fflush (write_result_file);
|
|
if (write_result_path)
|
|
fclose (write_result_file);
|
|
solver->statistics ();
|
|
solver->resources ();
|
|
solver->section ("shutting down");
|
|
solver->message ("exit %d", res);
|
|
if (less_pipe) {
|
|
close (1);
|
|
pclose (less_pipe);
|
|
}
|
|
#ifndef __WIN32
|
|
if (time_limit > 0)
|
|
alarm (0);
|
|
#endif
|
|
|
|
return res;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// The real initialization is delayed.
|
|
|
|
void App::init () {
|
|
|
|
assert (!solver);
|
|
|
|
#ifndef __WIN32
|
|
time_limit = -1;
|
|
#endif
|
|
force_strict_parsing = 1;
|
|
force_writing = false;
|
|
max_var = 0;
|
|
timesup = false;
|
|
|
|
// Call 'new Solver' only after setting 'reportdefault' and do not
|
|
// add this call to the member initialization above. This is because for
|
|
// stand-alone usage the default report default value is 'true' while for
|
|
// library usage it should remain 'false'. See the explanation in
|
|
// 'options.hpp' related to 'reportdefault' for details.
|
|
|
|
CaDiCaL::Options::reportdefault = 1;
|
|
solver = new Solver ();
|
|
Signal::set (this);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
App::App () : solver (0) { } // Only partially initialize the app.
|
|
|
|
App::~App () {
|
|
if (!solver) return; // Only partially initialized.
|
|
Signal::reset ();
|
|
delete solver;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
#ifndef QUIET
|
|
|
|
void App::signal_message (const char * msg, int sig) {
|
|
solver->message (
|
|
"%s%s %ssignal %d%s (%s)%s",
|
|
tout.red_code (), msg,
|
|
tout.bright_red_code (), sig,
|
|
tout.red_code (), Signal::name (sig),
|
|
tout.normal_code ());
|
|
}
|
|
|
|
#endif
|
|
|
|
void App::catch_signal (int sig) {
|
|
#ifndef QUIET
|
|
if (!get ("quiet")) {
|
|
solver->message ();
|
|
signal_message ("caught", sig);
|
|
solver->section ("result");
|
|
solver->message ("UNKNOWN");
|
|
solver->statistics ();
|
|
solver->resources ();
|
|
solver->message ();
|
|
signal_message ("raising", sig);
|
|
}
|
|
#else
|
|
(void) sig;
|
|
#endif
|
|
}
|
|
|
|
void App::catch_alarm () {
|
|
// Both approaches work. We keep them here for illustration purposes.
|
|
#if 0 // THIS IS AN ALTERNATIVE WE WANT TO KEEP AROUND.
|
|
solver->terminate (); // Immediate asynchronous call into solver.
|
|
#else
|
|
timesup = true; // Wait for solver to call 'App::terminate ()'.
|
|
#endif
|
|
}
|
|
|
|
} // end of 'namespace CaDiCaL'
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// The actual app is allocated on the stack and then its 'main' function is
|
|
// called. This looks like that there are no static variables, which is not
|
|
// completely true, since both the signal handler connected to the app as
|
|
// well as the terminal have statically allocated components as well as the
|
|
// options table 'Options::table'. All are shared among solvers.
|
|
|
|
int main (int argc, char ** argv) {
|
|
CaDiCaL::App app;
|
|
int res = app.main (argc, argv);
|
|
return res;
|
|
}
|