├── .gitignore ├── Cargo.toml ├── README.md ├── bench.pl ├── frat.c ├── makefile └── src ├── backparser.rs ├── dimacs.rs ├── drat_trim.rs ├── dratchk.rs ├── elab.rs ├── from_drat.rs ├── from_pr.rs ├── main.rs ├── midvec.rs ├── parser.rs ├── perm_clause.rs ├── serialize.rs ├── stat.rs ├── strip_frat.rs └── to_cnf.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.cnf 4 | *.p 5 | a.out 6 | .vscode/launch.json 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "frat-rs" 3 | version = "0.1.0" 4 | authors = ["Mario Carneiro "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [profile.release] 10 | debug = true 11 | 12 | [features] 13 | ascii = [] 14 | 15 | [dependencies] 16 | byteorder = "1.4.2" 17 | arrayvec = "0.5.2" 18 | either = "1.6.1" 19 | rand = "0.8" 20 | slab = "0.4.2" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The FRAT format and FRAT-rs 2 | 3 | FRAT-rs is a toolchain for processing and transforming files in the [FRAT format](https://link.springer.com/chapter/10.1007/978-3-030-72016-2_4). 4 | 5 | ## Build 6 | 7 | You must have the `rust` toolchain, including `cargo`, installed. Then you can build with: 8 | 9 | ``` 10 | cargo clean 11 | cargo build --release 12 | ``` 13 | 14 | This builds the FRAT toolchain that reads and writes binary FRAT files. In case you need ascii FRAT format, build with: 15 | 16 | ``` 17 | cargo clean 18 | cargo build --features=ascii --release 19 | ``` 20 | 21 | ## Usage 22 | 23 | FRAT-rs can be compiled using `make`. (It is written in Rust, so you will need to 24 | [get Rust](https://rustup.rs/) first to put `cargo` in your path.) 25 | 26 | * `frat-rs elab FRATFILE [--full] [-s|-ss] [-m[NUM]] [DIMACSFILE [LRATFILE] [-v] [-c]]`: 27 | Elaborates `FRATFILE`, the unsatisfiability proof of `DIMACSFILE`, 28 | and produces the corresponding `LRATFILE`. 29 | 30 | * If `--full` is specified, the entire FRAT file is checked, 31 | including steps that do not contribute to the final contradiction. 32 | (The default is to skip these steps, so we might generate a valid proof 33 | without ever noticing that the step that was skipped is not well formed.) 34 | 35 | * If `-s` is specified, the FRAT file is checked in "strict mode", which means 36 | that bogus proofs in `l` steps will be rejected. (The default behavior 37 | is to instead fall back on no-hint mode if the hint is wrong.) 38 | 39 | * If `-ss` is specified, the FRAT file is checked in "extra-strict mode", 40 | in which `l` steps cannot be omitted. 41 | 42 | * If `-m` is specified, the intermediate file (between FRAT and LRAT) will 43 | be generated in memory instead of on disk, which might be faster. 44 | The optional `NUM` argument is a size hint for the initial allocation in 45 | bytes, which defaults to 5 times the size of the `FRATFILE`. 46 | 47 | * If `DIMACSFILE` is specified, the resulting output will be checked against 48 | the given CNF, otherwise only the first phase of elaboration will run, 49 | producing a `FRATFILE.temp` file but no other output. 50 | (This temporary file is useful as input to `refrat`.) 51 | 52 | * If `LRATFILE` is specified, the output will be placed in `LRATFILE`, 53 | otherwise the elaborator will run but no output will be produced. 54 | 55 | * If `-v` is specified, the LRAT file is passed directly to `lratchk`. 56 | Omitting `LRATFILE` and specifying `-v` is a way to verify FRAT files 57 | without otherwise generating output. 58 | 59 | * If `-c` is specified (requires `LRATFILE`), comment lines from the original 60 | FRAT file will be transmitted to the `LRATFILE`. This is a good way to align 61 | important points in the proof with the output, but it is disabled by default 62 | since some LRAT checkers don't support comments. 63 | 64 | This is the main subcommand you want to use, if you are a solver developer 65 | producing FRAT files. 66 | 67 | * `frat-rs lratchk DIMACSFILE LRATFILE`: 68 | Checks `LRATFILE` against the input problem `DIMACSFILE`. 69 | 70 | This is essentially the same as 71 | [`lrat-check.c`](https://github.com/marijnheule/drat-trim/blob/master/lrat-check.c) 72 | but it is more robust (at the time of writing) and has 73 | no known bugs. It is provided as a convenience for FRAT and LRAT file testing, 74 | but for actual high assurance scenarios you should use `elab` to generate an 75 | LRAT file and then use a formally verified LRAT checker like 76 | [`cake_lpr`](https://github.com/tanyongkiam/cake_lpr) to check the resulting file. 77 | 78 | * `frat-rs stat FRATFILE`: 79 | Analyzes `FRATFILE` and displays statistics 80 | 81 | * `frat-rs strip-frat FRATFILE NEWFRATFILE`: 82 | Processes `FRATFILE`, and produces a corresponding `NEWFRATFILE` with 0% annotations 83 | 84 | * `frat-rs refrat ELABFILE FRATFILE`: 85 | Processes `ELABFILE`, a temporary file produced by the first elaboration 86 | pass of frat-rs, and produces `FRATFILE`, a corresponding FRAT proof with 87 | 100% annotations 88 | 89 | * `frat-rs to-cnf FRATFILE > DIMACSFILE`: 90 | FRAT files contain a copy of the CNF inside them. This command constructs 91 | a CNF file that `FRATFILE` could be a proof of, and writes it to stdout 92 | (or pipes it to `DIMACSFILE` in this example) 93 | 94 | * `frat-rs from-drat DIMACSFILE DRATFILE FRATFILE`: 95 | Processes `DIMACSFILE` and `DRATFILE` to produce a corresponding `FRATFILE` 96 | with 0% annotations. Note that despite the name, this also works on PR files, 97 | and will translate them into FRAT files with PR steps. 98 | 99 | * `frat-rs from-pr DIMACSFILE PRFILE FRATFILE`: 100 | Processes `DIMACSFILE` and `PRFILE` to produce a corresponding `FRATFILE` 101 | with no PR steps. This implements the 102 | [`pr2drat`](https://github.com/marijnheule/pr2drat) algorithm, but with proofs 103 | supplied in the resulting FRAT file. 104 | 105 | Note that `elab` can directly handle PR steps in FRAT files, and they will 106 | be preserved in the output (resulting in LPR files instead of LRAT). So the 107 | main reason you would use this command is if you want pure LRAT output, 108 | or just as another way to slice data to get another data set. 109 | 110 | * Experimental subcommands: 111 | 112 | * `frat-rs drat-trim`: A clone of 113 | [`drat-trim`](https://github.com/marijnheule/drat-trim/) in true 114 | "Rewrite it in Rust" fashion. It has exactly the same behavior and options 115 | as the original, see the README there for more info. 116 | 117 | * `frat-rs dratchk DIMACSFILE DRATFILE`: 118 | Checks `DRATFILE` against the input problem `DIMACSFILE` (unmaintained) 119 | -------------------------------------------------------------------------------- /bench.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env swipl 2 | 3 | % Prolog script for running benchmarks comparing FRAT-rs with DRAT-trim. 4 | % The script calls the following programs, which should be available in your enviroment: 5 | % 6 | % cadical : the CaDiCaL SAT solver (https://github.com/arminbiere/cadical) 7 | % hackdical : the modified, FRAT-producing version of CaDiCaL (https://github.com/digama0/cadical) 8 | % drat-trim : the DRAT proof checker (https://github.com/marijnheule/drat-trim) 9 | % lrat-checker : the LRAT checker included in DRAT-trim 10 | % frat-rs : the FRAT toolchain (https://github.com/digama0/frat) 11 | % 12 | % In addition, you need SWI-Prolog to run the script itself (https://www.swi-prolog.org/). 13 | % 14 | % Usage : bench.pl /path/to/DIMACS/file 15 | 16 | :- initialization(main, main). 17 | 18 | read_time(String, Time) :- 19 | string_concat("\tUser time (seconds): ", TimeString, String), 20 | number_string(Time, TimeString). 21 | 22 | read_missing(String, Missing) :- 23 | split_string(String, " ", "", [_, "missing", "proofs", Temp0]), 24 | string_concat("(", Temp1, Temp0), 25 | string_concat(MissingStr, "%)", Temp1), 26 | number_string(Missing, MissingStr). 27 | 28 | read_peak_mem(String, PeakMem) :- 29 | string_concat("\tMaximum resident set size (kbytes): ", PeakMemStr, String), 30 | number_string(PeakMem, PeakMemStr). 31 | 32 | read_avg_mem(String, AvgMem) :- 33 | string_concat("\tAverage resident set size (kbytes): ", AvgMemStr, String), 34 | number_string(AvgMem, AvgMemStr). 35 | 36 | read_item_core(Goal, Stream, Item) :- 37 | read_line_to_string(Stream, String), 38 | ( 39 | String = end_of_file -> 40 | ( 41 | write("Read item fail, EOF\n"), 42 | false 43 | ) ; 44 | ( 45 | call(Goal, String, Item) ; 46 | read_item_core(Goal, Stream, Item) 47 | ) 48 | ). 49 | 50 | read_item(Goal, File, Time) :- 51 | open(File, read, Stream), 52 | read_item_core(Goal, Stream, Time). 53 | 54 | run(Strings) :- 55 | atomic_list_concat(Strings, CMD), 56 | shell(CMD, _). 57 | 58 | run_and_time(Strings, TIME) :- 59 | atomic_list_concat(Strings, CMD), 60 | atomic_list_concat(["time -v ", CMD, " 2> temp"], TIME_CMD), 61 | shell(TIME_CMD, _), 62 | read_item(read_time, "temp", TIME), 63 | delete_file("temp"). 64 | 65 | run_and_measure(Strings, TIME, PEAK_MEM) :- 66 | atomic_list_concat(Strings, CMD), 67 | format('Using command ~w\n', CMD), 68 | atomic_list_concat(["time -v ", CMD, " 2> temp"], TIME_CMD), 69 | shell(TIME_CMD, _), 70 | read_item(read_time, "temp", TIME), 71 | read_item(read_peak_mem, "temp", PEAK_MEM), 72 | delete_file("temp"). 73 | 74 | print_and_log(Log, Format, Argument) :- 75 | format(Format, Argument), 76 | format(Log, Format, Argument). 77 | 78 | main([CNF_FILE]) :- 79 | 80 | open("bench_log", write, Log), 81 | 82 | write("\n------- Running Hackdical -------\n\n"), 83 | run_and_time(["hackdical -q ", CNF_FILE, " test.frat --lrat=true"], DIMACS_FRAT_TIME), 84 | print_and_log(Log, 'DIMACS-to-FRAT time : ~w seconds\n', DIMACS_FRAT_TIME), 85 | size_file("test.frat", FRAT_SIZE), % test.frat 86 | print_and_log(Log, 'FRAT file size : ~w bytes\n', FRAT_SIZE), 87 | 88 | write("\n------- Obtaining FRAT file statistics -------\n\n"), 89 | shell("frat-rs stat test.frat > frat_stats"), 90 | read_item(read_missing, "frat_stats", MISSING), 91 | print_and_log(Log, 'Missing hints : ~w%\n', MISSING), 92 | write("\nFrat statistics:\n\n"), 93 | shell("cat frat_stats"), 94 | delete_file("frat_stats"), 95 | 96 | write("\n------- Elaborating FRAT to LRAT -------\n\n"), 97 | run_and_measure(["frat-rs elab test.frat ", CNF_FILE, " test.lrat"], FRAT_LRAT_TIME, FRAT_LRAT_PEAK_MEM), % test.frat, test.frat.temp, test.lrat 98 | delete_file("test.frat"), % test.frat.temp, test.lrat 99 | print_and_log(Log, 'FRAT-to-LRAT time : ~w seconds\n', FRAT_LRAT_TIME), 100 | print_and_log(Log, 'FRAT-to-LRAT peak memory usage : ~w kb\n', FRAT_LRAT_PEAK_MEM), 101 | 102 | size_file("test.frat.temp", TEMP_SIZE), 103 | delete_file("test.frat.temp"), % test.lrat 104 | print_and_log(Log, 'TEMP file size : ~w bytes\n', TEMP_SIZE), 105 | size_file("test.lrat", FRAT_LRAT_SIZE), 106 | print_and_log(Log, 'LRAT-from-FRAT file size : ~w bytes\n', FRAT_LRAT_SIZE), 107 | 108 | write("\n------- Checking LRAT from FRAT (C) -------\n\n"), 109 | run_and_time(["lrat-check ", CNF_FILE, " test.lrat"], FRAT_LRAT_CHK_C_TIME), 110 | print_and_log(Log, 'LRAT-from-FRAT check time (C) : ~w seconds\n', FRAT_LRAT_CHK_C_TIME), 111 | 112 | write("\n------- Checking LRAT from FRAT (Rust) -------\n\n"), 113 | run(["frat-rs lratchk ", CNF_FILE, " test.lrat 2>&1"]), 114 | delete_file("test.lrat"), % test.frat 115 | 116 | write("\n------- Running Cadical -------\n\n"), 117 | run_and_time(["cadical -q ", CNF_FILE, " test.drat"], DIMACS_DRAT_TIME), 118 | print_and_log(Log, 'DIMACS-to-DRAT time : ~w seconds\n', DIMACS_DRAT_TIME), 119 | size_file("test.drat", DRAT_SIZE), 120 | print_and_log(Log, 'DRAT file size : ~w bytes\n', DRAT_SIZE), 121 | 122 | 123 | write("\n------- Elaborating DRAT to LRAT -------\n\n"), 124 | run_and_measure(["drat-trim ", CNF_FILE, " test.drat -L test.lrat"], DRAT_LRAT_TIME, DRAT_LRAT_PEAK_MEM), 125 | delete_file("test.drat"), % test.lrat 126 | print_and_log(Log, 'DRAT-to-LRAT time : ~w seconds\n', DRAT_LRAT_TIME), 127 | print_and_log(Log, 'DRAT-to-LRAT peak memory usage : ~w kb\n', DRAT_LRAT_PEAK_MEM), 128 | size_file("test.lrat", DRAT_LRAT_SIZE), 129 | print_and_log(Log, 'LRAT-from-DRAT file size : ~w bytes\n', DRAT_LRAT_SIZE), 130 | 131 | write("\n------- Checking LRAT from DRAT (C) -------\n\n"), 132 | run_and_time(["lrat-check ", CNF_FILE, " test.lrat"], DRAT_LRAT_CHK_C_TIME), 133 | print_and_log(Log, 'LRAT-from-DRAT check time (C) : ~w seconds\n', DRAT_LRAT_CHK_C_TIME), 134 | 135 | write("\n------- Checking LRAT from DRAT (Rust) -------\n\n"), 136 | run(["frat-rs lratchk ", CNF_FILE, " test.lrat 2>&1"]), 137 | delete_file("test.lrat"), % 138 | 139 | write("\n------- Bench Statistics -------\n\n"), 140 | format('DIMACS-to-FRAT time : ~w seconds\n', DIMACS_FRAT_TIME), 141 | format('FRAT file size : ~w bytes\n', FRAT_SIZE), 142 | format('Missing hints : ~w%\n', MISSING), 143 | format('FRAT-to-LRAT time : ~w seconds\n', FRAT_LRAT_TIME), 144 | format('FRAT-to-LRAT peak memory usage : ~w kb\n', FRAT_LRAT_PEAK_MEM), 145 | format('TEMP file size : ~w bytes\n', TEMP_SIZE), 146 | format('LRAT-from-FRAT file size : ~w bytes\n', FRAT_LRAT_SIZE), 147 | format('LRAT-from-FRAT check time (C) : ~w seconds\n', FRAT_LRAT_CHK_C_TIME), 148 | format('DIMACS-to-DRAT time : ~w seconds\n', DIMACS_DRAT_TIME), 149 | format('DRAT file size : ~w bytes\n', DRAT_SIZE), 150 | format('DRAT-to-LRAT time : ~w seconds\n', DRAT_LRAT_TIME), 151 | format('DRAT-to-LRAT peak memory usage : ~w kb\n', DRAT_LRAT_PEAK_MEM), 152 | format('LRAT-from-DRAT file size : ~w bytes\n', DRAT_LRAT_SIZE), 153 | format('LRAT-from-DRAT check time (C) : ~w seconds\n', DRAT_LRAT_CHK_C_TIME), 154 | 155 | atomic_list_concat( 156 | [ DIMACS_FRAT_TIME, FRAT_SIZE, MISSING, FRAT_LRAT_TIME, 157 | FRAT_LRAT_PEAK_MEM, TEMP_SIZE, FRAT_LRAT_SIZE, FRAT_LRAT_CHK_C_TIME, 158 | DIMACS_DRAT_TIME, DRAT_SIZE, DRAT_LRAT_TIME, DRAT_LRAT_PEAK_MEM, 159 | DRAT_LRAT_SIZE, DRAT_LRAT_CHK_C_TIME ], 160 | ", ", 161 | CSV 162 | ), 163 | format('CSV : ~w', CSV). 164 | -------------------------------------------------------------------------------- /frat.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define ERROR 1 5 | #define INIT 128 6 | #define push_back(buffer, val) { \ 7 | if (buffer ## Size == buffer ## Alloc) { \ 8 | buffer ## Alloc += buffer ## Alloc >> 1; \ 9 | buffer = (typeof(buffer)) realloc(buffer, sizeof(*buffer) * buffer ## Alloc); } \ 10 | buffer[buffer ## Size++] = val; } 11 | #define max(a, b) ({ int _x = a; int _y = b; _x < _y ? _y : _x; }) 12 | 13 | int* buffer = 0; 14 | int bufferAlloc = 0; 15 | int bufferSize = 0; 16 | 17 | typedef struct { 18 | int lit; 19 | int step; 20 | int necessary; 21 | int* hyps; 22 | } RUPstep; 23 | 24 | int nSteps = 1; 25 | RUPstep* RUPsteps = 0; 26 | int RUPstepsSize = 0; 27 | int RUPstepsAlloc = 0; 28 | int* RUPbuffer = 0; 29 | int RUPbufferSize = 0; 30 | int RUPbufferAlloc = 0; 31 | int* generation = 0; 32 | 33 | void print_lit_only(int lit, int gen) { 34 | if (gen == 0) printf("%d", lit); 35 | else if (gen > 0) printf("%d.%d", lit, gen); 36 | else printf("%d.%d", -lit, -gen); 37 | } 38 | void print_lit(int lit) { 39 | printf(" | "); 40 | print_lit_only(lit, generation[abs(lit) - 1]); 41 | } 42 | 43 | #define RUP_UP 1 44 | #define RUP_AT 2 45 | int RUP(int* steps, int* formula, int formulaSize, int* clause) { 46 | int localSteps = 1; 47 | 48 | // try unit propagation 49 | int* units = RUPbuffer; 50 | for (int i = 0; i < formulaSize; i++) 51 | if (buffer[formula[i]] && !buffer[formula[i]+1]) push_back(RUPbuffer, i); 52 | push_back(RUPbuffer, -1); 53 | for (int i = 0; i < formulaSize; i++) { 54 | int hyps = RUPbufferSize; 55 | push_back(RUPbuffer, steps[i]); 56 | for (int* p = buffer + formula[i]; *p; p++) { 57 | int step = 0; 58 | for (int* u = units; *u >= 0; u++) 59 | if (buffer[formula[*u]] == -*p) { 60 | push_back(RUPbuffer, steps[*u]); 61 | goto foundLit; 62 | } 63 | for (int* q = clause; *q; q++) { 64 | if (*q == *p) goto foundLit; 65 | } 66 | goto failUP; 67 | foundLit:; 68 | } 69 | push_back(RUPbuffer, 0); 70 | push_back(RUPsteps, ((RUPstep){0, 0, 0, RUPbuffer + hyps})); 71 | return RUP_UP; 72 | failUP: RUPbufferSize = hyps; 73 | } 74 | 75 | // try reverse unit propagation (asymmetric tautology) 76 | for (int* p = clause; *p; p++) 77 | push_back(RUPsteps, ((RUPstep){-*p, -(localSteps++), 0, 0})); 78 | while (1) { 79 | int progress = 0; 80 | for (int i = 0; i < formulaSize; i++) { 81 | int lit = 0; 82 | int hyps = RUPbufferSize; 83 | int trivial = 1; 84 | push_back(RUPbuffer, steps[i]); 85 | for (int* p = buffer + formula[i]; *p; p++) { 86 | int step = 0; 87 | for (int j = 0; j < RUPstepsSize; j++) 88 | if (RUPsteps[j].lit == -*p) { step = RUPsteps[j].step; break; } 89 | if (step != 0) { trivial = 0; push_back(RUPbuffer, step); } 90 | else if (lit) goto failRUP; 91 | else lit = *p; 92 | } 93 | for (int j = 0; j < RUPstepsSize; j++) 94 | if (RUPsteps[j].lit == lit) goto failRUP; 95 | push_back(RUPbuffer, 0); 96 | push_back(RUPsteps, ((RUPstep){lit, 97 | trivial ? steps[i] : -localSteps, 0, RUPbuffer + hyps})); 98 | localSteps++; 99 | if (!lit) return RUP_AT; 100 | progress = 1; 101 | continue; 102 | failRUP: RUPbufferSize = hyps; 103 | } 104 | if (!progress) { 105 | // printf(" failed\nRUP ="); 106 | // for (int j = 0; j < RUPstepsSize; j++) print_lit(RUPsteps[j].lit); 107 | // printf("\nformula:\n"); 108 | // for (int i = 0; i < formulaSize; i++) { 109 | // int taut = 0; 110 | // printf("%d: ", steps[i]); 111 | // for (int* p = buffer + formula[i]; *p; p++) 112 | // for (int j = 0; j < RUPstepsSize; j++) 113 | // if (RUPsteps[j].lit == *p) taut = 1; 114 | // for (int* p = buffer + formula[i]; *p; p++) { 115 | // int step = 0; 116 | // for (int j = 0; j < RUPstepsSize; j++) 117 | // if (RUPsteps[j].lit == -*p) goto skip; 118 | // print_lit(*p); 119 | // skip:; 120 | // } 121 | // if (taut) printf(" taut"); 122 | // printf("|\n"); 123 | // taut:; 124 | // } 125 | return 0; 126 | } 127 | } 128 | } 129 | 130 | int printRUP(int RUPresult, int begin, int end) { 131 | switch (RUPresult) { 132 | case RUP_UP: { 133 | printf("%d: UP", nSteps); 134 | for (int* p = RUPsteps[begin].hyps; *p; p++) printf(" %d", *p); 135 | } return 1; 136 | case RUP_AT: { 137 | int stack = RUPbufferSize; 138 | push_back(RUPbuffer, end-1); 139 | while (RUPbufferSize > stack) { 140 | RUPstep* s = RUPsteps + RUPbuffer[--RUPbufferSize]; 141 | s->necessary = 1; 142 | if (s->hyps && s->hyps[1]) 143 | for (int* p = s->hyps; *p; p++) if (*p < 0) { 144 | int i = begin + -*p - 1; 145 | if (!RUPsteps[i].necessary) push_back(RUPbuffer, i); 146 | } 147 | } 148 | for (int i = begin; i < end; i++) { 149 | RUPstep s = RUPsteps[i]; 150 | if (s.necessary) { 151 | if (s.hyps) { 152 | printf(" .%d: UP", -s.step); 153 | for (int* p = s.hyps; *p; p++) { 154 | if (*p > 0) printf(" %d", *p); 155 | else printf(" .%d", -*p); 156 | } 157 | if (s.lit) print_lit(s.lit); 158 | printf("\n"); 159 | } else { 160 | printf(" .%d: hyp", -s.step); 161 | print_lit(s.lit); 162 | printf("\n"); 163 | } 164 | } 165 | } 166 | printf("%d: AT", nSteps); 167 | } return 1; 168 | } 169 | return 0; 170 | } 171 | 172 | typedef struct { 173 | unsigned size; 174 | int uses, usesEnd; 175 | int conjuncts, positive; 176 | int RUPtype; 177 | int RUPresults; 178 | } RATresult; 179 | 180 | RATresult* RATresults = 0; 181 | int RATresultsSize = 0; 182 | int RATresultsAlloc = 0; 183 | 184 | RATresult* tryRAT(int* steps, int* formula, int formulaSize, int* clause, int lit) { 185 | push_back(RATresults, (RATresult){}); 186 | RATresult* r = RATresults + (RATresultsSize-1); 187 | r->uses = RUPbufferSize; 188 | for (int i = 0; i < formulaSize; i++) { 189 | for (int* p = buffer + formula[i]; *p; p++) { 190 | if (*p == lit) { push_back(RUPbuffer, i); r->positive = 1; break; } 191 | if (*p == -lit) { push_back(RUPbuffer, ~i); r->conjuncts++; break; } 192 | } 193 | } 194 | r->usesEnd = RUPbufferSize; 195 | r->size += r->usesEnd - r->uses; 196 | 197 | if (r->conjuncts) { 198 | if (r->positive) r->size++; 199 | for (int u = r->uses; u < r->usesEnd; u++) if (RUPbuffer[u] < 0) { 200 | int i = ~RUPbuffer[u]; 201 | for (int* p = buffer + formula[i]; *p; p++) if (*p != -lit) 202 | r->size++; 203 | } 204 | } else r->size++; 205 | 206 | if (r->conjuncts) { 207 | r->RUPresults = RUPbufferSize; 208 | for (int u = r->uses; u < r->usesEnd; u++) if (RUPbuffer[u] < 0) { 209 | push_back(RUPbuffer, 0); 210 | push_back(RUPbuffer, 0); 211 | push_back(RUPbuffer, 0); 212 | } 213 | int k = r->RUPresults; 214 | for (int u = r->uses; u < r->usesEnd; u++) if (RUPbuffer[u] < 0) { 215 | int i = ~RUPbuffer[u]; 216 | RUPbuffer[k++] = RUPbufferSize; 217 | RUPbuffer[k++] = RUPstepsSize; 218 | int* resolvent = RUPbuffer + RUPbufferSize; 219 | for (int* p = buffer + formula[i]; *p; p++) if (*p != -lit) { 220 | r->size++; 221 | push_back(RUPbuffer, *p); 222 | } 223 | for (int* p = clause; *p; p++) if (*p != lit) { 224 | r->size++; 225 | push_back(RUPbuffer, *p); 226 | } 227 | push_back(RUPbuffer, 0); 228 | r->RUPtype = RUP(steps, formula, formulaSize, resolvent); 229 | RUPbuffer[k++] = RUPstepsSize; 230 | switch (r->RUPtype) { 231 | case RUP_UP: r->size++; break; 232 | case RUP_AT: r->size += RUPstepsSize - RUPbuffer[k-1]; break; 233 | default: r->usesEnd = u+1; return 0; 234 | } 235 | } 236 | } 237 | r->size++; 238 | return r; 239 | } 240 | 241 | int main(int argc, char** argv) { 242 | if (argc != 3) return 1; 243 | FILE* inputFile = fopen(argv[1], "r"); 244 | if (inputFile == NULL) { 245 | printf("error opening \"%s\".\n", argv[1]); return ERROR; } 246 | 247 | int nVars = 0; 248 | long nClauses = 0; 249 | 250 | inputLoop: switch (getc_unlocked(inputFile)) { 251 | case 'c': while (getc_unlocked(inputFile) != '\n'); goto inputLoop; 252 | case 'p': { 253 | if (fscanf(inputFile, " cnf %i %li \n", &nVars, &nClauses) == 2) 254 | break; 255 | } // fallthrough 256 | default: printf("error reading input file\n"); return ERROR; 257 | } 258 | int* formula = (int*)malloc(sizeof(int) * nClauses); 259 | int* steps = (int*)malloc(sizeof(int) * nClauses); 260 | 261 | bufferAlloc = INIT; 262 | buffer = (int*)malloc(sizeof(int) * bufferAlloc); 263 | bufferSize = 0; 264 | 265 | int clauseStart = 0; 266 | for (int clauses = 0; clauses < nClauses;) { 267 | int lit = 0; 268 | if (fscanf(inputFile, " %i ", &lit) != 1) return 2; 269 | push_back(buffer, lit); 270 | if (!lit) { 271 | steps[clauses] = nSteps++; 272 | formula[clauses++] = clauseStart; 273 | clauseStart = bufferSize; 274 | } 275 | } 276 | fclose(inputFile); 277 | 278 | int generationAlloc = nVars; 279 | generation = (int*)calloc(generationAlloc, sizeof(int)); 280 | for (int i = 0; i < nClauses; i++) { 281 | printf("%d: hyp", steps[i]); 282 | for (int* p = buffer + formula[i]; *p; p++) print_lit(*p); 283 | printf("\n"); 284 | } 285 | FILE* proofFile = fopen(argv[2], "r"); 286 | if (proofFile == NULL) { 287 | printf("error opening \"%s\".\n", argv[2]); return ERROR; } 288 | 289 | int binaryMode = 0; 290 | { int c1 = getc_unlocked(proofFile); 291 | if (c1 == 'a') binaryMode = 1; 292 | else if (c1 == 'd') { 293 | int c2 = getc_unlocked(proofFile); 294 | binaryMode = c2 != ' '; // bad luck if you try to delete variable 32 295 | ungetc(c2, proofFile); 296 | } else binaryMode = 0; 297 | ungetc(c1, proofFile); } 298 | 299 | int formulaAlloc = nClauses; 300 | int formulaSize = nClauses; 301 | int stepsAlloc = nClauses; 302 | int stepsSize = nClauses; 303 | 304 | RUPstepsAlloc = INIT; 305 | RUPsteps = (RUPstep*)malloc(sizeof(RUPstep) * RUPstepsAlloc); 306 | RUPbufferAlloc = INIT; 307 | RUPbuffer = (int*)malloc(sizeof(int) * RUPbufferAlloc); 308 | RATresultsAlloc = INIT; 309 | RATresults = (RATresult*)malloc(sizeof(RATresult) * RATresultsAlloc); 310 | 311 | while (1) { 312 | int k; 313 | int clauseIdx = bufferSize; 314 | if (binaryMode) { 315 | if ((k = getc_unlocked(proofFile)) == EOF) break; 316 | int c; 317 | while (1) { 318 | unsigned int ulit = 0, mul = 0; 319 | do { 320 | c = getc_unlocked(proofFile); 321 | ulit |= (c & 0x7F) << mul; 322 | mul += 7; 323 | } while (c & 0x80); 324 | int lit = (ulit & 1) ? -(ulit >> 1) : ulit >> 1; 325 | push_back(buffer, lit); 326 | if (!lit) break; 327 | } 328 | } else { 329 | if (fscanf(proofFile, " ")); 330 | k = getc_unlocked(inputFile); 331 | if (k == EOF) break; 332 | if (k != 'd') { ungetc(k, inputFile); k = 'a'; } 333 | int lit; 334 | while (1) { 335 | if (fscanf(proofFile, " %d", &lit) != 1) goto bigLoop; 336 | push_back(buffer, lit); 337 | if (!lit) break; 338 | } 339 | } 340 | int* clause = buffer + clauseIdx; 341 | 342 | // printf("formula:\n"); 343 | // for (int i = 0; i < formulaSize; i++) { 344 | // printf(" %d: ", steps[i]); 345 | // for (int* p = buffer + formula[i]; *p; p++) print_lit(*p); 346 | // printf("\n"); 347 | // } 348 | 349 | // printf("--- %c", k); 350 | // for (int* p = clause; *p; p++) printf(" %d", *p); 351 | // printf("\n"); 352 | 353 | switch (k) { 354 | case 'a': { 355 | for (int* p = clause; *p; p++) if (abs(*p) > generationAlloc) { 356 | printf("expand\n"); 357 | int a = generationAlloc; 358 | generationAlloc = max(a + (a >> 1), abs(*p)); 359 | generation = (int*) realloc(generation, sizeof(int) * generationAlloc); 360 | for (int i = a; i < generationAlloc; i++) generation[i] = 0; 361 | } 362 | 363 | RUPstepsSize = 0; 364 | RUPbufferSize = 0; 365 | int rup = RUP(steps, formula, formulaSize, clause); 366 | if (printRUP(rup, 0, RUPstepsSize)) { 367 | for (int* p = clause; *p; p++) print_lit(*p); 368 | printf("\n"); 369 | } else { 370 | // There is a literal l in C such that 371 | // for each clause C' in F with ~l in C', C >< C' has AT. 372 | // Then F, C |- _|_ implies F |- _|_ 373 | 374 | // Shows l|Di, ~l|C'i |- x'|Di, ~x'|C'i, x'|C 375 | // 1:Hyp |- l|Di 376 | // 2:Hyp |- ~l|C'i 377 | // 3:def x' := l|C' 378 | // 1':1,3:orL |- x'|Di 379 | // 4:andE |- ~C'|C'i <- not cnf 380 | // 2':3,2,4:orE |- ~x'|C'i 381 | // 5:AT |- C'i|C 382 | // 6:5:andI |- C'|C <- not cnf 383 | // 7:3,6:orR |- x'|C 384 | 385 | // 2:Hyp |- ~l|C'i 386 | // 3:def x' := C' 387 | // 4:andE |- ~C'|C'i <- not cnf 388 | // 2':3,4:def |- ~x'|C'i 389 | // 5:AT |- C'i|C 390 | // 6:5:andI |- C'|C <- not cnf 391 | // 7:3,6:def |- x'|C 392 | 393 | // 3:def x' := true 394 | // 1':3:trueI |- x'|Di 395 | // 7:3:trueI |- x'|C 396 | 397 | RATresultsSize = 0; 398 | RATresult* r = 0; 399 | int lit = 0; 400 | printf("attempts:"); 401 | for (int* p = clause; *p; p++) { 402 | RATresult* cur = tryRAT(steps, formula, formulaSize, clause, *p); 403 | if (cur) printf(" %d", cur->size); else printf(" fail"); 404 | if (cur && (!r || cur->size < r->size)) { r = cur; lit = *p; } 405 | } 406 | printf("\n"); 407 | if (!r) { r = RATresults; lit = clause[0]; } 408 | int oldgen = generation[abs(lit) - 1]; 409 | int newgen = (lit > 0) == (oldgen > 0) ? abs(oldgen)+1 : -(abs(oldgen)+1); 410 | int def = nSteps++; 411 | printf("~%d: ", def); 412 | print_lit_only(lit, newgen); 413 | printf(" := "); 414 | if (r->conjuncts) { 415 | char* startLine = "(\n ("; 416 | if (r->positive) { 417 | print_lit_only(lit, oldgen); 418 | startLine = " | (\n ("; 419 | } 420 | for (int u = r->uses; u < r->usesEnd; u++) if (RUPbuffer[u] < 0) { 421 | char* start = startLine; 422 | int i = ~RUPbuffer[u]; 423 | for (int* p = buffer + formula[i]; *p; p++) if (*p != -lit) { 424 | printf("%s", start); 425 | print_lit_only(*p, generation[abs(*p) - 1]); 426 | start = " | "; 427 | } 428 | startLine = ")\n & ("; 429 | } 430 | printf("))\n"); 431 | } else printf("true\n"); 432 | 433 | generation[abs(lit) - 1] = newgen; 434 | for (int u = r->uses; u < r->usesEnd; u++) { 435 | int i; 436 | if (RUPbuffer[u] >= 0) { i = RUPbuffer[u]; 437 | if (r->conjuncts) 438 | printf("~%d: orL %d %d", nSteps, def, steps[i]); 439 | else printf("~%d: trueI %d", nSteps, def); 440 | } else { i = ~RUPbuffer[u]; 441 | if (r->positive) 442 | printf("~%d: orE_andE %d %d", nSteps, def, steps[i]); 443 | else printf("~%d: andE %d", nSteps, def); 444 | } 445 | for (int* p = buffer + formula[i]; *p; p++) print_lit(*p); 446 | printf("\n"); 447 | steps[i] = nSteps++; 448 | } 449 | if (r->conjuncts) { 450 | int* rp = RUPbuffer + r->RUPresults; 451 | for (int u = r->uses; u < r->usesEnd; u++) if (RUPbuffer[u] < 0) { 452 | int i = ~RUPbuffer[u]; 453 | int* resolvent = RUPbuffer + *rp++; 454 | int begin = *rp++; 455 | int end = *rp++; 456 | if (!printRUP(r->RUPtype, begin, end)) 457 | printf("~%d: failed", nSteps); 458 | nSteps++; 459 | for (int* p = resolvent; *p; p++) print_lit(*p); 460 | printf("\n"); 461 | } 462 | if (r->positive) printf("%d: orR_andI %d", nSteps, def); 463 | else printf("%d: andI %d", nSteps, def); 464 | int newStep = nSteps; 465 | nSteps -= r->conjuncts; 466 | while (nSteps < newStep) printf(" %d", nSteps++); 467 | } else printf("%d: trueI %d", nSteps, def); 468 | print_lit(lit); 469 | for (int* p = clause; *p; p++) if (*p != lit) print_lit(*p); 470 | printf("\n"); 471 | } 472 | push_back(formula, clauseIdx); 473 | push_back(steps, nSteps); 474 | nSteps++; 475 | } break; 476 | case 'd': { 477 | for (int i = 0; i < formulaSize; i++) { 478 | int* p = buffer + formula[i], *q = clause; 479 | while (*p && *p == *q) {p++; q++;} 480 | if (*p == 0 && *q == 0) { 481 | printf("clear %d\n", steps[i]); 482 | while (++i < formulaSize) { 483 | formula[i-1] = formula[i]; 484 | steps[i-1] = steps[i]; 485 | } 486 | formulaSize--; 487 | stepsSize--; 488 | break; 489 | } 490 | } 491 | } break; 492 | default: printf("invalid step %d", k); return ERROR; 493 | } 494 | } bigLoop: 495 | fclose(proofFile); 496 | return 0; 497 | } 498 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := frat 2 | 3 | frat: ./src/*.rs 4 | cargo build --release 5 | mv ./target/release/frat-rs . 6 | 7 | clean: 8 | rm frat-rs 9 | -------------------------------------------------------------------------------- /src/backparser.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{self, Read, Seek, SeekFrom}; 3 | use super::parser::*; 4 | pub use super::parser::{Proof, Step, ElabStep}; 5 | 6 | pub struct VecBackParser(pub Vec); 7 | 8 | impl Iterator for VecBackParser { 9 | type Item = Segment; 10 | 11 | fn next(&mut self) -> Option { 12 | let (&n, most) = self.0.split_last()?; 13 | if n != 0 { panic!("expected 0 byte") } 14 | let i = most.iter().rposition(|&n| n == 0).map_or(0, |i| i + 1); 15 | Some(Bin.segment(|| i, self.0.drain(i..))) 16 | } 17 | } 18 | 19 | pub struct BackParser { 20 | file: File, 21 | remaining: usize, 22 | pos: usize, 23 | last_read: usize, 24 | buffers: Vec>, 25 | free: Vec>, 26 | mode: M, 27 | scan: M::BackScanState, 28 | } 29 | 30 | impl BackParser { 31 | pub fn new(mode: M, mut file: File) -> io::Result> { 32 | let len = file.metadata()?.len() as usize; 33 | let pos = len.checked_sub(1).map_or(0, |l| l % BUFFER_SIZE + 1); 34 | file.seek(SeekFrom::End(-(pos as i64)))?; 35 | let mut buf = Box::new([0; BUFFER_SIZE]); 36 | file.read_exact(&mut buf[..pos])?; 37 | Ok(BackParser { 38 | file, 39 | remaining: len / BUFFER_SIZE - if pos == BUFFER_SIZE {1} else {0}, 40 | pos, 41 | last_read: pos, 42 | buffers: vec![buf], 43 | free: Vec::new(), 44 | scan: mode.new_back_scan(), 45 | mode, 46 | }) 47 | } 48 | 49 | fn read_chunk(&mut self) -> io::Result>> { 50 | if self.remaining == 0 { return Ok(None) } 51 | let mut buf = self.free.pop().unwrap_or_else(|| Box::new([0; BUFFER_SIZE])); 52 | self.file.seek(SeekFrom::Current(-((BUFFER_SIZE + self.last_read) as i64)))?; 53 | self.file.read_exact(&mut *buf)?; 54 | self.last_read = BUFFER_SIZE; 55 | self.remaining -= 1; 56 | Ok(Some(buf)) 57 | } 58 | 59 | fn parse_segment_from(&mut self, b: usize, i: usize) -> Segment { 60 | let seg_start = || (self.remaining + (self.buffers.len() - (b + 1))) * BUFFER_SIZE + i; 61 | if b == 0 { 62 | let res = self.mode.segment(seg_start, self.buffers[0][i..self.pos].iter().copied()); 63 | self.pos = i; 64 | res 65 | } else { 66 | let res = self.mode.segment(seg_start, 67 | self.buffers[b][i..].iter() 68 | .chain(self.buffers[1..b].iter().rev().flat_map(|buf| buf.iter())) 69 | .chain(self.buffers[0][..self.pos].iter()).copied()); 70 | self.pos = i; 71 | self.free.extend(self.buffers.drain(0..b)); 72 | res 73 | } 74 | } 75 | } 76 | 77 | impl Iterator for BackParser { 78 | type Item = Segment; 79 | 80 | fn next(&mut self) -> Option { 81 | for b in 0.. { 82 | let buf: &[u8; BUFFER_SIZE] = match self.buffers.get(b) { 83 | None => match self.read_chunk().expect("could not read from proof file") { 84 | None => { 85 | if b == 1 && self.pos == 0 { break } 86 | return Some(self.parse_segment_from(b-1, 0)) 87 | }, 88 | Some(buf) => { self.buffers.push(buf); self.buffers.last().unwrap() } 89 | }, 90 | Some(buf) => buf 91 | }; 92 | if b == 0 { 93 | if self.pos != 0 { 94 | if let Some(i) = self.scan.back_scan(&buf[..self.pos-1]) { 95 | return Some(self.parse_segment_from(b, i)) 96 | } 97 | } 98 | } else if let Some(i) = self.scan.back_scan(buf) { 99 | return Some(self.parse_segment_from(b, i)) 100 | } 101 | } 102 | None 103 | } 104 | } 105 | 106 | pub struct StepIter(pub I); 107 | 108 | impl> Iterator for StepIter { 109 | type Item = Step; 110 | 111 | fn next(&mut self) -> Option { 112 | match self.0.next() { 113 | None => None, 114 | Some(Segment::Comment(s)) => Some(Step::Comment(s)), 115 | Some(Segment::Orig(idx, vec)) => Some(Step::Orig(idx, vec)), 116 | Some(Segment::Add(idx, vec)) => Some(Step::Add(idx, AddStep(vec), None)), 117 | Some(Segment::Del(idx, vec)) => Some(Step::Del(idx, vec)), 118 | Some(Segment::Reloc(relocs)) => Some(Step::Reloc(relocs)), 119 | Some(Segment::Final(idx, vec)) => Some(Step::Final(idx, vec)), 120 | Some(Segment::LProof(steps)) => match self.0.next() { 121 | Some(Segment::Add(idx, vec)) => 122 | Some(Step::Add(idx, AddStep(vec), Some(Proof::LRAT(steps)))), 123 | _ => panic!("'l' step not preceded by 'a' step") 124 | }, 125 | Some(Segment::Todo(idx)) => Some(Step::Todo(idx)), 126 | } 127 | } 128 | } 129 | 130 | pub struct ElabStepIter(pub I); 131 | 132 | impl> Iterator for ElabStepIter { 133 | type Item = ElabStep; 134 | 135 | fn next(&mut self) -> Option { 136 | match self.0.next() { 137 | None => None, 138 | Some(Segment::Comment(s)) => Some(ElabStep::Comment(s)), 139 | Some(Segment::Orig(idx, vec)) => Some(ElabStep::Orig(idx, vec)), 140 | Some(Segment::Add(_, _)) => panic!("add step has no proof"), 141 | Some(Segment::Del(idx, vec)) => 142 | {assert!(vec.is_empty()); Some(ElabStep::Del(idx))}, 143 | Some(Segment::Reloc(relocs)) => Some(ElabStep::Reloc(relocs)), 144 | Some(Segment::LProof(steps)) => match self.0.next() { 145 | Some(Segment::Add(idx, vec)) => 146 | Some(ElabStep::Add(idx, AddStep(vec), steps)), 147 | _ => panic!("'l' step not preceded by 'a' step") 148 | }, 149 | Some(Segment::Final(_, _)) => panic!("unexpected 'f' segment"), 150 | Some(Segment::Todo(_)) => self.next(), 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/dimacs.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 3 | enum Token { 4 | Nat(i64), 5 | Ident(Ident) 6 | } 7 | 8 | use self::Token::*; 9 | 10 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 11 | enum Ident { Comment, Problem, Cnf, Del } 12 | 13 | use self::Ident::*; 14 | 15 | #[derive(Debug, Clone)] 16 | struct Lexer { 17 | /// input iterator 18 | input: I, 19 | 20 | /// internal buffer to map to known keywords 21 | buffer: Vec, 22 | 23 | /// the current character that is being dispatched upon 24 | peek: u8, 25 | } 26 | 27 | impl> Lexer { 28 | fn from(input: I) -> Lexer { 29 | let mut lex = Lexer { 30 | input, 31 | buffer: Vec::new(), 32 | peek: 0 33 | }; 34 | lex.bump(); 35 | lex 36 | } 37 | 38 | fn bump_opt(&mut self) -> Option { 39 | let peeked = self.input.next()?; 40 | self.peek = peeked; 41 | Some(peeked) 42 | } 43 | 44 | fn bump(&mut self) -> u8 { 45 | self.peek = self.bump_opt().unwrap_or(0); 46 | self.peek 47 | } 48 | 49 | fn skip_line(&mut self) { 50 | while self.peek != b'\n' && self.peek != 0 { 51 | self.bump(); 52 | } 53 | } 54 | 55 | fn scan_comment(&mut self) -> Ident { 56 | self.skip_line(); 57 | Comment 58 | } 59 | 60 | fn scan_keyword(&mut self) -> Ident { 61 | self.buffer.clear(); 62 | self.buffer.push(self.peek); 63 | while (self.bump() as char).is_alphanumeric() { 64 | if self.buffer.len() < 3 { 65 | self.buffer.push(self.peek); 66 | } else { panic!("unknown keyword") } 67 | } 68 | match &*self.buffer { 69 | b"c" => self.scan_comment(), 70 | b"p" => Problem, 71 | b"cnf" => Cnf, 72 | b"d" => Del, 73 | _ => panic!("unknown keyword") 74 | } 75 | } 76 | 77 | fn scan_nat(&mut self) -> i64 { 78 | let mut val = (self.peek as char).to_digit(10) 79 | .expect("expected a digit to base 10: (0...9)") as i64; 80 | while let Some(parsed) = (self.bump() as char).to_digit(10) { 81 | val = val.checked_mul(10).unwrap().checked_add(parsed as i64).unwrap(); 82 | } 83 | val 84 | } 85 | } 86 | 87 | impl> Iterator for Lexer { 88 | type Item = Token; 89 | 90 | fn next(&mut self) -> Option { 91 | while (self.peek as char).is_whitespace() { 92 | self.bump(); 93 | } 94 | if self.peek == 0 { return None; } 95 | match self.peek { 96 | b'a'..=b'z' => match self.scan_keyword() { 97 | Comment => self.next(), 98 | tk => Some(Ident(tk)) 99 | }, 100 | b'0'..=b'9' => Some(Nat(self.scan_nat())), 101 | b'-' => { self.bump(); Some(Nat(-self.scan_nat())) }, 102 | _ => panic!("invalid token start") 103 | } 104 | } 105 | } 106 | 107 | pub type Clause = Box<[i64]>; 108 | 109 | pub struct DimacsIter(Lexer); 110 | 111 | impl> DimacsIter { 112 | pub fn from(input: I) -> (usize, usize, Self) { 113 | let mut lex = Lexer::from(input); 114 | match (lex.next(), lex.next(), lex.next(), lex.next()) { 115 | (Some(Ident(Problem)), Some(Ident(Cnf)), Some(Nat(vars)), Some(Nat(clauses))) => 116 | (vars.try_into().unwrap(), clauses.try_into().unwrap(), DimacsIter(lex)), 117 | _ => panic!("parse DIMACS failed") 118 | } 119 | } 120 | } 121 | 122 | impl> Iterator for DimacsIter { 123 | type Item = Vec; 124 | fn next(&mut self) -> Option> { 125 | let mut clause = Vec::new(); 126 | loop { 127 | match self.0.next()? { 128 | Nat(0) => break, 129 | Nat(lit) => clause.push(lit), 130 | _ => panic!("parse DIMACS failed") 131 | } 132 | } 133 | Some(clause) 134 | } 135 | } 136 | 137 | pub fn parse_dimacs_map(input: impl Iterator, f: impl FnMut(Vec) -> T) -> (usize, Vec) { 138 | let (vars, clauses, it) = DimacsIter::from(input); 139 | let mut fmla = Vec::with_capacity(clauses); 140 | fmla.extend(it.map(f)); 141 | (vars, fmla) 142 | } 143 | 144 | pub fn parse_dimacs(input: impl Iterator) -> (usize, Vec) { 145 | parse_dimacs_map(input, |x| x.into()) 146 | } 147 | -------------------------------------------------------------------------------- /src/drat_trim.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::{Display, Debug}, io::StdinLock}; 2 | use std::io::{self, Read, Write, Seek, SeekFrom, BufReader}; 3 | use std::ops::{Deref, DerefMut}; 4 | use std::fs::File; 5 | use std::mem; 6 | use std::time::Instant; 7 | use either::Either; 8 | use io::{BufRead, BufWriter, stdout}; 9 | use crate::{dimacs, midvec::MidVec, parser::{DRATParser, DRATStep}}; 10 | 11 | const TIMEOUT: u64 = 40000; 12 | const INIT: usize = 4; 13 | const BIGINIT: usize = 1000000; 14 | const INFOBITS: usize = 2; 15 | const ACTIVE: usize = 1; 16 | 17 | #[derive(Copy, Clone)] 18 | enum Mode { 19 | BackwardUnsat, 20 | ForwardUnsat, 21 | ForwardSat, 22 | } 23 | 24 | enum VerifyResult { 25 | Fail, 26 | Unsat, 27 | Derivation, 28 | } 29 | enum Warning { 30 | /// display warning messages 31 | Normal, 32 | /// suppress warning messages 33 | None, 34 | /// exit after first warning 35 | Hard, 36 | } 37 | 38 | #[repr(u8)] #[derive(Copy, Clone, Debug)] 39 | enum Assign { 40 | Unassigned, 41 | Assigned, 42 | Assumed, 43 | Mark, 44 | } 45 | 46 | impl Default for Assign { 47 | fn default() -> Self { Self::Unassigned } 48 | } 49 | impl Assign { 50 | #[inline] fn assigned(self) -> bool { !matches!(self, Assign::Unassigned) } 51 | } 52 | impl From for Assign { 53 | #[inline] fn from(b: bool) -> Self { 54 | if b {Assign::Assigned} else {Assign::Unassigned} 55 | } 56 | } 57 | 58 | fn get_hash(lits: &[i64]) -> usize { 59 | crate::perm_clause::get_clause_hash(lits).0 as u32 as usize % BIGINIT 60 | } 61 | 62 | #[derive(Copy, Clone)] 63 | struct ClauseId(usize); 64 | 65 | impl ClauseId { 66 | fn new(id: usize, active: bool) -> Self { Self(id << 1 | active as usize) } 67 | #[inline] fn id(self) -> usize { self.0 >> 1 } 68 | #[inline] fn active(self) -> bool { self.0 & ACTIVE != 0 } 69 | } 70 | 71 | impl Debug for ClauseId { 72 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 73 | if self.active() { write!(f, "*{}", self.id()) } else { write!(f, "{}", self.id()) } 74 | } 75 | } 76 | 77 | #[derive(Copy, Clone)] 78 | struct Witness(usize); 79 | 80 | impl Witness { 81 | #[inline] fn new(id: Option) -> Self { Self(id.unwrap_or(usize::MAX)) } 82 | #[inline] fn get(self) -> Option { 83 | if self.0 == usize::MAX {None} else {Some(self.0)} 84 | } 85 | } 86 | 87 | struct Clause { 88 | ida: ClauseId, 89 | pivot: i64, 90 | maxdep: usize, 91 | witness: Witness, 92 | lits: Vec, 93 | } 94 | 95 | impl Clause { 96 | fn as_ref(&self) -> ClauseRef<'_> { 97 | ClauseRef {ida: self.ida, lits: &self.lits} 98 | } 99 | 100 | #[inline] fn id(&self) -> usize { self.ida.id() } 101 | #[inline] fn active(&self) -> bool { self.ida.active() } 102 | #[inline] fn deactivate(&mut self) { self.ida.0 &= !ACTIVE } 103 | #[inline] fn activate(&mut self) { self.ida.0 |= ACTIVE } 104 | } 105 | 106 | impl Deref for Clause { 107 | type Target = Vec; 108 | fn deref(&self) -> &Vec { &self.lits } 109 | } 110 | impl DerefMut for Clause { 111 | fn deref_mut(&mut self) -> &mut Vec { &mut self.lits } 112 | } 113 | 114 | impl Display for Clause { 115 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 116 | self.as_ref().fmt(f) 117 | } 118 | } 119 | 120 | struct ClauseRef<'a> {ida: ClauseId, lits: &'a [i64]} 121 | 122 | impl<'a> Display for ClauseRef<'a> { 123 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 124 | write!(f, "[{}] ", self.ida.0)?; 125 | for &lit in self.lits { write!(f, "{} ", lit)? } 126 | write!(f, "0") 127 | } 128 | } 129 | 130 | #[derive(Copy, Clone)] 131 | struct Watch(usize); 132 | 133 | impl Watch { 134 | #[inline] fn new(idx: usize, mask: bool) -> Watch { 135 | Watch(idx << 1 | mask as usize) 136 | } 137 | fn clause(self) -> usize { self.0 >> 1 } 138 | fn mark(&mut self) { self.0 |= ACTIVE } 139 | } 140 | 141 | #[derive(Copy, Clone, PartialEq, Eq)] 142 | struct ProofStep(usize); 143 | 144 | impl ProofStep { 145 | const NULL: ProofStep = ProofStep(0); 146 | #[inline] fn add(idx: usize) -> ProofStep { ProofStep(idx << INFOBITS) } 147 | #[inline] fn del(idx: usize) -> ProofStep { ProofStep(idx << INFOBITS | 1) } 148 | #[inline] fn clause(self) -> usize { self.0 >> INFOBITS } 149 | #[inline] fn is_del(self) -> bool { self.0 & 1 != 0 } 150 | } 151 | 152 | struct TrackLen(R, usize); 153 | 154 | impl TrackLen { 155 | fn new(r: R) -> Self { Self(r, 0) } 156 | fn bytes_read(&self) -> usize { self.1 } 157 | } 158 | 159 | impl Read for TrackLen { 160 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 161 | let n = self.0.read(buf)?; 162 | self.1 += n; 163 | Ok(n) 164 | } 165 | } 166 | impl BufRead for TrackLen { 167 | fn fill_buf(&mut self) -> io::Result<&[u8]> { 168 | self.0.fill_buf() 169 | } 170 | 171 | fn consume(&mut self, amt: usize) { 172 | self.0.consume(amt); 173 | self.1 += amt; 174 | } 175 | } 176 | 177 | #[derive(Copy, Clone, PartialEq, Eq)] 178 | struct Reason(usize); 179 | 180 | impl Reason { 181 | const NONE: Self = Self(0); 182 | #[inline] fn new(clause_id: usize) -> Self { Self(clause_id + 1) } 183 | #[inline] fn get(self) -> Option { self.0.checked_sub(1) } 184 | } 185 | 186 | #[derive(Copy, Clone, PartialEq, Eq)] 187 | struct Dependency(i64); 188 | 189 | impl Dependency { 190 | fn new(id: i64, forced: bool) -> Self { Self(id << 1 | forced as i64) } 191 | #[inline] fn id(self) -> i64 { self.0 >> 1 } 192 | #[inline] fn forced(self) -> bool { self.0 & 1 != 0 } 193 | #[inline] fn is_pos(self) -> bool { self.0 >= 0 } 194 | } 195 | 196 | impl Debug for Dependency { 197 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 198 | if self.forced() { write!(f, "*{}", self.id()) } else { write!(f, "{}", self.id()) } 199 | } 200 | } 201 | 202 | struct SolverOpts { 203 | /// prints the core lemmas to the file LEMMAS (LRAT format) 204 | lrat_file: Option>, 205 | trace_file: Option>, 206 | /// prints the active clauses to the file ACTIVE (DIMACS format) 207 | active_file: Option>, 208 | /// time limit in seconds 209 | timeout: u64, 210 | mask: bool, 211 | /// if false, run in plain mode (i.e., ignore deletion information) 212 | delete: bool, 213 | /// force binary proof parse mode 214 | bin_mode: bool, 215 | /// optimize proof till fixpoint by repeating verification 216 | optimize: bool, 217 | /// compress core lemmas (emit binary proof) 218 | bin_output: bool, 219 | /// show progress bar 220 | bar: bool, 221 | backforce: bool, 222 | /// reduce mode 223 | reduce: bool, 224 | /// full mode (check all lemmas) 225 | full: bool, 226 | mode: Mode, 227 | /// more verbose output 228 | verb: bool, 229 | warning: Warning, 230 | /// delete proof file after parsing 231 | del_proof: bool, 232 | /// prints the unsatisfiable core to the file CORE (DIMACS format) 233 | core_str: Option, 234 | /// prints the core lemmas to the file LEMMAS (DRAT format) 235 | lemma_str: Option, 236 | start_time: Instant, 237 | } 238 | 239 | impl SolverOpts { 240 | fn new() -> Self { 241 | Self { 242 | core_str: None, 243 | active_file: None, 244 | lemma_str: None, 245 | lrat_file: None, 246 | trace_file: None, 247 | timeout: TIMEOUT, 248 | mask: false, 249 | verb: false, 250 | del_proof: false, 251 | backforce: false, 252 | optimize: false, 253 | warning: Warning::Normal, 254 | bar: false, 255 | mode: Mode::BackwardUnsat, 256 | delete: true, 257 | reduce: true, 258 | full: false, 259 | bin_mode: false, 260 | bin_output: false, 261 | start_time: Instant::now(), 262 | } 263 | } 264 | 265 | fn warn(&self, f: impl FnOnce()) { 266 | match self.warning { 267 | Warning::Normal => f(), 268 | Warning::None => {} 269 | Warning::Hard => { 270 | f(); 271 | std::process::exit(80); 272 | } 273 | } 274 | } 275 | } 276 | 277 | struct LRATLine { 278 | name: i64, 279 | clause: Box<[i64]>, 280 | chain: Box<[i64]> 281 | } 282 | struct Solver { 283 | opts: SolverOpts, 284 | db: Vec, 285 | false_stack: Vec, 286 | false_a: MidVec, 287 | forced: usize, 288 | processed: usize, 289 | count: usize, 290 | rat_mode: bool, 291 | rat_count: usize, 292 | num_active: usize, 293 | lrat_table: Box<[Option]>, 294 | num_lemmas: usize, 295 | rat_set: Vec, 296 | pre_rat: Vec, 297 | deps: Vec, 298 | max_var: i64, 299 | prep: bool, 300 | current: usize, 301 | num_removed: usize, 302 | time: ClauseId, 303 | num_vars: usize, 304 | num_clauses: usize, 305 | unit_stack: Vec, 306 | witness: Vec>, 307 | reason: Box<[Reason]>, 308 | num_resolve: usize, 309 | watch_list: MidVec>, 310 | opt_proof: Vec, 311 | formula: Vec, 312 | proof: Vec, 313 | } 314 | 315 | impl Deref for Solver { 316 | type Target = SolverOpts; 317 | fn deref(&self) -> &Self::Target { &self.opts } 318 | } 319 | 320 | fn creating(s: &Option, f: impl FnOnce(File) -> io::Result<()>) -> io::Result<()> { 321 | if let Some(s) = s { f(File::create(s)?) } else {Ok(())} 322 | } 323 | 324 | macro_rules! reason {($self:ident, $lit:expr) => { $self.reason[$lit.abs() as usize] }} 325 | 326 | macro_rules! assign {($self:ident, $lit:expr) => {{ 327 | let lit = $lit; 328 | $self.false_a[-lit] = Assign::Assigned; 329 | $self.false_stack.push(-lit); 330 | }}} 331 | 332 | fn pop_ext(vec: &[T], idx: &mut usize) -> Option { 333 | *idx = idx.checked_sub(1)?; 334 | Some(vec[*idx].clone()) 335 | } 336 | 337 | fn shrink_vec(vec: &mut Vec, size: usize) { 338 | let old = mem::replace(vec, Vec::with_capacity(size)); 339 | vec.extend(old); 340 | } 341 | 342 | fn write_lit(f: &mut impl Write, lit: i64) -> io::Result<()> { 343 | let mut l = lit.unsigned_abs() << 1; 344 | if lit < 0 { l += 1 } 345 | loop { 346 | f.write_all(&[if l <= 127 {l} else {128 + (l & 127)} as u8])?; 347 | l >>= 7; 348 | if l == 0 {return Ok(())} 349 | } 350 | } 351 | 352 | impl Solver { 353 | #[inline] fn reason(&mut self, lit: i64) -> &mut Reason { 354 | &mut reason!(self, lit) 355 | } 356 | 357 | fn assign(&mut self, lit: i64) { assign!(self, lit) } 358 | 359 | #[inline] fn add_watch(&mut self, clause: usize, lit: i64) { 360 | self.watch_list[lit].push(Watch::new(clause, self.opts.mask)) 361 | } 362 | 363 | fn remove_watch(&mut self, clause: usize, lit: i64) { 364 | let watch = &mut self.watch_list[lit]; 365 | if watch.len() > INIT && watch.capacity() > 2 * watch.len() { 366 | shrink_vec(watch, (3 * watch.len()) >> 1); 367 | } 368 | let i = watch.iter().position(|&w| w.clause() == clause).unwrap(); 369 | watch.swap_remove(i); 370 | } 371 | 372 | fn add_unit(&mut self, index: usize) { 373 | self.unit_stack.push(index) 374 | } 375 | 376 | fn pop_forced(&mut self) -> Option { 377 | pop_ext(&self.false_stack, &mut self.forced) 378 | } 379 | 380 | fn pop_all_forced(&mut self) { 381 | while self.forced < self.false_stack.len() { 382 | let lit = self.false_stack.pop().unwrap(); 383 | self.false_a[lit] = Assign::Unassigned; 384 | reason!(self, lit) = Reason::NONE; 385 | } 386 | } 387 | 388 | fn remove_unit(&mut self, lit: i64) { 389 | if let Some(i) = self.unit_stack.iter() 390 | .position(|&cl| self.db[cl][0] == lit) { 391 | self.unit_stack.remove(i); 392 | } 393 | } 394 | 395 | fn unassign_unit(&mut self, lit: i64) { 396 | if self.verb { println!("c removing unit {}", lit) } 397 | while self.false_a[-lit].assigned() { 398 | self.forced = self.forced.checked_sub(1).unwrap(); 399 | let old = self.false_stack[self.forced]; 400 | if self.verb { println!("c removing unit {} ({})", old, lit) } 401 | self.false_a[old] = Assign::Unassigned; 402 | *self.reason(old) = Reason::NONE; 403 | } 404 | self.processed = self.forced; 405 | self.false_stack.truncate(self.forced); 406 | } 407 | 408 | fn mark_watch(&mut self, clause: usize, index: usize) { 409 | self.watch_list[self.db[clause][index]] 410 | .iter_mut().find(|w| w.clause() == clause) 411 | .unwrap().mark(); 412 | } 413 | 414 | fn add_dependency(&mut self, dep: Dependency) { 415 | // if self.trace_file.is_some() || self.lrat_file.is_some() { 416 | self.deps.push(dep); 417 | // } 418 | } 419 | 420 | fn mark_clause(&mut self, clause: usize, skip_index: usize, forced: bool) { 421 | self.num_resolve += 1; 422 | let id = self.db[clause].ida; 423 | self.add_dependency(Dependency::new(id.id() as i64, forced)); 424 | if !id.active() { 425 | self.num_active += 1; 426 | self.db[clause].activate(); 427 | match (self.opts.mode, self.db[clause].len() > 1) { 428 | (Mode::BackwardUnsat, true) => self.opt_proof.push(ProofStep::del(clause)), 429 | (_, false) => return, 430 | _ => {} 431 | } 432 | self.mark_watch(clause, 0); 433 | self.mark_watch(clause, 1); 434 | } 435 | for &lit in &self.db[clause][skip_index..] { 436 | self.false_a[lit] = Assign::Mark 437 | } 438 | } 439 | 440 | fn analyze(&mut self, clause: usize, skip_index: usize) { 441 | let mut assigned = self.false_stack.len(); 442 | self.mark_clause(clause, skip_index, assigned > self.forced); 443 | while let Some(lit) = pop_ext(&self.false_stack, &mut assigned) { 444 | match self.false_a[lit] { 445 | Assign::Mark => if let Some(cl) = self.reason(lit).get() { 446 | self.mark_clause(cl, 1, assigned > self.forced); 447 | }, 448 | Assign::Assumed if !self.rat_mode && self.reduce => { 449 | self.num_removed += 1; 450 | let cl = &mut *self.db[self.current]; 451 | let i = cl.iter().position(|&i| i == lit).unwrap(); 452 | cl.remove(i); 453 | } 454 | _ => {} 455 | } 456 | let force = assigned < self.forced; 457 | if !force { *self.reason(lit) = Reason::NONE } 458 | self.false_a[lit] = force.into(); 459 | } 460 | self.processed = self.forced; 461 | self.false_stack.truncate(self.forced); 462 | } 463 | 464 | fn propagate(&mut self) -> bool { 465 | let mut check = 0; 466 | let mode = !self.prep; 467 | let mut start = [self.processed; 2]; 468 | let mut prev: Option<(i64, (*mut Vec, usize))> = None; 469 | 'flip_check: loop { 470 | check ^= 1; 471 | while let Some(&lit) = self.false_stack.get(start[check]) { 472 | start[check] += 1; 473 | let (watch, mut wi) = match prev { 474 | Some((_lit, _watch)) if lit == _lit => _watch, 475 | _ => (&mut self.watch_list[lit] as *mut _, 0), 476 | }; 477 | // SAFETY: The borrow of self.watch_list[lit] here is disjoint from 478 | // the borrow of self.watch_list[ci] below because lit != ci 479 | // (clauses have no duplicates) 480 | let watch = unsafe {&mut *watch}; 481 | 'next_clause: while let Some(&w) = watch.get(wi) { 482 | if w.0 & mode as usize != check { wi += 1; continue } 483 | let index = w.clause(); 484 | let cl = &mut *self.db[index]; 485 | let (a, b) = if let [a, b, ..] = **cl {(a, b)} else {panic!()}; 486 | if self.false_a[-a].assigned() || 487 | self.false_a[-b].assigned() { 488 | wi += 1; continue 489 | } 490 | if a == lit {cl.swap(0, 1)} 491 | for i in 2..cl.len() { 492 | let ci = cl[i]; 493 | if !self.false_a[ci].assigned() { 494 | cl.swap(1, i); 495 | debug_assert!(lit != ci); 496 | self.watch_list[ci].push(watch.swap_remove(wi)); 497 | continue 'next_clause 498 | } 499 | } 500 | let lit2 = cl[0]; wi += 1; 501 | if !self.false_a[lit2].assigned() { 502 | self.assign(lit2); 503 | *self.reason(lit2) = Reason::new(index); 504 | if check == 0 { 505 | start[0] -= 1; 506 | prev = Some((lit, (watch, wi))); 507 | continue 'flip_check 508 | } 509 | } else { self.analyze(index, 0); return true } 510 | } 511 | } 512 | if check == 0 {break} 513 | } 514 | self.processed = self.false_stack.len(); 515 | false 516 | } 517 | 518 | fn propagate_units(&mut self) -> bool { 519 | while let Some(i) = self.pop_forced() { 520 | self.false_a[i] = Assign::Unassigned; 521 | reason!(self, i) = Reason::NONE; 522 | } 523 | self.processed = 0; self.false_stack.clear(); 524 | for &c in &self.unit_stack { 525 | let lit = self.db[c][0]; 526 | reason!(self, lit) = Reason::new(c); 527 | assign!(self, lit); 528 | } 529 | if self.propagate() {return true} 530 | self.forced = self.processed; 531 | false 532 | } 533 | 534 | /// Put falsified literals at the end and returns the size under the current 535 | /// assignment: `sat = true` means satisfied, `size = 0` means falsified 536 | fn sort_size(&mut self, lemma: usize) -> (bool, usize) { 537 | let mut size = 0; 538 | let lemma = &mut **self.db[lemma]; 539 | let mut sat = false; 540 | for last in 0..lemma.len() { 541 | let lit = lemma[last]; 542 | if !self.false_a[lit].assigned() { 543 | sat |= self.false_a[-lit].assigned(); 544 | lemma.swap(last, size); 545 | size += 1; 546 | } 547 | } 548 | (sat, size) 549 | } 550 | 551 | fn core_iter(&self, core: bool) -> impl Iterator { 552 | self.formula.iter() 553 | .map(move |step| &self.db[step.clause()]) 554 | .filter(move |cl| cl.active() == core) 555 | } 556 | 557 | fn print_core(&mut self, core_count: usize) -> io::Result<()> { 558 | println!("c {} of {} clauses in core ", 559 | core_count, self.num_clauses); 560 | 561 | creating(&self.core_str, |mut f| { 562 | writeln!(f, "p cnf {} {}", self.num_vars, core_count)?; 563 | for clause in self.core_iter(true) { 564 | for i in &**clause {write!(f, "{} ", i)?} 565 | writeln!(f, "0")?; 566 | } 567 | Ok(()) 568 | }) 569 | } 570 | 571 | fn print_lrat_line(&self, f: &mut impl Write, time: usize) -> io::Result<()> { 572 | let l = self.lrat_table[time].as_ref().unwrap(); 573 | if self.bin_output { 574 | f.write_all(b"a")?; 575 | write_lit(f, l.name)?; 576 | for &lit in &*l.clause { write_lit(f, lit)? } 577 | write_lit(f, 0)?; 578 | for &lit in &*l.chain { write_lit(f, lit)? } 579 | write_lit(f, 0) 580 | } else { 581 | write!(f, "{} ", l.name)?; 582 | for &lit in &*l.clause { write!(f, "{} ", lit)? } 583 | write!(f, "0 ")?; 584 | for &lit in &*l.chain { write!(f, "{} ", lit)? } 585 | writeln!(f, "0") 586 | } 587 | } 588 | 589 | fn print_proof(&mut self, core_count: usize) -> io::Result<()> { 590 | println!("c {} of {} lemmas in core using {} resolution steps", 591 | self.num_active - core_count + 1, self.num_lemmas + 1, self.num_resolve); 592 | println!("c {} PR lemmas in core; {} redundant literals in core lemmas", 593 | self.rat_count, self.num_removed); 594 | 595 | match self.mode { 596 | Mode::ForwardUnsat => { 597 | println!("c optimized proofs are not supported for forward checking"); 598 | return Ok(()); 599 | } 600 | Mode::BackwardUnsat => { 601 | let mut num_lemmas = 0; 602 | self.proof = self.opt_proof.iter().copied().rev() 603 | .inspect(|ad| if !ad.is_del() { num_lemmas += 1 }) 604 | .collect(); 605 | self.num_lemmas = num_lemmas; 606 | } 607 | _ => {} 608 | } 609 | 610 | creating(&self.lemma_str, |mut f| { 611 | for &ad in &self.proof { 612 | let lemma = &self.db[ad.clause()]; 613 | if lemma.len() <= 1 && ad.is_del() {continue} 614 | if self.bin_output { 615 | f.write_all(if ad.is_del() {b"d"} else {b"a"})? 616 | } else if ad.is_del() {write!(f, "d ")?} 617 | let reslit = lemma.pivot; 618 | for lit in lemma.iter().copied().filter(|&lit| lit == reslit) 619 | .chain(lemma.iter().copied().filter(|&lit| lit != reslit)) 620 | .chain(lemma.witness.get().map(|w| &*self.witness[w]) 621 | .unwrap_or(&[]).iter().copied()) { 622 | if self.bin_output { write_lit(&mut f, lit)? } 623 | else { write!(f, "{} ", lit)? } 624 | } 625 | if self.bin_output { write_lit(&mut f, 0)? } 626 | else { writeln!(f, "0")? } 627 | } 628 | if self.bin_output { f.write_all(b"a")?; write_lit(&mut f, 0) } 629 | else { writeln!(f, "0") } 630 | })?; 631 | 632 | if let Some(mut f) = self.opts.lrat_file.take() { 633 | let mut last_added = self.num_clauses; 634 | for &ad in &self.proof { 635 | let lemmas = &self.db[ad.clause()]; 636 | if !ad.is_del() { 637 | if last_added == 0 { 638 | if self.bin_output { write_lit(&mut f, 0)? } 639 | else { writeln!(f, "0")? } 640 | } 641 | last_added = lemmas.id(); 642 | self.print_lrat_line(&mut f, last_added)?; 643 | } else if last_added != self.num_clauses && lemmas.len() > 1 { 644 | if last_added != 0 { 645 | if self.bin_output { f.write_all(b"d")? } 646 | else { write!(f, "{} d ", last_added)? } 647 | last_added = 0; 648 | } 649 | if self.bin_output { write_lit(&mut f, lemmas.id() as i64)? } 650 | else { write!(f, "{} ", lemmas.id())? } 651 | } 652 | f.flush()?; 653 | } 654 | if last_added != self.num_clauses { 655 | if self.bin_output { write_lit(&mut f, 0)? } 656 | else { writeln!(f, "0")? } 657 | } 658 | self.print_lrat_line(&mut f, self.count)?; 659 | f.flush()?; 660 | let writes = f.seek(SeekFrom::Current(0))?; 661 | if writes != 0 { 662 | println!("c wrote optimized proof in LRAT format of {} bytes", writes); 663 | } 664 | } 665 | 666 | Ok(()) 667 | } 668 | 669 | fn print_no_core(&mut self) -> io::Result<()> { 670 | if let Some(lrat) = &mut self.opts.lrat_file { 671 | if self.opts.bin_output { lrat.write_all(b"d")? } 672 | else { write!(lrat, "{} d ", self.num_clauses)? } 673 | for step in &self.formula { 674 | let clause = &self.db[step.clause()]; 675 | if !clause.active() { 676 | if self.opts.bin_output { write_lit(lrat, clause.id() as i64)? } 677 | else { write!(lrat, "{} ", clause.id())? } 678 | } 679 | } 680 | if self.opts.bin_output { write_lit(lrat, 0)? } 681 | else { writeln!(lrat, "0")? } 682 | } 683 | Ok(()) 684 | } 685 | 686 | fn print_trace(&mut self) -> io::Result<()> { 687 | if let Some(mut f) = self.opts.trace_file.take() { 688 | for (step, i) in self.formula.iter().zip(1..) { 689 | let clause = &self.db[step.clause()]; 690 | if clause.active() { 691 | write!(f, "{} ", i)?; 692 | for i in &**clause {write!(f, "{} ", i)?} 693 | writeln!(f, "0 0")?; 694 | } 695 | } 696 | f.flush()?; 697 | } 698 | Ok(()) 699 | } 700 | 701 | fn print_active(&mut self) -> io::Result<()> { 702 | if let Some(active) = &mut self.opts.active_file { 703 | for lit in (-self.max_var..=self.max_var).filter(|&i| i != 0) { 704 | for &w in &self.watch_list[lit] { 705 | let clause = &**self.db[w.clause()]; 706 | if clause[0] == lit { 707 | for i in clause {write!(active, "{} ", i)?} 708 | writeln!(active, "0")?; 709 | } 710 | } 711 | } 712 | } 713 | Ok(()) 714 | } 715 | 716 | fn postprocess(&mut self) -> io::Result<()> { 717 | self.print_no_core()?; 718 | self.print_active()?; 719 | let core_count = self.core_iter(true).count(); 720 | self.print_core(core_count)?; 721 | self.print_trace()?; 722 | self.print_proof(core_count) 723 | } 724 | 725 | fn print_dependencies_file(&mut self, clause: Option, mode: bool) -> io::Result<()> { 726 | if let Some(f) = if mode {&mut self.opts.lrat_file} else {&mut self.opts.trace_file} { 727 | let db = &self.db; 728 | let time = clause.map_or(self.count, |c| db[c].id()); 729 | let (name, clause) = if let Some(cl) = clause { 730 | let cl = &db[cl]; 731 | let mut sorted = cl.to_vec(); 732 | let reslit = cl.pivot; 733 | sorted.sort_by_key(|&lit| if lit == reslit {0} else {lit.abs()}); 734 | // print the witness for PR 735 | if let Some(w) = cl.witness.get() { sorted.extend_from_slice(&self.witness[w]) } 736 | (self.time.id() as i64, sorted.into()) 737 | } else { 738 | (self.count as i64, Box::new([]) as Box<[_]>) 739 | }; 740 | 741 | let chain = if self.deps.iter().all(|&step| step.is_pos()) { 742 | self.deps.iter().rev().map(|&step| step.id()).collect::>() 743 | } else { 744 | let mut chain = Vec::with_capacity(self.deps.len()); 745 | 746 | // first print the preRAT units in order of becoming unit 747 | self.pre_rat.clear(); 748 | let mut last = 0; 749 | while let Some(len) = self.deps[last..].iter().position(|&step| step.id() < 0) { 750 | let next = last + len; 751 | for cls in self.deps[last..next].iter().rev().copied() { 752 | if !cls.forced() && !self.pre_rat.contains(&cls) { 753 | self.pre_rat.push(cls); 754 | chain.push(cls.id()); 755 | } 756 | } 757 | last = next + 1; 758 | } 759 | 760 | // print dependencies in order of becoming unit 761 | for &cls in self.deps.iter().rev() { 762 | if mode { 763 | if cls.forced() {chain.push(cls.id())} 764 | } else if cls.is_pos() && !self.pre_rat.contains(&cls) { 765 | self.pre_rat.push(cls); 766 | chain.push(cls.id()); 767 | } 768 | } 769 | 770 | chain.into_boxed_slice() 771 | }; 772 | 773 | if !mode { 774 | write!(f, "{} ", name)?; 775 | for &lit in &*clause {write!(f, "{} ", lit)?}; write!(f, "0 ")?; 776 | for &lit in &*chain {write!(f, "{} ", lit)?}; writeln!(f, "0")?; 777 | } 778 | self.lrat_table[time] = Some(LRATLine {name, clause, chain}) 779 | } 780 | Ok(()) 781 | } 782 | 783 | fn print_dependencies(&mut self, clause: Option) -> io::Result<()> { 784 | if let Some(idx) = clause { 785 | let maxdep = self.deps.iter().map(|dep| dep.0).fold(0, i64::max) as usize; 786 | assert!(maxdep < self.db[idx].ida.0); 787 | self.db[idx].maxdep = maxdep; 788 | } 789 | self.print_dependencies_file(clause, false)?; 790 | self.print_dependencies_file(clause, true) 791 | } 792 | 793 | fn get_rat_set(&mut self, mut eligible: impl FnMut(&Clause) -> bool) -> Vec { 794 | let mut rat_set = mem::take(&mut self.rat_set); 795 | rat_set.clear(); 796 | // Loop over all literals to calculate resolution candidates 797 | for i in (-self.max_var..=self.max_var).filter(|&i| i != 0) { 798 | // Loop over all watched clauses for literal 799 | for &w in &self.watch_list[i] { 800 | let watched = &self.db[w.clause()]; 801 | if watched[0] == i && eligible(watched) { // If watched literal is in first position 802 | rat_set.push(w.clause()); 803 | } 804 | } 805 | } 806 | rat_set.sort_by_key(|&i| self.db[i].ida.0); 807 | rat_set 808 | } 809 | 810 | fn check_one_rat(&mut self, 811 | cl: usize, 812 | clear_blocked: bool, 813 | mut witness: impl FnMut(i64) -> bool 814 | ) -> bool { 815 | let mut clause = &self.db[cl]; 816 | let ida = clause.ida; 817 | 818 | let mut blocked = 0; 819 | let mut reason = Reason::NONE; 820 | for &lit in &**clause { 821 | if self.false_a[-lit].assigned() && !witness(-lit) { 822 | let reason2 = reason!(self, lit); 823 | let f = |bl: Reason| bl.get().map(|a| self.db[a].ida.0); 824 | if blocked == 0 || f(reason) > f(reason2) { 825 | blocked = lit; reason = reason2; 826 | } 827 | } 828 | } 829 | 830 | if blocked != 0 { 831 | if let Some(reason) = reason.get() { 832 | self.analyze(reason, 1); 833 | if clear_blocked { *self.reason(blocked) = Reason::NONE } 834 | } 835 | } else { 836 | for i in 0..clause.len() { 837 | let lit = clause[i]; 838 | if !self.false_a[lit].assigned() && !witness(-lit) { 839 | self.assign(-lit); 840 | *self.reason(lit) = Reason::NONE; 841 | clause = &self.db[cl]; 842 | } 843 | } 844 | if !self.propagate() { return false } 845 | } 846 | self.add_dependency(Dependency::new(-(ida.id() as i64), true)); 847 | true 848 | } 849 | 850 | fn check_pr(&mut self, w: usize) -> bool { 851 | let mut witness = mem::take(&mut self.witness[w]); 852 | 853 | // remove from omega all literals in alpha+? 854 | witness.retain(|&lit| !self.false_a[-lit].assigned() || { 855 | if self.verb { println!("c removing overlap {}", lit) } 856 | false 857 | }); 858 | 859 | // Calculate the clauses which are reduced but not satisfied by omega 860 | let rat_set = self.get_rat_set(|watched| { 861 | let mut reduced = false; 862 | for &lit in &**watched { 863 | for &j in &witness { 864 | if j == lit { return false } 865 | if j == -lit { reduced = true } 866 | } 867 | } 868 | reduced 869 | }); 870 | 871 | // Check all clauses in rat_set for RUP 872 | let mut last_active = None; 873 | loop { 874 | let p_active = rat_set.iter().map(|&cl| &self.db[cl]) 875 | .filter(|&pr_cls| pr_cls.active()).count(); 876 | if mem::replace(&mut last_active, Some(p_active)) 877 | .map_or(false, |last| last >= p_active) { 878 | self.rat_set = rat_set; 879 | self.witness[w] = witness; 880 | return true 881 | } 882 | 883 | self.deps.clear(); 884 | for &cl in rat_set.iter().rev() { 885 | let pr_cls = &self.db[cl]; 886 | if matches!(self.mode, Mode::BackwardUnsat) && !pr_cls.active() { 887 | if self.verb { println!("c PR check ignores unmarked clause: {}", pr_cls) } 888 | continue 889 | } 890 | if self.verb { println!("c PR clause: {}", pr_cls) } 891 | if !self.check_one_rat(cl, false, |lit| witness.contains(&lit)) { 892 | self.rat_set = rat_set; 893 | self.witness[w] = witness; 894 | self.pop_all_forced(); 895 | if self.verb { println!("c PR check on witness failed") } 896 | return false 897 | } 898 | } 899 | } 900 | } 901 | 902 | fn check_rat(&mut self, pivot: i64) -> bool { 903 | // Loop over all literals to calculate resolution candidates 904 | let (mode, verb) = (self.mode, self.verb); 905 | let rat_set = self.get_rat_set(|watched| watched.contains(&-pivot) && { 906 | if matches!(mode, Mode::BackwardUnsat) && !watched.active() { 907 | if verb { println!("c RAT check ignores unmarked clause: {}", watched) } 908 | return false 909 | } 910 | true 911 | }); 912 | 913 | // self.prep = true; 914 | // Check all clauses in rat_set for RUP 915 | self.deps.clear(); 916 | for &cl in rat_set.iter().rev() { 917 | if verb { println!("c RAT clause: {}", self.db[cl]) } 918 | if !self.check_one_rat(cl, true, |lit| lit == pivot) { 919 | self.rat_set = rat_set; 920 | self.pop_all_forced(); 921 | if verb { println!("c RAT check on pivot {} failed", pivot) } 922 | return false 923 | } 924 | } 925 | 926 | true 927 | } 928 | 929 | fn redundancy_check(&mut self, index: usize, sat: bool, size: usize) -> io::Result { 930 | let mut clause = &self.db[index]; 931 | if let Some(w) = clause.witness.get() { 932 | for &lit in &*self.witness[w] { 933 | if self.false_a[lit].assigned() { 934 | println!("c ERROR: witness literal {} complement of units in lemma {}", lit, clause); 935 | return Ok(false) 936 | } 937 | } 938 | } 939 | 940 | let reslit = clause.pivot; 941 | let witness = clause.witness; 942 | if self.verb { println!("c checking lemma ({}, {}) {}", size, reslit, clause) } 943 | 944 | if !self.full && !clause.active() {return Ok(true)} 945 | 946 | if sat { 947 | let reason = reason!(self, clause[0]); 948 | self.db[reason.get().unwrap()].activate(); 949 | return Ok(true) 950 | } 951 | 952 | let false_pivot = self.false_a[reslit].assigned(); 953 | self.rat_mode = false; 954 | self.deps.clear(); 955 | for &lit in &clause[..size] { 956 | if self.false_a[-lit].assigned() { // should only occur in forward mode 957 | self.warn(|| println!("c WARNING: found a tautological clause in proof: {}", clause)); 958 | self.pop_all_forced(); 959 | return Ok(true) 960 | } 961 | self.false_a[lit] = Assign::Assumed; 962 | self.false_stack.push(lit); 963 | reason!(self, lit) = Reason::NONE; 964 | } 965 | 966 | let indegree = self.num_resolve; 967 | self.current = index; 968 | if self.propagate() { 969 | let prep = self.num_resolve - indegree <= 2; 970 | if prep != self.prep { 971 | self.prep = prep; 972 | if self.verb { 973 | println!("c [{}] preprocessing checking mode {}", 974 | self.time.0, if prep {"on"} else {"off"}) 975 | } 976 | } 977 | if self.verb { println!("c lemma has RUP") } 978 | self.print_dependencies(Some(index))?; 979 | return Ok(true) 980 | } 981 | 982 | // Failed RUP check. Now test RAT. 983 | // println!("RUP check failed. Starting RAT check."); 984 | if self.verb { println!("c RUP check failed; starting RAT check on pivot {}.", reslit) } 985 | 986 | if false_pivot { return Ok(false) } 987 | 988 | let forced = self.forced; 989 | self.rat_mode = true; 990 | self.forced = self.false_stack.len(); 991 | 992 | let mut success = if let Some(w) = witness.get() { 993 | if !self.check_pr(w) { return Ok(false) } 994 | true 995 | } else { self.check_rat(reslit) }; 996 | clause = &self.db[index]; 997 | if !success { 998 | self.warn(|| println!("c WARNING: RAT check on proof pivot failed: {}", clause)); 999 | for i in 0..size { 1000 | let lit = clause[i]; 1001 | if lit == reslit { continue } 1002 | if self.check_rat(lit) { 1003 | self.db[index].pivot = lit; 1004 | success = true; 1005 | break 1006 | } 1007 | clause = &self.db[index]; 1008 | } 1009 | } 1010 | 1011 | if success { self.print_dependencies(Some(index))? } 1012 | 1013 | self.processed = forced; 1014 | self.forced = forced; 1015 | self.pop_all_forced(); 1016 | 1017 | if !success { 1018 | println!("c RAT check failed on all possible pivots"); 1019 | return Ok(false) 1020 | } 1021 | 1022 | self.rat_count += 1; 1023 | if self.verb { 1024 | if witness.get().is_some() { println!("c lemma has PR") } 1025 | else { println!("c lemma has RAT on {}", self.db[index].pivot) } 1026 | } 1027 | Ok(true) 1028 | } 1029 | 1030 | fn init(&mut self) -> io::Result { 1031 | self.forced = 0; 1032 | self.processed = 0; 1033 | self.false_stack.clear(); 1034 | self.rat_mode = false; 1035 | self.num_removed = 0; 1036 | self.opt_proof.clear(); 1037 | self.num_resolve = 0; 1038 | self.rat_count = 0; 1039 | self.num_active = 0; 1040 | self.unit_stack.clear(); 1041 | for i in 1..=self.max_var { 1042 | self.reason[i as usize] = Reason::NONE; 1043 | self.false_a[i] = Assign::Unassigned; 1044 | self.false_a[-i] = Assign::Unassigned; 1045 | self.watch_list[i].clear(); 1046 | self.watch_list[-i].clear(); 1047 | } 1048 | for i in 0..self.formula.len() { 1049 | let c = self.formula[i].clause(); 1050 | let clause = &mut self.db[c]; 1051 | clause.deactivate(); 1052 | match ***clause { 1053 | [] => { 1054 | println!("c formula contains empty clause"); 1055 | creating(&self.core_str, |mut f| writeln!(f, "p cnf 0 1\n0"))?; 1056 | creating(&self.lemma_str, |mut f| writeln!(f, "0"))?; 1057 | return Ok(true) 1058 | } 1059 | [i, j, ..] => { 1060 | self.add_watch(c, i); 1061 | self.add_watch(c, j); 1062 | } 1063 | [i] => if self.false_a[i].assigned() { 1064 | println!("c found complementary unit clauses: {}", i); 1065 | creating(&self.core_str, |mut f| 1066 | writeln!(f, "p cnf {} 2\n{} 0\n{} 0", i.abs(), i, -i))?; 1067 | creating(&self.lemma_str, |mut f| writeln!(f, "0"))?; 1068 | if let Some(lrat) = &mut self.opts.lrat_file { 1069 | let db = &self.db; 1070 | let j = self.formula.iter().position( 1071 | |&step| *db[step.clause()] == [-i]).unwrap(); 1072 | writeln!(lrat, "{} 0 {} {} 0", self.num_clauses + 1, j + 1, i + 1)?; 1073 | } 1074 | return Ok(true) 1075 | } else if !self.false_a[-i].assigned() { 1076 | self.add_unit(c); 1077 | self.assign(i); 1078 | } 1079 | } 1080 | } 1081 | self.deps.clear(); 1082 | self.time = ClauseId(self.count); // Alternative time init 1083 | if self.propagate_units() { 1084 | println!("c UNSAT via unit propagation on the input instance"); 1085 | self.print_dependencies(None)?; 1086 | self.postprocess()?; 1087 | return Ok(true) 1088 | } 1089 | Ok(false) 1090 | } 1091 | 1092 | fn verify(&mut self, end_incl: usize) -> io::Result { 1093 | if self.init()? {return Ok(VerifyResult::Unsat)} 1094 | if let Mode::ForwardUnsat = self.mode { 1095 | println!("c start forward verification") 1096 | } 1097 | 1098 | let mut active = self.num_clauses; 1099 | let mut adds = 0u64; 1100 | let last_step = 'start_verification: loop { 1101 | for step in 0..self.proof.len() { 1102 | let ad = self.proof[step]; 1103 | let mut lemmas = &self.db[ad.clause()]; 1104 | 1105 | self.time = lemmas.ida; 1106 | if ad.is_del() { active -= 1 } 1107 | else { active += 1; adds += 1; } 1108 | if matches!(self.mode, Mode::ForwardSat) && self.verb { 1109 | println!("c {} active clauses", active); 1110 | } 1111 | 1112 | if let [lit] = ***lemmas { // found a unit 1113 | if self.verb { 1114 | println!("c found unit in proof {} [{}]", lit, self.time.0); 1115 | } 1116 | if ad.is_del() { 1117 | if let Mode::ForwardSat = self.mode { 1118 | self.remove_unit(lit); 1119 | self.propagate_units(); 1120 | } else { // no need to remove units while checking UNSAT 1121 | if self.verb { println!("c removing proof step: d {}", lemmas) } 1122 | self.proof[step] = ProofStep::NULL; 1123 | continue 1124 | } 1125 | } else if matches!(self.mode, Mode::BackwardUnsat) && self.false_a[-lit].assigned() { 1126 | self.proof[step] = ProofStep::NULL; 1127 | continue 1128 | } else { self.add_unit(ad.clause()) } 1129 | lemmas = &self.db[ad.clause()]; 1130 | } 1131 | 1132 | if ad.is_del() { 1133 | if let [lit, lit2, ..] = ***lemmas { // if delete and not unit 1134 | if *self.reason(lit) == Reason::new(ad.clause()) { 1135 | if let Mode::ForwardSat = self.mode { // also for FORWARD_UNSAT? 1136 | self.remove_watch(ad.clause(), lit); 1137 | self.remove_watch(ad.clause(), lit2); 1138 | self.propagate_units(); 1139 | } else { // ignore pseudo unit clause deletion 1140 | if self.verb { 1141 | println!("c ignoring deletion instruction {}: {}", 1142 | step, self.db[ad.clause()]) 1143 | } 1144 | self.proof[step] = ProofStep::NULL; 1145 | } 1146 | } else { 1147 | self.remove_watch(ad.clause(), lit); 1148 | self.remove_watch(ad.clause(), lit2); 1149 | } 1150 | if let Mode::ForwardSat | Mode::BackwardUnsat = self.mode { continue } 1151 | } 1152 | } 1153 | 1154 | let (sat, size) = self.sort_size(ad.clause()); 1155 | let (sat, size) = match (ad.is_del(), self.mode) { 1156 | (true, Mode::ForwardSat) => { 1157 | if sat { self.propagate_units(); } 1158 | if !self.redundancy_check(ad.clause(), sat, size)? { 1159 | println!("c failed at proof line {} (modulo deletion errors)", step + 1); 1160 | return Ok(VerifyResult::Fail) 1161 | } 1162 | continue 1163 | } 1164 | (false, Mode::ForwardUnsat) if step >= end_incl => { 1165 | if sat {continue} // bus error in drat-trim? 1166 | if !self.redundancy_check(ad.clause(), sat, size)? { 1167 | println!("c failed at proof line {} (modulo deletion errors)", step + 1); 1168 | return Ok(VerifyResult::Fail) 1169 | } 1170 | self.deps.clear(); 1171 | self.sort_size(ad.clause()) 1172 | } 1173 | _ => (sat, size) 1174 | }; 1175 | if let [lit, lit2, ..] = **self.db[ad.clause()] { 1176 | self.add_watch(ad.clause(), lit); 1177 | self.add_watch(ad.clause(), lit2); 1178 | } 1179 | match (sat, size) { 1180 | (_, 0) => { 1181 | println!("c conflict claimed, but not detected"); 1182 | return Ok(VerifyResult::Fail) 1183 | } 1184 | (false, 1) => { 1185 | let lit = self.db[ad.clause()][0]; 1186 | if self.verb { println!("c found unit {}", lit) } 1187 | self.assign(lit); 1188 | *self.reason(lit) = Reason::new(ad.clause()); 1189 | if self.propagate() { break 'start_verification step } 1190 | self.forced = self.processed; 1191 | } 1192 | _ => {} 1193 | } 1194 | } 1195 | 1196 | match self.mode { 1197 | Mode::ForwardSat => if active == 0 { 1198 | self.postprocess()?; 1199 | return Ok(VerifyResult::Unsat) 1200 | }, 1201 | Mode::ForwardUnsat => { 1202 | self.postprocess()?; 1203 | println!("c VERIFIED derivation: all lemmas preserve satisfiability"); 1204 | if !matches!(self.warning, Warning::None) { 1205 | println!("c WARNING: no empty clause detected, this is not a refutation"); 1206 | } 1207 | return Ok(VerifyResult::Derivation) 1208 | } 1209 | Mode::BackwardUnsat => { 1210 | if self.backforce { 1211 | for step in 0..self.proof.len() { 1212 | let ad = self.proof[step]; 1213 | if !self.sort_size(ad.clause()).0 { 1214 | let clause = &mut self.db[ad.clause()]; 1215 | if ad.is_del() { clause.deactivate() } 1216 | else { clause.activate() } 1217 | } 1218 | } 1219 | } else { 1220 | println!("c ERROR: no conflict"); 1221 | return Ok(VerifyResult::Fail) 1222 | } 1223 | } 1224 | } 1225 | 1226 | unreachable!() 1227 | }; 1228 | 1229 | match self.mode { 1230 | Mode::ForwardUnsat => { 1231 | self.print_dependencies(None)?; 1232 | self.postprocess()?; 1233 | return Ok(VerifyResult::Unsat) 1234 | } 1235 | Mode::ForwardSat => { 1236 | if !self.backforce { self.print_dependencies(None)? } 1237 | println!("c ERROR: found empty clause during SAT check"); 1238 | return Ok(VerifyResult::Fail) 1239 | } 1240 | Mode::BackwardUnsat => {} 1241 | } 1242 | 1243 | if !self.backforce { self.print_dependencies(None)? } 1244 | println!("c detected empty clause; start verification via backward checking"); 1245 | self.forced = self.processed; 1246 | self.opt_proof.clear(); 1247 | 1248 | let mut _checked = 0; 1249 | let mut _skipped = 0; 1250 | let max = adds as f64; 1251 | let backward_time = Instant::now(); 1252 | for step in (0..=last_step).rev() { 1253 | let runtime = backward_time.elapsed(); 1254 | if runtime.as_secs() > self.timeout && !self.optimize { 1255 | println!("s TIMEOUT"); 1256 | return Ok(VerifyResult::Fail) 1257 | } 1258 | if self.bar && adds % 1000 == 0 { 1259 | let time = runtime.as_secs_f64(); 1260 | let fraction = 1.0 - adds as f64 / max; 1261 | print!("c {:.2}% [", 100.0 * fraction); 1262 | for f in 1..=20 { 1263 | if fraction * 20.0 < f as f64 {print!(" ")} else {print!("=")} 1264 | } 1265 | print!("] time remaining: {:.2} seconds ", time / fraction - time); 1266 | if step == 0 {println!()} 1267 | stdout().flush()? 1268 | } 1269 | 1270 | let ad = self.proof[step]; 1271 | if ad == ProofStep::NULL {continue} 1272 | let clause = &self.db[ad.clause()]; 1273 | if ad.is_del() { 1274 | let size = self.sort_size(ad.clause()); 1275 | let clause = &self.db[ad.clause()]; 1276 | if self.verb { println!("c adding clause ({}) {}", 1277 | if size.0 {-(size.1 as isize)} else {size.1 as isize}, clause) } 1278 | if let [lit, lit2, ..] = ***clause { 1279 | self.add_watch(ad.clause(), lit); 1280 | self.add_watch(ad.clause(), lit2); 1281 | } else {unreachable!()} 1282 | } else { 1283 | adds -= 1; 1284 | match ***clause { 1285 | [lit, lit2, ..] => { 1286 | self.remove_watch(ad.clause(), lit); 1287 | self.remove_watch(ad.clause(), lit2); 1288 | if *self.reason(lit) == Reason::new(ad.clause()) { 1289 | self.unassign_unit(lit) 1290 | } 1291 | } 1292 | [lit] => self.unassign_unit(lit), 1293 | [] => unreachable!() 1294 | } 1295 | 1296 | let (sat, size) = self.sort_size(ad.clause()); 1297 | let clause = &mut self.db[ad.clause()]; 1298 | self.time = clause.ida; 1299 | if !self.opts.full && !self.time.active() { 1300 | _skipped += 1; 1301 | continue 1302 | } 1303 | assert!(!sat && size >= 1); 1304 | self.num_removed += clause.len() - size; 1305 | clause.truncate(size); 1306 | 1307 | if self.opts.verb { 1308 | println!("c validating clause ({}, {}): {}", clause.pivot, size, clause); 1309 | } 1310 | if !self.redundancy_check(ad.clause(), sat, size)? { 1311 | println!("c failed at proof line {} (modulo deletion errors)", step + 1); 1312 | return Ok(VerifyResult::Fail) 1313 | } 1314 | _checked += 1; 1315 | self.opt_proof.push(ad); 1316 | } 1317 | } 1318 | self.postprocess()?; 1319 | Ok(VerifyResult::Unsat) 1320 | } 1321 | 1322 | fn match_clause(db: &[Clause], clause_list: &mut Vec, lits: &[i64]) -> Option { 1323 | let i = clause_list.iter().position(|&c| db[c].lits == lits)?; 1324 | Some(clause_list.swap_remove(i)) 1325 | } 1326 | 1327 | fn deactivate(&mut self) { 1328 | self.num_active = 0; 1329 | for &step in &self.proof { 1330 | self.db[step.clause()].deactivate(); 1331 | } 1332 | } 1333 | 1334 | fn shuffle_proof(&mut self, iteration: usize) { 1335 | use rand::Rng; 1336 | let mut rng = rand::thread_rng(); 1337 | let mut base = 100_f64; 1338 | for _ in 1..iteration { base *= 1.1 } 1339 | let mut _step = 0; 1340 | 1341 | // randomly remove clause deletion steps 1342 | let db = &self.db; 1343 | self.proof.retain(|&step| !step.is_del() || 1344 | rng.gen_range(0.0..1000.0) >= 1345 | base * iteration as f64 / db[step.clause()].len() as f64); 1346 | 1347 | for step in (1..self.proof.len()).rev() { 1348 | let a = self.proof[step]; 1349 | if a.is_del() {continue} 1350 | let b = self.proof[step - 1]; 1351 | if b.is_del() { self.proof.swap(step, step - 1) } 1352 | else if a.clause() != b.clause() { 1353 | // SAFETY: the two elements of the array are non-overlapping 1354 | let c = unsafe { &mut *(&mut self.db[a.clause()] as *mut Clause) }; 1355 | let d = &mut self.db[b.clause()]; 1356 | let coinflip = false; 1357 | // let coinflip = rng.gen(); 1358 | if c.maxdep < d.maxdep || coinflip && c.maxdep < d.ida.0 { 1359 | mem::swap(&mut c.ida, &mut d.ida); 1360 | self.proof.swap(step, step - 1) 1361 | } 1362 | } 1363 | } 1364 | 1365 | for ad in &self.proof { 1366 | if ad.is_del() {continue} 1367 | let clause = &mut self.db[ad.clause()]; 1368 | for i in 0..clause.len()-1 { 1369 | let j = rng.gen_range(i+1..clause.len()); 1370 | clause.swap(i, j); 1371 | } 1372 | } 1373 | } 1374 | 1375 | fn parse(opts: SolverOpts, input_file: File, proof_file: impl BufRead) -> (bool, Self) { 1376 | let mut unsat = false; 1377 | let (num_vars, num_clauses, input_file) = 1378 | dimacs::DimacsIter::from(BufReader::new(input_file).bytes().map(|c| c.unwrap())); 1379 | let mut input_file = input_file.zip(1..); 1380 | let mut formula = Vec::with_capacity(num_clauses); 1381 | println!("c parsing input formula with {} variables and {} clauses", num_vars, num_clauses); 1382 | let mut db = Vec::with_capacity(BIGINIT); 1383 | let mut proof = Vec::with_capacity(BIGINIT); 1384 | let mut hash_table: Vec> = std::iter::repeat_with(Vec::new).take(BIGINIT).collect(); 1385 | let mut witness = Vec::with_capacity(INIT); 1386 | let mut file_switch_flag = false; 1387 | let mut reader = TrackLen::new(proof_file); 1388 | let mut proof_file = DRATParser::from(opts.bin_mode, (&mut reader).bytes().map(|c| c.unwrap())).zip(1..); 1389 | let mut active = 0; 1390 | let mut max_var = 0; 1391 | let mut count = 1; 1392 | let mut num_lemmas = 0; 1393 | loop { 1394 | if !file_switch_flag { file_switch_flag |= formula.len() >= num_clauses } 1395 | let (line, del, mut lits) = if !file_switch_flag { 1396 | let (clause, line) = if let Some(c) = input_file.next() {c} else { 1397 | opts.warn(|| println!("\ 1398 | c WARNING: early EOF of the input formula\n\ 1399 | c WARNING: {} clauses less than expected", num_clauses - formula.len())); 1400 | file_switch_flag = true; 1401 | continue 1402 | }; 1403 | for &lit in &clause { 1404 | assert!(lit.abs() <= num_vars as i64, 1405 | "illegal literal {} due to max var {}", lit, num_vars); 1406 | } 1407 | (line, false, clause) 1408 | } else { 1409 | let (step, line) = if let Some(res) = proof_file.next() {res} else {break}; 1410 | match step { 1411 | DRATStep::Comment(_) => continue, 1412 | DRATStep::Add(lits) => (line, false, lits.0), 1413 | DRATStep::Del(lits) => (line, true, lits) 1414 | } 1415 | }; 1416 | max_var = lits.iter().copied().fold(max_var, i64::max); 1417 | 1418 | // split line into witness (only for PR) 1419 | let (pivot, wit) = if let Some((&pivot, lits1)) = lits.split_first() { 1420 | (pivot, lits1.iter().position(|&lit| lit == pivot) 1421 | .map(|i| lits.drain(i+1..).collect())) 1422 | } else { (0, None) }; 1423 | let wit = Witness::new(wit.map(|w| {let n = witness.len(); witness.push(w); n})); 1424 | lits.sort_unstable(); 1425 | if lits.len() != {lits.dedup(); lits.len()} { 1426 | opts.warn(|| println!( 1427 | "c WARNING: detected and deleted duplicate literals at line {}", line)); 1428 | } 1429 | let size = lits.len(); 1430 | if size == 0 && !file_switch_flag { unsat = true } 1431 | if del && matches!(opts.mode, Mode::BackwardUnsat) && size <= 1 { 1432 | opts.warn(|| println!( 1433 | "c WARNING: backward mode ignores deletion of (pseudo) unit clause {}", 1434 | ClauseRef {ida: ClauseId(0), lits: &lits})); 1435 | continue 1436 | } 1437 | let hash = get_hash(&lits); 1438 | if del { 1439 | if opts.delete { 1440 | if let Some(idx) = Self::match_clause(&db, &mut hash_table[hash], &lits) { 1441 | if let Mode::ForwardSat = opts.mode {db[idx].pivot = lits[0]} 1442 | active -= 1; 1443 | proof.push(ProofStep::del(idx)); 1444 | } else { 1445 | opts.warn(|| println!( 1446 | "c WARNING: deleted clause on line {} does not occur: {}", 1447 | line, ClauseRef {ida: ClauseId(0), lits: &lits})); 1448 | } 1449 | } 1450 | } else { 1451 | let ida = ClauseId::new(count, matches!(opts.mode, Mode::ForwardSat) && formula.len() < num_clauses); 1452 | count += 1; 1453 | let idx = db.len(); 1454 | db.push(Clause {ida, lits, pivot, maxdep: 0, witness: wit}); 1455 | hash_table[hash].push(idx); 1456 | active += 1; 1457 | if formula.len() < num_clauses { 1458 | formula.push(ProofStep::add(idx)); 1459 | } else { 1460 | proof.push(ProofStep::add(idx)); 1461 | num_lemmas += 1; 1462 | } 1463 | } 1464 | } 1465 | if matches!(opts.mode, Mode::ForwardSat) && active != 0 { 1466 | opts.warn(|| println!( 1467 | "c WARNING: {} clauses active if proof succeeds", active)); 1468 | for list in hash_table { 1469 | for c in list { 1470 | println!("c {}", db[c]); 1471 | proof.push(ProofStep::del(c)) 1472 | } 1473 | } 1474 | } else { 1475 | drop(hash_table); 1476 | } 1477 | db.shrink_to_fit(); 1478 | 1479 | println!("c finished parsing, read {} bytes from proof file", reader.bytes_read()); 1480 | let n = max_var as usize; 1481 | (unsat, Self { 1482 | opts, 1483 | count, 1484 | db, 1485 | num_vars, 1486 | num_clauses, 1487 | max_var, 1488 | num_lemmas, 1489 | formula, 1490 | proof, 1491 | witness, 1492 | false_stack: Vec::with_capacity(n + 1), 1493 | reason: vec![Reason::NONE; n + 1].into(), 1494 | false_a: MidVec::with_capacity(max_var), 1495 | opt_proof: Vec::with_capacity(2 * num_lemmas + num_clauses), 1496 | rat_set: Vec::with_capacity(INIT), 1497 | pre_rat: Vec::with_capacity(n), 1498 | lrat_table: std::iter::repeat_with(|| None).take(count + 1).collect(), 1499 | deps: Vec::with_capacity(INIT), 1500 | watch_list: MidVec::with_capacity_by(max_var, || Vec::with_capacity(INIT)), 1501 | unit_stack: Vec::with_capacity(n), 1502 | prep: false, 1503 | num_removed: 0, 1504 | num_active: 0, 1505 | forced: 0, 1506 | processed: 0, 1507 | rat_mode: false, 1508 | rat_count: 0, 1509 | num_resolve: 0, 1510 | time: ClauseId(0), 1511 | current: 0, 1512 | }) 1513 | } 1514 | } 1515 | 1516 | fn print_help() -> ! { 1517 | println!("usage: drat-trim [INPUT] [] [