There are two main reasons for why the clause propagation phase (recall Algorithm 5.5, lines 23–30) is an important part of PDR. First, as pushing clauses reduces the difference between two neighboring layers, it speeds up the convergence of the algorithm towards a successful layer repetition check (on unsatisfiable problems). Second, it generally strengthens the layers, which then provide better guidance for the path construction.15
Clause propagation is, however, also quite costly, requiring one SAT-solver call per layer clause.
In this section, we describe a new method for speeding up propagation. We notice that during each failed attempt to push a clause the SAT solver computes a model, which is normally thrown away. We propose to keep the model instead and use it as a witness for why the respective clause could not be pushed forward. It only makes sense to recheck a particular clause for pushing when its witnessing model falsifies a newly added clause.
14
And the search direction being from the goal (more precisely, from the set of bad states, i.e. states violating the given property) towards an initial state.
15Clause propagation of iteration k is also an opportunity to insert clauses into the till then empty layer Lk+1 before the start of iteration k + 1. Sometimes, thanks to pushed clauses, iterations pass off without actually entering the path construction loop.
Because this test can be implemented via subsumption, we obtain an effective necessary condition which allows us to often skip the expensive SAT-solver call.
After explaining the idea of witnesses in full detail (Section 5.4.1), we describe a scheme in which subsumption is not only used to prune layers (as already proposed by Bradley, 2011), but also to trigger obligation rescheduling and, more importantly, clause pushing (Section 5.4.2). Because utilization of witnesses makes the pushing cheap, it allows us to essentially merge the clause propagation phase with path construction. Thus all clauses can be pushed as far as possible at all times, which makes the guidance more precise and allows for earlier discovery of layer repetitions.
Experimental evaluation of triggered clause pushing is part of Section 5.5.
5.4.1 Witnesses for failed push attempts
Consider a clause C ∈ Li\Li+1that could not be pushed forward. This means the query
on line 26 Algorithm 5.5 returned SAT. We may now inspect the model computed by the SAT solver and extract a state wC which satisfies Li−1and to which there is a transition
from a state satisfying ∼C. Notice that as long as wC remains to satisfy Li−1 during
the potential strengthenings of the layer, the query in question cannot become UNSAT. The state wC, therefore, represents a witness for why C cannot be pushed forward from
Li−1 to Li.
But how do we efficiently recognize whether wC still satisfies Li−1 after a new clause
D has been added to the layer? The answer is: via subsumption! It is only when D⊆ ∼Lits(wC)
that wC ceases to be a witness, because it does not satisfy the strengthened Li−1. When
this happens we may directly retry the pushing query of line 26 and either discover a new witness or finally push the clause C to Li.
It may seem expensive to perform the subsumption test against every witness whenever a new clause is derived. Note, however, that efficient implementations of PDR already use subsumption routinely to test each new clause against all other clauses (as detailed below) and that by also considering the witnesses, one per each clause, the overhead is at most doubled. Moreover, in our proposed scheme the subsumption check is only applied within the context of those layers where the new clause itself is known to be sufficiently strong. This typically helps to further reduce this cost.
5.4.2 Implementing triggered clause pushing via subsumption
It has been observed that PDR often derives a clause C to be inserted into layer Li while
Li already contains a weaker clause D ⊇ C. Bradley (2011) proposes to remove such
clauses during propagation; the implementation described by E´en et al. (2011) is more eager and clears layers via subsumption each time a new clause is derived. Removing subsumed clauses pays off, because they do not contribute to better guidance and only
∆0: clauses O0: obligations (∗, 0) R0: push requests W0: witnesses b b ∆1: clauses O1: obligations (∗, 1) R1: push requests W1: witnesses b b b b b b b b b b
Figure 5.3: Organizing the data structures of PDR with triggered clause pushing. A bi-directional link is maintained between a clause and its witness or its push request.
make the layers unnecessarily large.16
Once subsumption is implemented for reducing layers it can also be used for pruning obligations. By construction, an explaining clause C learned from an unsuccessful ex- tension of an obligation (s, i) satisfies C ⊆ ∼Lits(s). But the clause may also subsume other obligations (t, i) currently on Q. These can be directly rescheduled to index i + 1, each saving us one SAT-solver call.
Now we describe a new way to organize the data structures of PDR such that 1) subsumption by newly derived clauses can be used to prune layers and obligations, 2) clause pushing triggered by subsuming a witness is integrated into the path construction phase to keep clauses pushed as far as possible at all times. This will require three main updates of the algorithm’s data structures.
First, to avoid duplicating clauses we use the delta encoding of layers proposed by E´en et al. (2011). A delta layer ∆iconsist of clauses appearing last in Li. Thus ∆i = Li\Li+1
and Li =Sj≥i∆j. Next, for each layer clause C, we either associate it with its witness
wC or store a push request for it, which means it will need to be considered for pushing.
Finally, instead of using the priority queue Q, we explicitly separate obligations into sets Oi based on their index. The whole situation is depicted in Figure 5.3.
The algorithm now works as follows. It picks the smallest index i such that there is either an obligation in Oi or a request in Ri. If both sets are non empty, obligations are
picked first.17 Handling an obligation corresponds to asking the query from line 11 in
Algorithm 5.5 and either creates a new obligation or derives a new clause to be added to ∆i. Similarly, handling a push request corresponds to the query of line 26 and either
generates a new witness, which is stored to Wi, or pushes the clause from ∆i to ∆i+1.
In both cases a new clause may be added to a layer, which is where subsumption comes into play.
When a clause C is added into ∆i we put a push request for it into Ri and then 16Although a subsumed clause could potentially be pushed to a higher layer than the subsuming clause and become useful there, implementations prefer to keep the layers small and get rid of the subsumed clause immediately.
17Because by learning from an unsuccessful extension of an obligation from O
iwe may further strengthen Li. Then we consider the requests from Ri. If a clause is successfully pushed to Li+1it may subsume obligations waiting in Oi+1.
do the following: 1) we remove all the clauses from ∆i subsumed by C (along with
their witnesses or associated push requests), 2) we remove the subsumed witnesses from Wi and insert push requests for the respective clauses into Ri, 3) we reschedule the
subsumed obligations from Oi to Oi+1. If the clause C was pushed to ∆i from ∆i−1, we
are done. If, on the other hand, C was derived from an unsuccessful extension, it formally strengthened all L0, . . . , Li. We therefore continue towards lower indexes performing 1)
and 2)18for j = i− 1, i − 2, . . . A key observation is that the iteration can be stopped as
soon as the clause C is itself subsumed by some clause D from ∆j. Since layers of low
index are stronger than those further on, the iteration typically terminates way before reaching j = 0. This way a lot of time spent on futile subsumption tests can be saved.
Two notes on related work
E´en et al. (2011) use subsumption to tests whether an obligation has a chance of get- ting successfully extended, just before attempting the corresponding SAT call (look for method isBlocked ). We perform the same check, but already when the potential sub- suming clause gets derived. The advantage of our approach is that an obligation is only tested against new clauses and not those that were already in the layers when the obligation was created and for which the test must fail by construction.
To mitigate the quadratic cost (in the number of performed iterations) of clause prop- agation, Bayless et al. (2013) propose, as one of their improvements of PDR, to only start propagating clauses from the index of the least delta layer which received a new clause during the last path construction phase. As the authors admit, this modification may lead to a loss of PDR’s convergence guarantees. Our approach is similar in spirit, but does not have the corresponding drawback. We save time by only reattempting to push clauses which lost their witnesses, but never fail to discover all of them, because we only stop our search when the new clause is itself subsumed.