A modern SAT Solver for Propositional Logic in Rust

Splr is a modern SAT solver in Rust, based on Glucose 4.1. It adopts, or adopted, various research results on modern SAT solvers:

Many thanks to SAT researchers.

Please check ChangeLog about recent updates.

Correctness

Though Splr comes with ABSOLUTELY NO WARRANTY, I'd like to show some results.

Version 0.15.0

Install

Just run cargo install splr after installing the latest cargo. Two executables will be installed:

Alternatively, Nix users can use nix build.

Usage

Splr is a standalone program, taking a CNF file. The result will be saved to a file, which format is defined by SAT competition 2011 rules.

plain $ splr cnfs/unif-k3-r4.25-v360-c1530-S1293537826-039.cnf unif-k3-r4.25-v360-c1530-S1293537826-039.cnf 360,1530 |time: 732.01 #conflict: 9663289, #decision: 23326959, #propagate: 546184944 Assignment|#rem: 351, #fix: 0, #elm: 9, prg%: 2.5000 Clause|Remv: 69224, LBD2: 2857, BinC: 1, Perm: 1522 Conflict|entg: 7.0499, cLvl: 12.2451, bLvl: 11.1030, /cpr: 30.74 Learning|avrg: 10.4375, trnd: 1.0069, #RST: 566140, /dpc: 1.18 misc|vivC: 7370, subC: 0, core: 122, /ppc: 61.53 Result|file: ./ans_unif-k3-r4.25-v360-c1530-S1293537826-039.cnf s SATISFIABLE: cnfs/unif-k3-r4.25-v360-c1530-S1293537826-039.cnf

plain $ cat ans_unif-k3-r4.25-v360-c1530-S1293537826-039.cnf c This file was generated by splr-0.15.0 for cnfs/unif-k3-r4.25-v360-c1530-S1293537826-039.cnf c c unif-k3-r4.25-v360-c1530-S1293537826-039.cnf, #var: 360, #cls: 1530 c #conflict: 9663289, #decision: 23326959, #propagate: 546184944, c Assignment|#rem: 351, #fix: 0, #elm: 9, prg%: 2.5000, c Clause|Remv: 69224, LBD2: 2857, BinC: 1, Perm: 1522, c Conflict|entg: 7.0499, cLvl: 12.2451, bLvl: 11.1030, /cpr: 30.74, c Learing|avrg: 10.4375, trnd: 1.0069, #RST: 566140, /dpc: 1.18, c misc|vivC: 7370, subC: 0, core: 122, /ppc: 61.53, c Strategy|mode: generic, time: 732.03, c c config::VarRewardDecayRate 0.960 c assign::NumConflict 9663289 c assign::NumDecision 23326959 c assign::NumPropagation 546184944 c assign::NumRephase 734 c assign::NumRestart 566141 c assign::NumVar 360 c assign::NumAssertedVar 0 c assign::NumEliminatedVar 9 c assign::NumReconflict 653 c assign::NumRepropagation 12460396 c assign::NumUnassertedVar 351 c assign::NumUnassignedVar 351 c assign::NumUnreachableVar 0 c assign::RootLevel 0 c assign::AssignRate 0.000 c assign::DecisionPerConflict 1.179 c assign::PropagationPerConflict 61.527 c assign::ConflictPerRestart 122.388 c assign::ConflictPerBaseRestart 122.388 c assign::BestPhaseDivergenceRate 0.000 c clause::NumBiClause 1 c clause::NumBiClauseCompletion 0 c clause::NumBiLearnt 1 c clause::NumClause 70746 c clause::NumLBD2 2857 c clause::NumLearnt 69224 c clause::NumReduction 1461 c clause::NumReRegistration 0 c clause::Timestamp 0 c clause::LiteralBlockDistance 10.438 c clause::LiteralBlockEntanglement 7.050 c state::Vivification 735 c state::VivifiedClause 7370 c state::VivifiedVar 0 c state::NumCycle 734 c state::NumStage 1461 c state::IntervalScale 1 c state::IntervalScaleMax 1024 c state::BackjumpLevel 11.103 c state::ConflictLevel 12.245 c s SATISFIABLE v 1 -2 3 4 5 6 -7 -8 9 -10 -11 -12 13 -14 ... -360 0

