#
A Verified Implementation of the DPLL Algorithm in Dafny^{ †}

^{1}

^{2}

^{*}

^{†}

^{‡}

## Abstract

**:**

## 1. Introduction

**tru**sted and

**e**fficient

**SAT**solver), in the Dafny system [3]. Dafny is a high-level imperative programming language with some object oriented features. Its main characteristic is that all methods in the project can be formally specified by using preconditions, postconditions, and invariants. The specifications are checked at compilation time by using the satisfiability modulo theories (SMT) solver

`Z3`[4]. If a postcondition cannot be established (either due to a timeout or due to the fact that it does not hold), compilation fails; therefore, we can place a high degree of trust in a program verified using the Dafny system. For readers unfamiliar with the Dafny system, we feature a brief overview in Section 3.

- A
- Unit Propagation. Also called boolean constraint propagation, this is a core optimization in high-performance SAT solvers [5]. It refers to the idea of speeding up the search process by using unit clauses, which are clauses whose literals are all currently false, except for one, whose value is not yet set. For every unit clause, the value of the unset literal must be true in any satisfying assignment. Forcing these literals to be true in the current assignment is called unit propagation.
- B
- Fast Data Structures. In order to perform unit propagation as efficiently as possible, the solver must be able to quickly identify unit clauses. To this end, the solver either maintains for each clause counters with the number of literals currently known to be true (resp. false) [5], or uses lazy data structures [6,7].
- C
- Variable Ordering Heuristics. The search space might be very different depending on the order in which the propositional variables are assigned. Variable ordering heuristics use information such as the number of occurrences of a variable in the formula to guide the algorithm [8] by choosing variables in a order that typically reduces the search space.
- D
- Backjumping. In typical backtracking search algorithms, if the current truth assignment does not satisfy the formula, we go one level up in the search tree and reset the value of the last assigned variable. Backjumping [9] takes this idea a step further: if the reason that the current assignment does not satisfy the formula is a variable that has been set earlier, we may go up several levels, thereby improving speed.
- E
- Conflict Analysis. This improvement is intimately tied to backjumping. The idea is to analyze the conflict clause, a clause that is not satisfied by the current assignment, in order to identify a level to which to backjump that is as early as possible [10] in the search tree.
- F
- Clause Learning and Forgetting. Introduced in the
`GRASP`solver [10], clause learning means that, each time a conflict is found in the search, a clause that explains the conflict is added to the initial formula. The idea is that the learned clause is logically entailed by the initial formula and therefore does not change its satisfiability status. The learned clause however prevents the same conflict from manifesting in the future, thereby reducing the search space. Because not all learned clauses are useful, a strategy to delete (forget) such clauses is also typically used. - G
- Restart Strategy. In addition to using variable ordering heuristics, SAT solvers sometimes choose the next variable to assign in a random manner. This makes the search nondeterministic and the running times follow a heavily tailed distribution [11]. To improve on this distribution, SAT solvers regularly drop the current assignment [12] and start the search process over, a process known as restarts.

`DIMACS`format, has also been written in Dafny. The parser is therefore also verified not to contain, for example, bugs such as out-of-bounds errors; however, we do not provide a full functional specification for the parser. Specifying the parser as a function from strings to CNF formulae and proving its correctness is an orthogonal concern. Parsing is therefore the only part of our solver that must be trusted without a computer-checked proof.

- The new implementation features machine integers, which improve performance approximately 10 times in our tests. Going to machine integers from unbounded integers requires proving upper bounds on indices throughout the code.
- The new implementation features mutable data structures for identifying unit clauses. Our previous approach used Dafny sequences (
`seq`), which are immutable and cause a performance drawback because they are updated frequently. The new mutable data structures make the solver significantly faster, but they are more difficult to reason about and verify. - We have implemented and verified a variable ordering heuristic.
- We have also improved the methodology of our verification approach. In particular we have significantly reduced verification time. By carefully specifying invariants and separating concerns in the implementation, the verification time is now approximately 5 min for the entire project.In contrast, in our previous implementation, one method (
`setLiteral`) took approximately 10 min to verify on its own (the entire project used to take about 2 h to verify in its entirety).

## 2. The DPLL Algorithm

**Example**

**1.**

Algorithm 1: The DPLL procedure [23] that we have implemented and verified. |

## 3. Brief Overview of Auto-Active Proofs in Dafny

`requires`keyword, and the postconditions are given after the

`ensures`keyword. The proof is given as a set of annotations consisting of invariants, variants, helper lemmas, and others.

`k`in the sorted array

`T`.

methodbinarySearch(T:array<int>, k :int)returns(r :int)requires∀ i, j • 0 ≤ i < j < T.Length ==> T[i] ≤ T[j];ensuresr ≥ 0 ==> 0 ≤ r < T.Length T[r] = k;ensuresr < 0 ==> k ∉ T[..] {varstart :int:= 0;varend :int:= T.Length - 1;while(start ≤ end)invariant0 ≤ start ≤ T.Length;invariant−1 ≤ end < T.Length;invariantk ∉ T[..start];invariantk ∉ T[end + 1..]; decreases end - start; {varmid :int:= (start + end) / 2;if(k < T[mid]) { end := mid - 1; }else if(k > T[mid]) { start := mid + 1; }else{returnmid; } }return-1; }

`binarySearch`function states that the array argument should be sorted in increasing order of values, while the two postconditions state that the result should be either an index where the key

`k`appears in the array, or a negative number indicating that the key

`k`is not in the array

`T`.

`Z3`SMT solver, which tries to prove their logical validity. If for whatever reason the SMT solver cannot prove a verification condition, compilation of the entire Dafny development fails.

**auto**matic verification (SMT solver) and inter

**active**verification (adding annotations), the Dafny system is called an auto-active prover. Auto-active proofs are sometimes also referred to as assertional proofs.

- The code might not satisfy the specification, either due to
- (a)
- an error in the code or
- (b)
- an error in the specification;

- There might be missing helper annotations that are needed for Dafny to be able to perform the proof;
- The underlying SMT solver might simply not have enough computational resources to finish the proof.

`binarySearch`method as above, which now fails to verify. The only difference is that the precondition stating that the array is sorted, marked by

`(*)`, is specified in a syntactically different manner:

methodbinarySearch(T:array<int>, k :int)returns(r :int)requires∀ j • 0 ≤ j < T.Length − 1 ==> T[j] ≤ T[j + 1]; // (*)ensuresr ≥ 0 ==> 0 ≤ r < T.Length Λ T[r] = k;ensuresr < 0 ==> k ∉ T[..] { // [...] the same code as above; the method now fails to verify }