plain $ dmcr cnfs/unif-k3-r4.25-v360-c1530-S1293537826-039.cnf A valid assignment set for cnfs/unif-k3-r4.25-v360-c1530-S1293537826-039.cnf is found in ans_unif-k3-r4.25-v360-c1530-S1293537826-039.cnf

If you want to certificate unsatisfiability, use --certify or -c and use proof checker like Grid.

Firstly run splr with the certificate option -c.

plain $ splr -c cnfs/unif-k3-r4.25-v360-c1530-S1028159446-096.cnf unif-k3-r4.25-v360-c1530-S1028159446-096.cnf 360,1530 |time: 204.09 #conflict: 4018458, #decision: 9511129, #propagate: 221662222 Assignment|#rem: 345, #fix: 7, #elm: 8, prg%: 4.1667 Clause|Remv: 11290, LBD2: 2018, BinC: 137, Perm: 1517 Conflict|entg: 4.5352, cLvl: 8.0716, bLvl: 6.9145, /cpr: 112.08 Learning|avrg: 1.5625, trnd: 0.2219, #RST: 237295, /dpc: 1.07 misc|vivC: 4085, subC: 0, core: 345, /ppc: 52.55 Result|file: ./ans_unif-k3-r4.25-v360-c1530-S1028159446-096.cnf Certificate|file: proof.drat s UNSATISFIABLE: cnfs/unif-k3-r4.25-v360-c1530-S1028159446-096.cnf

A: Verify with drat-trim

plain $ drat-trim cnfs/unif-k3-r4.25-v360-c1530-S1028159446-096.cnf proof.drat c parsing input formula with 360 variables and 1530 clauses c finished parsing c detected empty clause; start verification via backward checking c 1530 of 1530 clauses in core c 2036187 of 4029964 lemmas in core using 68451907 resolution steps c 0 RAT lemmas in core; 908116 redundant literals in core lemmas s VERIFIED c verification time: 105.841 seconds

B: Verify with gratchk

Firstly you have to convert the generated DRAT file to a GRAT file.

```plain $ gratgen cnfs/unif-k3-r4.25-v360-c1530-S1028159446-096.cnf proof.drat -o proof.grat c sizeof(cdbt) = 4 c sizeof(cdbt*) = 8 c Using RAT run heuristics c Parsing formula ... 1ms c Parsing proof (ASCII format) ... 32251ms c Forward pass ... 2073ms c Starting Backward pass c Single threaded mode

0% 10 20 30 40 50 60 70 80 90 100% |----|----|----|----|----|----|----|----|----|----|


c Waiting for aux-threads ...done c Lemmas processed by threads: 2032698 mdev: 0 c Finished Backward pass: 65356ms c Writing combined proof ... 19088ms s VERIFIED c Timing statistics (ms) c Parsing: 32253 c Checking: 67465 c * bwd: 65356 c Writing: 19088 c Overall: 118808 c * vrf: 99720

c Lemma statistics c RUP lemmas: 2032698 c RAT lemmas: 0 c RAT run heuristics: 0 c Total lemmas: 2032698

c Size statistics (bytes) c Number of clauses: 4031493 c Clause DB size: 309680252 c Item list: 128778112 c Pivots store: 16777216 ```

Then verify it with gratchk.

plain $ gratchk unsat cnfs/unif-k3-r4.25-v360-c1530-S1028159446-096.cnf proof.grat c Reading cnf c Reading proof c Done c Verifying unsat s VERIFIED UNSAT

Calling Splr from Rust programs

Since 0.4.0, you can use Splr in your programs. (Here I suppose that you uses Rust 2021.)

```rust use splr::*;

fn main() { let v: Vec> = vec![vec![1, 2], vec![-1, 3], vec![1, -3], vec![-1, 2]]; match Certificate::try_from(v) { Ok(Certificate::SAT(ans)) => println!("s SATISFIABLE: {:?}", ans), Ok(Certificate::UNSAT) => println!("s UNSATISFIABLE"), Err(e) => panic!("s UNKNOWN; {}", e), } } ```

All solutions SAT solver

```rust use splr::*; use std::env::args;

fn main() { let cnf = args().nth(1).expect("takes an arg"); let assigns: Vec = Vec::new(); println!("#solutions: {}", run(&cnf, &assigns)); }

[cfg(feature = "incremental_solver")]

fn run(cnf: &str, assigns: &[i32]) -> usize { let mut solver = Solver::tryfrom(cnf).expect("panic at loading a CNF"); for n in assigns.iter() { solver.addassignment(*n).expect("panic at assertion"); } let mut count = 0; loop { match solver.solve() { Ok(Certificate::SAT(ans)) => { count += 1; println!("s SATISFIABLE({}): {:?}", count, ans); let ans = ans.iter().map(|i| -i).collect::>(); match solver.addclause(ans) { Err(SolverError::Inconsistent) => { println!("c no answer due to level zero conflict"); break; } Err(e) => { println!("s UNKNOWN; {:?}", e); break; } Ok() => solver.reset(), } } Ok(Certificate::UNSAT) => { println!("s UNSATISFIABLE"); break; } Err(e) => { println!("s UNKNOWN; {}", e); break; } } } count } ```

Since 0.4.1, Solver has iter(). So you can iterate on satisfiable 'solution: Vec<i32>'s as:

```rust

[cfg(feature = "incremental_solver")]

for (i, v) in Solver::try_from(cnf).expect("panic").iter().enumerate() { println!("{}-th answer: {:?}", i, v); } ```

Mnemonics used in the progress message

| mnemonic | meaning | | ------------ | ----------------------------------------------------------------------------------------- | | #var | the number of variables used in the given CNF file | | #cls | the number of clauses used in the given CNF file | | time | elapsed CPU time in seconds (or wall-clock time if CPU time is not available) | | #conflict | the number of conflicts | | #decision | the number of decisions | | #propagate | the number of propagates (its unit is literal) | | #rem | the number of remaining variables | | #fix | the number of asserted variables (which has been assigned a value at decision level zero) | | #elm | the number of eliminated variables | | prg% | the percentage of remaining variables / total variables | | Remv | the current number of learnt clauses that are not bi-clauses | | LBD2 | the accumulated number of learnt clauses which LBDs are 2 | | BinC | the current number of binary learnt clauses | | Perm | the current number of given clauses and binary learnt clauses | | entg | the current average of 'Literal Block entanglement' | | cLvl | the EMA of decision levels at which conflicts occur | | bLvl | the EMA of decision levels to which backjumps go | | /cpr | the EMA of conflicts per restart | | avrg | the EMA, Exponential Moving Average, of LBD of learnt clauses | | trnd | the current trend of the LBD's EMA | | #RST | the number of restarts | | /dpc | the EMA of decisions per conflict | | vivC | the number of the vivified clauses | | subC | the number of the clauses subsumed by clause elimination processor | | core | the number of unreachable vars | | /ppc | the EMA of propagations per conflict | | time | the elapsed CPU time in seconds |

Command line options

```plain A modern CDCL SAT solver in Rust Activated features: best phase tracking, stage-based clause elimination, stage-based clause vivification, stage-based dynamic restart threshold, Learning-Rate Based rewarding, reason-side rewarding, stage-based re-phasing, trail saving, unsafe access

USAGE: splr [FLAGS] [OPTIONS] FLAGS: -h, --help Prints help information -C, --no-color Disable coloring -q, --quiet Disable any progress message -c, --certify Writes a DRAT UNSAT certification file -j, --journal Shows log about restart stages -l, --log Uses Glucose-like progress report -V, --version Prints version information OPTIONS: --cl Soft limit of #clauses (6MC/GB) 0 -t, --timeout CPU time limit in sec. 5000 --ecl Max #lit for clause subsume 64 --evl Grow limit of #cls in var elim. 0 --evo Max #cls for var elimination 20000 -o, --dir Output directory . -p, --proof DRAT Cert. filename proof.drat -r, --result Result filename/stdout --vdr Var reward decay rate 0.96 ARGS: DIMACS CNF file ```

Solver description

Splr-0.15.0 adopts the following features by default:

Splr-0.15.0 discarded various dynamic and heuristic-based control schemes used in 0.14.0. The following figure shows the details.

search algorithm in Splr 0.14

Note: I use the following terms here: - a stage -- a span in which solver uses the same restart parameters - a cycle -- a group of continuos spans of which the corresponding Luby values make a non-decreasing sequence - a segment -- a group of continuos cycles which are separated by new maximum Luby values' occurrences

Bibliography

License

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.


2020-2022, Narazaki Shuji