lemmaplusOne(T :array<int>)requires∀ j • 0 ≤ j < T.Length − 1 ==> T[j] ≤ T[j + 1];ensures∀ j, k • 0 ≤ j < k < T.Length ==> T[j] ≤ T[k]; { ∀ j | 0 ≤ j < T.Lengthensures∀ k • j < k < T.Length ==> T[j] ≤ T[k]; {varkp := j + 1;while(kp < T.Length)invariantj < kp ≤ T.Length;invariant∀ kpp • j < kpp < kp ==> T[j] ≤ T[kpp]; {assertT[j] ≤ T[kp - 1]; // (**) kp := kp + 1; } } }

`while`loop essentially works as a proof by induction, with the invariant serving as the induction hypothesis. In order to help the system prove the invariant, the helper assertion in the line marked

`(**)`is required. This assertion is an immediate logical consequence of the invariant, obtained by instantiating the ∀ quantifier; however, it is impossible for Dafny to make all such instantiations by itself, because in general there are an infinite number of possible instantiations. This is one case where Dafny requires help on the part of the developer. With the lemma verified, it remains to call it in the

`binarySearch`method that uses the alternative formulation of the precondition:

methodbinarySearch(T:array<int>, k :int)returns(r :int)requires∀ j • 0 ≤ j < T.Length − 1 ==> T[j] ≤ T[j + 1]; // (*)ensuresr ≥ 0 ==> 0 ≤ r < T.Length T[r] = k;ensuresr < 0 ==> k ∉ T[..] { plusOne(T); // helper annotation: “call” the lemma // [...] the same code as above; the method now verifies successfully }

`reads`clauses and

`modifies`clauses, which restrict the use of the heap and are important in developments using data structures such as arrays.

## 4. A Verified Implementation of the DPLL Algorithm

#### 4.1. Data Structures

#### 4.1.1. Representing the CNF Formula

`Int32.t`, which we define in the file

`int32.dfy`:

moduleInt32 {newtype{:nativeType "int"} t = x | -2000000 ≤ x < 2000001 const max : t := 2000000; const min : t := -2000000; }

`Int32.t`represents bounded integers. This type requires to prove that all computations involving values of type

`Int32.t`remain within the bounds. The bounds themselves can be set to any larger constants without affecting the proofs. The advantage is that values of type

`Int32.t`are represented by machine integers and hence these computations are very fast.

`int`, have the disadvantage that they should be compiled to big integers, which have a significant performance overhead; therefore, by representing variables and literals as values of type

`Int32.t`instead of

`int`, we gain efficiency.

`DataStructures`(a trait is similar to an abstract class in other object-oriented languages), presented in Figure 3.

`variablesCount`stores the number of propositional variables in the formula. The field

`clauses`stores the formula itself, as a sequence of clauses (each clause being a sequence of literals). Sequences (

`seq`) are immutable in Dafny, but storing clauses as sequences has no significant performance impact, because the clauses are set once at the beginning and never changed. The number of clauses is stored in

`clausesCount`. The array

`clauseLength`stores the number of literals in each clause.

`variablesCount`, and negative literals by bounded integers between $-1$ and −

`variablesCount`.

`DataStructures`has a number of predicates for checking the syntactical validity of propositional variables, literals, clauses, and others. Here is an example predicate that checks whether an integer represents a literal:

predicatevalidLiteral(literal : Int32.t)requiresvalidVariablesCount();reads‘variablesCount; {ifliteral = 0then falseelse if-variablesCount ≤ literal Λ literal ≤ variablesCountthen trueelse false}

#### 4.1.2. Representing the Current Assignment

`decisionLevel`stores the current decision level, which starts at $-1$ and is incremented with each decision variable. The current assignment has three different representations, each of which has its own use:

- The field
`truthAssignment`is an array storing for each propositional variable its current value: unknown is encoded by $-1$, false by 0 and true by 1. This field is useful for quickly (in time $O\left(1\right)$) retrieving the value of a given propositional variable. At the beginning of the search, it is initialized by $-1$ in all positions (no variable is set). - The fields
`traceVariable`and`traceValue`are arrays having the same size that store the current trace. The variable`traceVariable[i]`is the $\mathtt{i}$th variable to be set and it has a value of`traceValue[i]`. As explained in the previous section, the trace is split into layers. We store each layer`j`as two indices,`traceDLStart[j]`and`traceDLEnd[j]`, into the trace. The layer`j`consists of the variables`traceVariable[traceDLStart[j]]`(inclusive) up to`traceVariable[traceDLEnd[j]]`(exclusive). This representation of the truth assignment, as a trace split into layers, is useful for backtracking. As all of these fields are arrays, lookups and updates into them are very efficient. - The field
`assignmentsTrace`stores the entire trace of assignments. As it is marked`ghost`, this field is not used at runtime, but only at verification time, in order to enable the specification and verification of certain properties; therefore, it entails no runtime overhead.

// [...] some parts elided for~brevity

truthAssignment.Length = variablesCount Λ

(∀ i • 0 ≤ i < variablesCount ==> −1 ≤ truthAssignment[i] ≤ 1) Λ

(∀ i • 0 ≤ i < variablesCount ΛtruthAssignment[i] ≠ −1 ==> (i, convertIntToBool(truthAssignment[i]))inassignmentsTrace) Λ

(∀ i • 0 ≤ i < variablesCount ΛtruthAssignment[i] = −1 ==> (i,false) ∉ assignmentsTrace Λ (i,true) ∉ assignmentsTrace)

#### 4.1.3. Quickly Identifying Unit Clauses

`trueLiteralsCount`and

`falseLiteralsCount`are arrays indexed from $\mathtt{0}$ to $\left|\mathtt{clauses}\right|-1$ that store, for each clause, the number of literals in the clause that are currently true and false, respectively. An invariant stating that the two arrays truly contain the required number is computer-checked:

|trueLiteralsCount| = |clauses| Λ ∀ i • 0 ≤ i < |clauses| ==> 0 ≤ trueLiteralsCount[i] = countTrueLiterals(truthAssignment, clauses[i])

`falseLiteralsCount`). The function

`countTrueLiterals`serves as a mathematical specification of the number of literals currently true in a given clause, which works by actually counting one by one the literals that are true in the given clause.

- the ${\mathtt{i}}^{\mathit{th}}$ clause is true in the current assignment:
`trueLiteralsCount[i] > 0`; - the ${\mathtt{i}}^{\mathit{th}}$ clause is false in the current assignment:
`falseLiteralsCount[i] == clauseLength[i]`; - the ${\mathtt{i}}^{\mathit{th}}$ clause is unit in the current assignment:
`trueLiteralsCount[i] == 0 ∧ clauseLength[i] - falseLiteralsCount[i] == 1`.

`DataStructures`, the arrays

`positiveLiteralsToClauses`and

`negativeLiteralsToClauses`are used for updating as efficiently as possible the counters in

`trueLiteralsCount`and

`falseLiteralsCount`after a variable has been set (either due to a new decision, or due to unit propagation). These two arrays are indexed from

`0`to

`variablesCount - 1`. The sequence

`positiveLiteralsToClauses[i]`contains the indices of the clauses in which the variable

`i`occurs as a literal. The sequence

`negativeLiteralsToClauses[i]`contains the indices of the clauses in which the negation of the variable

`i`occurs as a literal.

`i`is set (or unset), it is sufficient to update the counters of the clauses in

`positiveLiteralsToClauses[i]`and

`negativeLiteralsToClauses[i]`, instead of updating all counters. The two arrays satisfy the following computer-checked invariant:

|positiveLiteralsToClauses| = variablesCount Λ ( ∀ variable • 0 ≤ variable < |positiveLiteralsToClauses| ==>(a similar invariant holds for $\mathtt{negativeLiteralsToClauses}$).ghost vars := positiveLiteralsToClauses[variable]; ... (∀ clauseIndex • clauseIndexins ==> variable+1inclauses[clauseIndex]) Λ (∀ clauseIndex • 0 ≤ clauseIndex < |clauses| Λ clauseIndex ∉ s ==> variable+1 ∉ clauses[clauseIndex]))

`valid`, which is added as a precondition and postcondition to all methods of the class/trait. In our case, the trait

`DataStructures`has a predicate

`valid`that consists of the conjunction of the snippets of code shown above, and some more lower-level conditions that we omit for brevity.

#### 4.2. Verified Operations over the Data Structures

`Formula`extends the trait

`DataStructures`by a constructor that sets up the data structures used to represent the formula and the current search state and also by a set of methods that implement several actions that can be taken by the DPLL algorithm:

- Creating a new layer (by increasing the current decision level);
- Setting the value of a propositional variable (either because it is a decision variable, or because of unit propagation);
- Build the current layer by setting a decision variable and performing all unit propagations necessary;
- Undo the assignments performed in the last layer.

`Formula`class (file

`solver/formula.dfy`). As part of the implementation of each method, we show that it preserves the data structure invariants described earlier.

#### 4.2.1. The Method `increaseDecisionLevel`

methodincreaseDecisionLevel()requiresvalidVariablesCount();requiresvalidAssignmentTrace();requiresdecisionLevel < variablesCount - 1;requiresdecisionLevel ≥ 0 ==> traceDLStart[decisionLevel] < traceDLEnd[decisionLevel];

modifies‘decisionLevel, traceDLStart, traceDLEnd;

ensuresdecisionLevel =old(decisionLevel) + 1;ensuresvalidAssignmentTrace();ensurestraceDLStart[decisionLevel] = traceDLEnd[decisionLevel];ensuresgetDecisionLevel(decisionLevel) = {};ensures∀ i • 0 ≤ i < decisionLevel ==>old(getDecisionLevel(i)) = getDecisionLevel(i);

`getDecisionLevel`returns all assignments performed at a given decision level:

functiongetDecisionLevel(dL : Int32.t) :set<(Int32.t,bool)>reads‘variablesCount, ‘decisionLevel, ‘traceDLStart, ‘traceDLEnd, ‘traceVariable, ‘traceValue, traceDLStart, traceDLEnd, traceVariable, traceValue, ‘assignmentsTrace;requiresvalidVariablesCount();requiresvalidAssignmentTrace()requires−1 ≤ dL ≤ decisionLevel;requirestraceVariable.Length = variablesCountas int;ensuresgetDecisionLevel(dL) ≤ assignmentsTrace; {ifdL = −1then{}else(setj | jinassignmentsTrace Λ j.0intraceVariable[traceDLStart[dL]..traceDLEnd[dL]]) }

`increaseDecisionLevel`ensures that the current assignment remains unchanged.

#### 4.2.2. The Method `setVariable`

methodsetVariable(variable : Int32.t, value :bool)requiresvalid();requiresvalidVariable(variable);requirestruthAssignment[variable] = -1;requires0 ≤ decisionLevel; // not emptymodifiestruthAssignment, traceVariable, traceValue, traceDLEnd, ‘assignmentsTrace, trueLiteralsCount, falseLiteralsCount;

ensuresvalid();ensuresvalue =false==>old(truthAssignment[..])[variableas int:= 0] = truthAssignment[..];ensuresvalue =true==>old(truthAssignment[..])[variableas int:= 1] = truthAssignment[..];ensurestraceDLStart[decisionLevel] < traceDLEnd[decisionLevel];ensurestraceVariable[traceDLEnd[decisionLevel]-1] = variable;ensurestraceValue[traceDLEnd[decisionLevel]-1] = value;ensures∀ i • 0 ≤ i < variablesCount Λ i ≠ decisionLevel ==> traceDLEnd[i] =old(traceDLEnd[i]);ensures∀ i • 0 ≤ i < variablesCount Λ i ≠old(traceDLEnd[decisionLevel]) ==> traceVariable[i] =old(traceVariable[i]) Λ traceValue[i] =old(traceValue[i]);ensures∀ x • 0 ≤ x <old(traceDLEnd[decisionLevel]) ==> traceVariable[x] =old(traceVariable[x]);

ensures∀ i • 0 ≤ i < decisionLevel ==>old(getDecisionLevel(i)) = getDecisionLevel(i);

ensuresassignmentsTrace =old(assignmentsTrace) + {(variable, value)};ensurescountUnsetVariables(truthAssignment[..]) + 1 =old(countUnsetVariables(truthAssignment[..]));

`setVariable`is to efficiently update the counters in the arrays

`trueLiteralsCount`and

`falseLiteralsCount`in a provably correct manner. To this end, the method steps over all affected clauses (stored in

`negativeLiteralsToClauses[variable]`and in

`positiveLiteralsToClauses[variable]`) and updates the counters only for these clauses. The technical difficulty in the proof is to reason about the clauses that are not affected and show that their counters do not need to change.

#### 4.2.3. The Method `setLiteral`

`setVariable`to set the value of the variable correspondingly, and then performs unit propagation repeatedly, until no more unit clauses are found. We show its signature and its specification:

methodsetLiteral(literal : Int32.t, value :bool)requiresvalid();requiresvalidLiteral(literal);requiresgetLiteralValue(truthAssignment[..], literal) = -1;requires0 ≤ decisionLevel;

modifiestruthAssignment, trueLiteralsCount, falseLiteralsCount, traceDLEnd, traceValue, traceVariable, ‘assignmentsTrace;

ensuresvalid();ensurestraceDLStart[decisionLevel] < traceDLEnd[decisionLevel];ensures∀ x • 0 ≤ x <old(traceDLEnd[decisionLevel]) ==> traceVariable[x] =old(traceVariable[x]);ensuresassignmentsTrace =old(assignmentsTrace) + getDecisionLevel(decisionLevel);ensures∀ i • 0 ≤ i < decisionLevel ==>old(getDecisionLevel(i)) = getDecisionLevel(i);ensurescountUnsetVariables(truthAssignment[..]) <old(countUnsetVariables(truthAssignment[..]));ensures(ghost var(variable, val) := convertLVtoVI(literal, value); isSatisfiableExtend(old(truthAssignment[..])[variableasint:= val]) ⇔ isSatisfiableExtend(truthAssignment[..]) );

decreasescountUnsetVariables(truthAssignment[..]), 0;

`setVariable`, where a single variable is affected, several variables might be set in

`setLiteral`as a result of unit propagation; therefore, the current truth assignment might change in several positions, hence the more complicated postconditions.

`setLiteral`, updated with the value of the decision variable.

`setLiteral`is part of a chain of mutually recursive functions shown in Figure 4.

`unitPropagation`takes a variable and the value that is has just been assigned:

and checks all clauses that might have become unit after this assignment. It uses the arraysmethodunitPropagation(variable : Int32.t, value :bool)

`negativeLiteralsToClauses[variable]`and

`positiveLiteralsToClauses[variable]`to only inspect the relevant clauses, which ensures that, in general, just a small fraction of the clauses are accessed at this step. When it identifies a unit clause, it calls

`propagate`:

which takes as input the index of a unit clause and it usesmethodpropagate(clauseIndex : Int32.t)

`setLiteral`recursively to make the unknown literal in the unit clause true. We only show the signature of the methods

`unitPropagation`and

`propagate`, because the specification is similar to the one for

`setLiteral`.

`decreases`annotation in the specification of the method

`setLiteral`shown above. The variant counts the number of variables that have not yet been set, and it is guaranteed to decrease strictly at every recursion step, without ever becoming negative, thereby ensuring a machine-checked proof of termination.

#### 4.2.4. The Method `revertLastDecisionLevel`

`positiveLiteralsToClauses`and

`negativeLiteralsToClauses`. Its signature and specification are:

methodrevertLastDecisionLevel()requiresvalid();requires0 ≤ decisionLevel;

modifies‘assignmentsTrace, ‘decisionLevel, truthAssignment, trueLiteralsCount, falseLiteralsCount, traceDLEnd;

ensuresdecisionLevel =old(decisionLevel) - 1;ensuresassignmentsTrace =old(assignmentsTrace) -old(getDecisionLevel(decisionLevel));ensuresvalid();ensures∀ i • 0 ≤ i ≤ decisionLevel ==>old(getDecisionLevel(i)) = getDecisionLevel(i);ensuresdecisionLevel > −1 ==> traceDLStart[decisionLevel] < traceDLEnd[decisionLevel];

#### 4.3. Proof of the Main Algorithm

`solve`method in the file

`solver/solver.dfy`, which is specified as follows:

methodsolve()returns(result : SATUNSAT)requiresformula.valid();requiresformula.decisionLevel > −1 ==> formula.traceDLStart[formula.decisionLevel] < formula.traceDLEnd[formula.decisionLevel];

modifiesformula.truthAssignment, formula.traceVariable, formula.traceValue, formula.traceDLStart, formula.traceDLEnd, formula‘decisionLevel, formula‘assignmentsTrace, formula.trueLiteralsCount, formula.falseLiteralsCount;

ensuresformula.valid();ensuresold(formula.decisionLevel) = formula.decisionLevel;ensuresold(formula.assignmentsTrace) = formula.assignmentsTrace;ensures∀ i • 0 ≤ i ≤ formula.decisionLevel ==>old(formula.getDecisionLevel(i)) = formula.getDecisionLevel(i);ensuresformula.decisionLevel > −1 ==> formula.traceDLStart[formula.decisionLevel] < formula.traceDLEnd[formula.decisionLevel];

ensuresresult.SAT? ==> formula.validValuesTruthAssignment(result.tau);ensuresformula.countUnsetVariables(formula.truthAssignment[..]) = formula.countUnsetVariables(old(formula.truthAssignment[..]));

ensuresresult.SAT? ==> formula.isSatisfiableExtend(formula.truthAssignment[..]);ensuresresult.UNSAT? ==> ¬formula.isSatisfiableExtend(formula.truthAssignment[..]);

decreasesformula.countUnsetVariables(formula.truthAssignment[..]), 1;

`solve`and

`step`. The implementation of the method

`solve`closely follows Algorithm 1:

(some helper assertions are elided for brevity).methodsolve()returns(result : SATUNSAT) // [...] specification elided {varhasEmptyClause :bool:= formula.getHasEmptyClause();if(hasEmptyClause) {returnUNSAT; }varisEmpty :bool:= formula.getIsEmpty();if(isEmpty) { result := SAT(formula.truthAssignment[..]);returnresult; }varliteral := formula.chooseLiteral(); result := step(literal,true);if(result.SAT?) {returnresult; } result := step(literal,false);returnresult; }

`step`method, for efficiency reasons: only the clauses that have a chance of becoming unit after setting the value of the chosen literal are inspected. An initial unit propagation that takes into account all clauses is also performed just before the method

`solve`is called.

`step`method is to set the value of the literal, perform unit propagation, and then call the method

`solve`recursively. The reason that we need a separate method, called

`step`, is to have a uniform specification for both the case where the literal is set to true and the case where it is set to false. The implementation of the

`step`method can be seen in Section 5.

`solve`first checks whether the current truth assignment trivially makes the formula true or false and it simply returns

`SAT`or

`UNSAT`in these cases.

`chooseLiteral`method. This method implements the variable ordering heuristic that we have discussed earlier. It chooses the literal that occurs most frequently in the clauses of the formula with the fewest unset literals among the clauses that are not yet satisfied. It returns the negation of this literal. The idea behind this heuristic is to speed up the algorithm by making clauses unit as quickly as possible.

`solve`method starts the search process by first setting the literal to true and then, if needed, to false. It achieves this in a modular fashion by calling the method

`step`, which sets the literal to the given boolean value and performs unit propagation using the previously described method

`setLiteral`. The method

`step`calls the method

`solve`recursively. At the end, it reverts the assignments. The specification of the

`step`methods closely follows the specification of the

`solve`method:

methodstep(literal : Int32.t, value :bool)returns(result : SATUNSAT)requiresformula.valid();requiresformula.decisionLevel < formula.variablesCount - 1;requiresformula.decisionLevel > −1 ==> formula.traceDLStart[formula.decisionLevel] < formula.traceDLEnd[formula.decisionLevel];requires¬formula.hasEmptyClause();requires¬formula.isEmpty();requiresformula.validLiteral(literal);requiresformula.getLiteralValue(formula.truthAssignment[..], literal) = -1;

modifiesformula.truthAssignment, formula.traceVariable, formula.traceValue, formula.traceDLStart, formula.traceDLEnd, formula‘decisionLevel, formula‘assignmentsTrace, formula.trueLiteralsCount, formula.falseLiteralsCount;

ensuresformula.valid();ensures old(formula.decisionLevel) = formula.decisionLevel;ensures old(formula.assignmentsTrace) = formula.assignmentsTrace;ensures∀ i • 0 ≤ i ≤ formula.decisionLevel ==>old(formula.getDecisionLevel(i)) = formula.getDecisionLevel(i);ensuresformula.decisionLevel > −1 ==> formula.traceDLStart[formula.decisionLevel] < formula.traceDLEnd[formula.decisionLevel];

ensuresresult.SAT? ==> formula.validValuesTruthAssignment(result.tau);ensuresresult.SAT? ==> (var(variable, val) := formula.convertLVtoVI(literal, value); formula.isSatisfiableExtend(formula.truthAssignment[..][variable := val]));

ensuresresult.UNSAT? ==> (var(variable, val) := formula.convertLVtoVI(literal, value); ¬formula.isSatisfiableExtend(formula.truthAssignment[..][variable := val]));

ensuresformula.countUnsetVariables(formula.truthAssignment[..]) = formula.countUnsetVariables(old(formula.truthAssignment[..]));

decreasesformula.countUnsetVariables(formula.truthAssignment[..]), 0;

`solve`are the postconditions encoding the functional correctness of the algorithm:

- If the
`solve`method returns`SAT`, then the current assignment can be extended to a satisfying assignment; - If the
`solve`method returns`UNSAT`, then no assignment extending the current assignment satisfies the formula.

`solve`method ends with the same truth assignment as the one it starts with. This means that it reverts the changes to the truth assignment, even if it finds a satisfying assignment. Otherwise, the preconditions and postconditions for the method

`solve`would become more verbose and less elegant. This undo process does not affect our ability to implement the two watched literals data structure in the future, because we only check that the truth assignment remains the same, not the helper data structures that help quickly identify unit clauses.

## 5. Benchmarks

#### 5.1. Benchmarking Methodology

#### 5.1.1. Machine

#### 5.1.2. Software

`BenchExec`framework [25], which helps ensure reproducibility, accurate measurement of performance, and limits resource usage of the benchmarked tool. For all solvers, we have used

`BenchExec`(https://github.com/sosy-lab/benchexec, accessed on 1 June 2022) to limit resource usage by using the following settings for each run: time limit set to

`120 s`, memory limit set to

`1024 MB`, CPU core limit set to

`1`.

#### 5.1.3. Tasks

`DIMACS`files (they end with two lines containing

`%`and

`0`, respectively, which could be mistaken for the empty clause by a naive parser). All code necessary to reproduce the benchmark can be found at https://github.com/andricicezar/truesat (accessed on 1 June 2022).

#### 5.2. Machine Integers

`TrueSAT`by using machine integers for representing variables and literals instead of mathematical integers. To understand this, we have created a modified version of TrueSAT where the type representing variables and literals is changed back to Dafny type

`int`, which represents mathematical integers. We denote this version by TrueSAT (BigInteger) in the graphs, because mathematical integers are compiled into instances of the class BigInteger (which represents integers as arrays of digits).

#### 5.3. DPLL Solvers

`TrueSAT`against other solvers implementing the DPLL algorithm. The TrueSAT solver has been compiled using version 3.6.0.40511 of the Dafny system and version 6.12.0.179 of the

`Mono`just-in-time compiler. The source code can be found in the

`truesat_src`folder of our repository.

`cs_solver`folder of our repository.

`cpp_solver`folder of our repository.

`Minlog`and compiled to Haskell. This solver also implements the DPLL algorithm. We denote it by Minlog solver; in order to run it on our benchmark we have extended it slightly by adding an unverified parser for

`DIMACS`files.

`Minlog`solver, which also implements the DPLL algorithm and is verified in the

`Minlog`proof assistant, is slower than the three solvers above. This is expected, since emphasis is not placed on speed in its implementation: the solver is implemented as a set of recursive

`Haskell`functions, with immutable data structures.

#### 5.4. CDCL Solvers

`versat`, and the verified solver IsaSAT. As these solvers implement the full CDCL algorithm, which can be exponentially faster than DPLL, they are expected to outperform our solver. Figure 9 summarizes the performance of these four solvers.

`-O2`level using

`g++`version 11.2.0. We have run MiniSAT using its default options.

`versat`is one of the earliest to be verified. It is written in the Guru dependently typed language and its code is extracted to C. It is verified formally only for soundness (not completeness, nor termination) and some checks are deferred to runtime. We have compiled the extracted C code obtained from the homepage of the author (https://homepage.divms.uiowa.edu/~astump/software.html, accessed on 1 June 2022) using the

`gcc`compiler version 11.2.0 at

`-O2`optimization level. We have altered the solver slightly to read its input from a file instead of the standard input in order to be compatible with the

`BenchExec`framework.

`IsaFOL`) project. It is written in

`Isabelle/HOL`(the

`Isabelle`proof assistant, instantiated with the theory of higher-order logic) and it is verified using the Isabelle refinement framework. At the top there is the CDCL calculus, which is refined down to an executable version. We have used the IsaSAT code extracted to the programming language

`ML`in the corresponding folder (

`Weidenbach_Book/IsaSAT/code/ML`) of the

`//bitbucket.org/isafol/isafol.git`repository (accessed 27 May 2022). We have compiled it using a recent version of the

`mlton`compiler (20210117-1.amd64-linux-glibc2.31), which is known to be the

`ML`compiler that produces the fastest executables.

`versat`solvers are an order of magnitude slower than MiniSAT, and the TrueSAT solver is an order of magnitude slower than IsaSAT and

`versat`. The solver

`versat`outperforms IsaSAT on the smaller inputs, but it seems to lose this advantage as the input becomes larger. The verified solvers are therefore still quite far of the performance of MiniSAT. The potential advantage of TrueSAT is that it is roughly as fast as a native (C++) implementation of the same algorithm, as we have discussed earlier. While not as high performance as a CDCL solver, extending it to CDCL offer a path towards a verified solver roughly as efficient as a native CDCL solver such as MiniSAT.

## 6. Related Work

`versat`[27]. It has been implemented in the Guru language, which uses dependent types for verification. The solver is extracted to efficient C code, where the imperative data structures rely on reference counting and on a statically enforced read/write discipline. The solver is only verified to be sound (not verified to be complete, nor terminating). Some checks are delayed until runtime; these checks (if they fail) could be a source of incompleteness. It implements several optimizations, such as conflict analysis and clause learning, which enable it to be quite efficient. The soundness criterion in

`versat`is slightly different from the one in

`TrueSAT`:

`versat`is verified to output UNSAT if the input formula allows for a resolution proof of the empty clause, while

`TrueSAT`is verified to output UNSAT if the input formula is semantically unsatisfiable. Of course, the two criteria (existence of a resolution proof of the empty clause and the formula being semantically unsatisfiable) are equivalent in propositional logic, but functional correctness proofs that are based on one or another can be very different.

`Isabelle/HOL`by Marić [28]. The Hoare triples associated to the solver pseudo-code are shown to be valid in the

`Isabelle/HOL`proof assistant. In subsequent work [29], Marić and Janičić have proven in Isabelle the functional correctness of a SAT solver that is represented as an abstract transition system and also of a shallow embedding of CDCL as a set of recursive functions [30].

`versat`and they show it is roughly as efficient on small instances, but on industrial problems it can be slower, because

`versat`implements additional optimizations, such as clause learning.

`Isabelle/HOL`proof assistant and it is part of the Isabelle Formalization of Logic project. The solver is verified using a refinement technique. At the top of the refinement chain there are logical calculi, such as CDCL, which are verified formally to be sound, complete, and terminating. These calculi are shown to be refined by lower level programs. At the bottom of the refinement chain there is an implementation in Standard ML. The framework also contains meta-theoretical results. Fleury [32] benchmarks the solver as still being two orders of magnitude slower than a state-of-the-art C solver and proposes additional optimizations. In our own solver, we do not prove any meta-theoretical properties of DPLL/CDCL; we concentrate on obtaining a verified imperative SAT solver in an auto-active manner. A key challenge is that the verification of Dafny code may take a lot of time in certain cases and we have to optimize our code for verification time as well.

`PVS`system, a decision procedure based on DPLL using sub-typing and dependent types. Efficiency seem to be secondary concern in these last two approaches.

## 7. Discussion

`modifies`and

`reads`annotations, conditions, post-conditions, lemmas, functions, predicates, helper assertions, ghost variables. We keep function methods and predicate methods, which serve as both executable code and as specification. The proportion of lines containing executable code in the entire development is approximately 1 to 5 (657 to 3303). For every line of executable code we have about 4 lines of logic (proofs, specifications).

- Patterns that improve the modularity of the code.
- Avoid nested loops, because they typically require sharing some invariants between the inner and the outer loop. This increases duplication of code, decreases elegance, and increases verification time.We have made use of this pattern in the
`revertLastDecisionLevel`method (in the file`solver/formula.dfy`), whose purpose is to revert the assignments made in the last decision level. The code of the method is very simple: it calls the`removeLastVariable`method repeatedly in a loop, and the`removeLastVariable`method also has a simple loop to update the counters.It would be simple to inline it and obtain two nested loops, but this would lead to a significant increase in verification time for the`revertLastDecisionLevel`method. - Use methods with few lines of code. We have found that it is better to extract basic blocks as a separate method, even if they consist of only a few lines of code. This forces to programmer to better understand the role of such basic blocks and improves modularity.In a typical imperative programming language, the programmer would simply inline these basic blocks. In our Dafny development, it is not unusual to have methods with few lines of code that have a larger specification, which might require a significant number of helper assertions to prove.

- Patterns that improve the modularity of specifications.
- Avoid nested quantifiers. We use this pattern when a formula such as $\forall x.\exists y.P(x,y)$ occurs in the specification. For such cases, we define a new predicate, $Q\left(x\right)$, that is equivalent to the subformula $\exists y.P(x,y)$. We then use $\forall x.Q\left(x\right)$ instead of $\forall x.\exists y.P(x,y)$.This helps improve the development in two distinct ways: firstly, it forces the developer to give a proper name, $Q\left(x\right)$, to the $\exists y.P(x,y)$ subformula, thereby clarifying their intention, and (2) it helps the underlying
`Z3`prover, which uses pattern-based quantifier instantiation, to perform better [37]. - Use as few
`modifies`and`reads`clauses as necessary. This is known to improve the verification time, sometimes significantly, because the prover knows that any object not under a`modifies`clause of a method remains the same after the method call. In particular, we have made extensive use of the less well-known backtick operator in`modifies`clauses, which allows to specify that a method is allowed to modify a particular field of an object, instead of the entire object.

`/proc`command line switch. Once the current lemma or method is verified, Dafny is run again on the entire project to check for other issues. We have also discovered that the

`Z3`axiom profiler [38], which allows to understand what verification conditions take a long time and why, does not scale well to projects the size of our solver.

- Shorten the verification time for individual methods and lemmas and make verification time more predictable,
- Improve error messages for verification conditions that fail to verify, and
- Construct a method better than using helper
`assert`ions for manually guiding the verifier.

## Author Contributions

## Funding

## Institutional Review Board Statement

## Informed Consent Statement

## Data Availability Statement

## Conflicts of Interest

## References

- Brummayer, R.; Lonsing, F.; Biere, A. Automated Testing and Debugging of SAT and QBF Solvers. In Proceedings of the 13th International Conference on Theory and Applications of Satisfiability Testing, SAT 2010, Edinburgh, UK, 11–14 July 2010; pp. 44–57. [Google Scholar] [CrossRef][Green Version]
- Balyo, T.; Heule, M.J.H.; Järvisalo, M. SAT Competition 2016: Recent Developments. In Proceedings of the Thirty-First AAAI Conference on Artificial Intelligence, San Francisco, CA, USA, 4–9 February 2017; pp. 5061–5063. [Google Scholar]
- Leino, K.R.M. Developing verified programs with Dafny. In Proceedings of the 35th International Conference on Software Engineering, ICSE ’13, San Francisco, CA, USA, 18–26 May 2013; pp. 1488–1490. [Google Scholar] [CrossRef]
- De Moura, L.M.; Bjørner, N. Z3: An Efficient SMT Solver. In Proceedings of the 14th International Conference on Tools and Algorithms for the Construction and Analysis of Systems, TACAS 2008, Budapest, Hungary, 29 March–6 April 2008; Volume 4963, pp. 337–340. [Google Scholar] [CrossRef][Green Version]
- Crawford, J.M.; Auton, L.D. Experimental Results on the Crossover Point in Random 3-SAT. Artif. Intell.
**1996**, 81, 31–57. [Google Scholar] [CrossRef][Green Version] - Zhang, H.; Stickel, M.E. Implementing the Davis-Putnam Method. J. Autom. Reason.
**2000**, 24, 277–296. [Google Scholar] [CrossRef] - Moskewicz, M.W.; Madigan, C.F.; Zhao, Y.; Zhang, L.; Malik, S. Chaff: Engineering an Efficient SAT Solver. In Proceedings of the 38th Design Automation Conference, DAC 2001, Las Vegas, NV, USA, 18–22 June 2001; pp. 530–535. [Google Scholar] [CrossRef]
- Hooker, J.N.; Vinay, V. Branching Rules for Satisfiability. J. Autom. Reason.
**1995**, 15, 359–383. [Google Scholar] [CrossRef][Green Version] - Prosser, P. Hybrid Algorithms for the Constraint Satisfaction Problem. Comput. Intell.
**1993**, 9, 268–299. [Google Scholar] [CrossRef][Green Version] - Marques Silva, J.P.; Sakallah, K.A. GRASP: A Search Algorithm for Propositional Satisfiability. IEEE Trans. Comput.
**1999**, 48, 506–521. [Google Scholar] [CrossRef][Green Version] - Gomes, C.P.; Selman, B.; Crato, N.; Kautz, H.A. Heavy-Tailed Phenomena in Satisfiability and Constraint Satisfaction Problems. J. Autom. Reason.
**2000**, 24, 67–100. [Google Scholar] [CrossRef] - Biere, A.; Fröhlich, A. Evaluating CDCL Restart Schemes. In Proceedings of Pragmatics of SAT 2015 and 2018; Berre, D.L., Järvisalo, M., Eds.; EPiC Series in Computing; EasyChair: Austin, TX, USA, 2018; Volume 59, pp. 1–17. [Google Scholar] [CrossRef]
- Davis, M.; Putnam, H. A Computing Procedure for Quantification Theory. J. ACM
**1960**, 7, 201–215. [Google Scholar] [CrossRef] - Davis, M.; Logemann, G.; Loveland, D.W. A machine program for theorem-proving. Commun. ACM
**1962**, 5, 394–397. [Google Scholar] [CrossRef] - Bayardo, R.J., Jr.; Schrag, R. Using CSP Look-Back Techniques to Solve Real-World SAT Instances. In Proceedings of the Fourteenth National Conference on Artificial Intelligence and Ninth Innovative Applications of Artificial Intelligence Conference, AAAI 97, IAAI 97, Providence, RI, USA, 27–31 July 1997; pp. 203–208. [Google Scholar]
- Iordache, V.; Ciobâcă, Ş. Verifying the Conversion into CNF in Dafny. In Proceedings of the 27th International Workshop on Logic, Language, Information, and Computation, WoLLIC 2021, Virtual Event, 5–8 October 2021; Volume 13038, pp. 150–166. [Google Scholar] [CrossRef]
- Schlichtkrull, A. Formalization of Logic in the Isabelle Proof Assistant. Ph.D. Thesis, Technical University of Denmark, Lyngby, Denmark, 2018. [Google Scholar]
- Leroy, X. A Formally Verified Compiler Back-end. J. Autom. Reason.
**2009**, 43, 363–446. [Google Scholar] [CrossRef][Green Version] - Hawblitzel, C.; Petrank, E. Automated Verification of Practical Garbage Collectors. Log. Methods Comput. Sci.
**2010**, 6, 1–41. [Google Scholar] [CrossRef][Green Version] - Hawblitzel, C.; Howell, J.; Lorch, J.R.; Narayan, A.; Parno, B.; Zhang, D.; Zill, B. Ironclad Apps: End-to-End Security via Automated Full-System Verification. In Proceedings of the 11th USENIX Symposium on Operating Systems Design and Implementation, OSDI ’14, Broomfield, CO, USA, 6–8 October 2014; pp. 165–181. [Google Scholar]
- Bhargavan, K.; Fournet, C.; Kohlweiss, M. miTLS: Verifying Protocol Implementations against Real-World Attacks. IEEE Secur. Priv.
**2016**, 14, 18–25. [Google Scholar] [CrossRef][Green Version] - Zinzindohoué, J.K.; Bhargavan, K.; Protzenko, J.; Beurdouche, B. HACL*: A Verified Modern Cryptographic Library. In Proceedings of the 2017 ACM SIGSAC Conference on Computer and Communications Security, CCS 2017, Dallas, TX, USA, 30 October–3 November 2017; pp. 1789–1806. [Google Scholar] [CrossRef][Green Version]
- Andrici, C.C.; Ciobâcă, Ş. Verifying the DPLL Algorithm in Dafny. In Proceedings of the Third Symposium on Working Formal Methods, Timişoara, Romania, 3–5 September 2019; Volume 303, pp. 3–15. [Google Scholar] [CrossRef][Green Version]
- Gomes, C.P.; Kautz, H.A.; Sabharwal, A.; Selman, B. Satisfiability Solvers. In Handbook of Knowledge Representation; van Harmelen, F., Lifschitz, V., Porter, B.W., Eds.; Elsevier: Amsterdam, The Netherlands, 2008; pp. 89–134. [Google Scholar] [CrossRef]
- Beyer, D.; Löwe, S.; Wendler, P. Reliable benchmarking: Requirements and solutions. Int. J. Softw. Tools Technol. Transf.
**2019**, 21, 1–29. [Google Scholar] [CrossRef] - Berger, U.; Lawrence, A.; Forsberg, F.N.; Seisenberger, M. Extracting verified decision procedures: DPLL and Resolution. Log. Methods Comput. Sci.
**2015**, 11, 1–18. [Google Scholar] [CrossRef][Green Version] - Oe, D.; Stump, A.; Oliver, C.; Clancy, K. versat: A Verified Modern SAT Solver. In Proceedings of the 13th International Conference on Verification, Model Checking, and Abstract Interpretation, VMCAI 2012, Philadelphia, PA, USA, 22–24 January 2012; pp. 363–378. [Google Scholar] [CrossRef][Green Version]
- Marić, F. Formalization and Implementation of Modern SAT Solvers. J. Autom. Reason.
**2009**, 43, 81–119. [Google Scholar] [CrossRef] - Marić, F.; Janičić, P. Formalization of Abstract State Transition Systems for SAT. Log. Methods Comput. Sci.
**2011**, 7, 1–37. [Google Scholar] [CrossRef][Green Version] - Marić, F. Formal verification of a modern SAT solver by shallow embedding into Isabelle/HOL. Theor. Comput. Sci.
**2010**, 411, 4333–4356. [Google Scholar] [CrossRef][Green Version] - Blanchette, J.C.; Fleury, M.; Lammich, P.; Weidenbach, C. A Verified SAT Solver Framework with Learn, Forget, Restart, and Incrementality. J. Autom. Reason.
**2018**, 61, 333–365. [Google Scholar] [CrossRef] [PubMed][Green Version] - Fleury, M. Optimizing a Verified SAT Solver. In Proceedings of the 11th NASA Formal Methods Symposium, NFM 2019, Houston, TX, USA, 7–9 May 2019; pp. 148–165. [Google Scholar] [CrossRef]
- Lescuyer, S. Formalizing and Implementing a Reflexive Tactic for Automated Deduction in Coq. Ph.D. Thesis, Université Paris Sud-Paris XI, Bures-sur-Yvette, France, 2011. [Google Scholar]
- Shankar, N.; Vaucher, M. The Mechanical Verification of a DPLL-Based Satisfiability Solver. Electron. Notes Theor. Comput. Sci.
**2011**, 269, 3–17. [Google Scholar] [CrossRef][Green Version] - Lammich, P. Efficient Verified (UN)SAT Certificate Checking. J. Autom. Reason.
**2020**, 64, 513–532. [Google Scholar] [CrossRef][Green Version] - Wetzler, N.; Heule, M.; Hunt, W.A.H., Jr. DRAT-trim: Efficient Checking and Trimming Using Expressive Clausal Proofs. In Proceedings of the 17th International Conference on Theory and Applications of Satisfiability Testing, SAT 2014, Vienna, Austria, 14–17 July 2014; Volume 8561, pp. 422–429. [Google Scholar] [CrossRef]
- Moskal, M. Programming with Triggers. In Proceedings of the 7th International Workshop on Satisfiability Modulo Theories, SMT ’09, Montreal, QC, Canada, 2–3 August 2009; pp. 20–29. [Google Scholar] [CrossRef]
- Becker, N.; Müller, P.; Summers, A.J. The Axiom Profiler: Understanding and Debugging SMT Quantifier Instantiations. In Proceedings of the 25th International Conference on Tools and Algorithms for the Construction and Analysis of Systems, TACAS 2019, Prague, Czech Republic, 6–11 April 2019; Volume 11427, pp. 99–116. [Google Scholar] [CrossRef][Green Version]

**Figure 1.**The search space of the DPLL algorithm on the formula in Example 1. Each node contains the current truth assignment and the edges represent assignments made by the algorithm. Vertical edges represent unit propagations, whereas the oblique edges represent decisions.

**Figure 2.**The assignments trace corresponding to the last search state in Figure 1. The first layer consists only of the decision variable ${x}_{1}$. The second layer consists of the decision variable ${x}_{2}$ and the propagated variable ${x}_{3}$. Literals colored in blue are true. As each clause has at least one true literal, the formula is satisfied by the current assignment.

**Figure 3.**The fields (file

`solver/data_structures.dfy`) we use for storing the formula and the solver state. The fields are presented in a slightly different order to improve presentation.

**Figure 5.**Flowchart of method

`solve`. A level-0 propagation of unit clauses is performed just before the call to the

`solve`method and is not shown in the figure.

**Figure 6.**Example of how Dafny compiles the code to C#. Aside from the minor and mostly cosmetic changes, note that proofs are not copied over. We add the spacing ourselves to improve readability.

**Figure 7.**Comparison of TrueSAT and a version of

`TrueSAT`using mathematical integers instead of machine integers. There are 2000 tasks with 100 variables, 200 tasks with 150 and 175 variables each, and 199 tasks with 200 variables. We plot the running time taken by the n-th fastest result for each solver. The y axis uses a logarithmic scale.

**Figure 8.**Comparison of the four DPLL solvers in our benchmark (lower is better). There are 2000 tasks with 100 variables, 200 tasks with 150 and 175 variables each, and 199 tasks with 200 variables. We plot the running time taken by the n-th fastest result for each solver. The y axis uses a logarithmic scale. The

`Minlog`solver and TrueSAT are verified, while the

`C++`and the C# solvers are not. The running time of any solver on any given task is capped at 120s. The

`Minlog`solver hits the memory limit on most tasks starting with 150 variables, and is therefore not shown in tasks with 175 and 200 variables.

**Figure 9.**Comparison of CDCL solvers against our verified DPLL solver (lower is better). There are 2000 tasks with 100 variables, 200 tasks with 150 and 175 variables each, and 199 tasks with 200 variables. We plot the running time taken by the n-th fastest result for each solver. The running time of any solver on any given task is capped at 120 s. The y axis uses a logarithmic scale. The IsaSAT solver and TrueSAT are verified, while MiniSAT is not.

Solver | Algorithm | Proof Assistant | Downside |
---|---|---|---|

versat [27] | CDCL | Guru | not fully verified |

Marić [30] | DPLL | Isabelle/HOL | not imperative |

Berger et al. [26] | DPLL | Minlog | DPLL-only, not imperative |

IsaSAT [31] | CDCL | Isabelle/HOL | not imperative |

TrueSAT (this work) | DPLL | Dafny | DPLL-only |

Code | |

Line count (w/whitespace): | 3893 |

Line count (w/out whitespace): | 3303 |

Lines of executable code | 657 |

Lines of logic | 2646 |

Classes: | 4 classes, 1 trait |

Methods: | 37 |

Specification | |

Predicates: | 37 |

Functions: | 22 |

Preconditions: | 423 |

Postconditions: | 193 |

Proofs | |

Lemmas: | 42 |

Invariants: | 195 |

Variants: | 49 |

Assertions: | 197 |

Reads annotations: | 41 |

Modifies annotations: | 29 |

Ghost variables: | 24 |

Executable code/annotations ratio: | approx. 1/4 |

Verification time | |

Entire project: | approx. 5 minutes |

Slowest method to verify: | SATSolver.solve() |

(approx. 1 minute) |

Publisher’s Note: MDPI stays neutral with regard to jurisdictional claims in published maps and institutional affiliations. |

© 2022 by the authors. Licensee MDPI, Basel, Switzerland. This article is an open access article distributed under the terms and conditions of the Creative Commons Attribution (CC BY) license (https://creativecommons.org/licenses/by/4.0/).

## Share and Cite

**MDPI and ACS Style**

Andrici, C.-C.; Ciobâcă, Ș.
A Verified Implementation of the DPLL Algorithm in Dafny. *Mathematics* **2022**, *10*, 2264.
https://doi.org/10.3390/math10132264

**AMA Style**

Andrici C-C, Ciobâcă Ș.
A Verified Implementation of the DPLL Algorithm in Dafny. *Mathematics*. 2022; 10(13):2264.
https://doi.org/10.3390/math10132264

**Chicago/Turabian Style**

Andrici, Cezar-Constantin, and Ștefan Ciobâcă.
2022. "A Verified Implementation of the DPLL Algorithm in Dafny" *Mathematics* 10, no. 13: 2264.
https://doi.org/10.3390/math10132264