• No results found

2.9 Representing the Combined System and Property using BDDs

2.9.2 BDD Operations

We introduce the algorithms underlying the symbolic model-checking procedure, as de-fined by Bryant [159] and reviewed by Andersen [166]. The full model-checking algorithm presented in Chapter 2.10 builds upon these basic operations.

If ϕ is purely propositional (i.e. contains no temporal operators) then we can easily construct a (reduced) BDD directly from ¬ϕ using a straightforward algorithm like Build (shown in Figure 2.9), which simply loops through the variable ordering, recursively con-structing each variable’s low and high subtrees14 [166]. Otherwise, we translate ¬ϕ into

14Unfortunately, this simple algorithm requires exponential (2n) time.

algorithm BU I L D(¬ϕ : propositional logic f ormula) : BDD // Build a reduced BDD for ¬ϕ

i : integer // i indexes the variable order

n : integer // n is the number of Boolean variables in ΣBDD

//Recursive routine to implement BU I L D function BU I L D-S T E P(¬ϕ, i): vertex begin

if (i > n) then //if there are no variables left to add if (¬ϕ == f alse) then return 0

else return 1

//Recursive calls to build sub-trees

else v0 = BU I L D-S T E P(¬ϕ[σi= 0], i + 1) //compute the low subtree v1 = BU I L D-S T E P(¬ϕ[σi= 1], i + 1) //compute the high subtree

//new_vertex creates a new vertex only if v0 and v1 are different // and the σ-labeled node with them as children

// does not already exist

new_vertex(σi, v0, v1) //make/connect the new node return

end

return (BDD = BU I L D-S T E P(¬ϕ, 0))

Figure 2.9 : Pseudocode for the Build algorithm from [166].

a symbolic automaton and then encode the result A¬ϕ as a BDD, as described in Chapter 2.9.1.

Once we have built BDDs representing each of M and A¬ϕ, we need to compute their product, as described in Chapter 2.7. LetΣBDDbe the set of BDD variables encodingΣ. The application of all binary operators over BDDs, including conjunction, disjunction, union, intersection, complementation (via xor with 1), and testing for implication, is implemented in a streamlined fashion utilizing a singular universal function. Apply, shown in Figure 2.10, takes as input a binary operator and BDDs representing two functions f1 and f2, both over the alphabetΣBDD = (σ1, . . . , σn) and produces a reduced graph representing the

function f1 < op > f2 defined as:

[ f1 < op > f2](σ1, . . . , σn)= f11, . . . , σn) < op > f21, . . . , σn).

Apply begins by comparing the root vertices of the two BDDs and proceeds downward.

There are four cases. If vertices v1and v2are both terminal vertices, the result is simply the terminal vertex representing the result of value(v1) < op > value(v2). If the variable labels of v1 and v2 are the same, we create a vertex in the result BDD with that variable label and operate on the pair of low subtrees, low(v1) and low(v2), and the pair of high subtrees, high(v1) and high(v2), to create the low and high subtrees of our new vertex, respectively.

Otherwise, one of the variables, var(v1) or var(v2) is before the other in the total variable ordering. Note that this later vertex may or may not be a terminal vertex; either way, the algorithm is the same. If v1 is the earlier vertex, then the function represented by the subtree with root v2is independent of the variable var(v1). (If this were not true, we would encounter the variable in both graphs.) In this case, we create a vertex in the result BDD labeled var(v1) and recur on the pair of subtrees {low(v1), v2} to construct this new node’s low subtree and on the pair of subtrees {high(v1), v2} to construct this new node’s high subtree. The last case, where var(v2) occurs before var(v1) in the total variable ordering, is symmetric. We presume new vertex() is some function that creates a new vertex iff no isometric vertex exists, thus maintaining the reduced nature of the BDD under construction.

Bryant [159] and Wegener and Sieling [167] describe implementation optimizations that yield a time complexity for this algorithm of O(|G1||G2|) where |G1| and |G2| are the sizes of the two graphs being operated upon.

Recall that finding a counterexample trace equates to finding a word in the language L (AM, ¬ϕ), which is essentially a satisfying assignment to the variables inΣ through time.

The model checker uses a BDD-based fixpoint algorithm to find a fair path in the

model-algorithm AP P L Y(op : operator; v1, v2 : vertex): vertex // Evaluate v1 op v2

u : vertex // u is the root vertex of the BDD representing v1 op v2

init(T ) // T is a hash table;

// T(i, j) is either ∅ or

// the earlier computed result of Apply-step(i, j) //Recursive routine to implement AP P L Y

function AP P L Y-S T E P(v1, v2 : vertex): vertex u = v1 op v2 //evaluate the simple Boolean expression

// (This is the base case.)

else if (var(v1) == var(v2)) then //if v1 and v2 are rooted // at the same variable

//progress down both trees to the next variable in the ordering u = new vertex(var(v1), AP P L Y-S T E P(low(v1), low(v2)),

Figure 2.10 : The Apply and Apply-step algorithms from [159].

algorithm SA T I S F Y-O N E(v: vertex, x: var_array) : Boolean //Find any satisfying assignment of the BDD rooted at v // Return the counterexample in x

if (v.value == 0) then return f alse //unsatisfiable if (v.value == 1) then return true //satisfiable i = v.level //i is the level of vertex v,

// also the place in the variable order x[i] = 0 //guess 0 for this vertex

if SA T I S F Y-O N E(v.low, x) then return true //satisfiable x[i] = 1 //backtrack if x[i] = 0 results in f alse

// and guess 1 instead

return SA T I S F Y-O N E(v.high, x) //we know x[i] = 1 will be true

Figure 2.11 : Pseudocode for the Satisfy-one algorithm from [159].

automaton product [66]. The basic algorithm underlying this process is Bryant’s Satisfy-one, shown in Figure 2.11 [159]. This algorithm is a simple recognition of the BDD princi-ple that every non-terminal vertex has the terminal vertex labeled 1 as a descendant. Thus, a classic depth-first search with backtracking upon visiting the 0 terminal is guaranteed to find a satisfying path from the root to the terminal 1 in linear time.15 Starting from the root, Satisfy-one arbitrarily guesses the low branch of each non-terminal vertex first, and stores a satisfying assignment (aka counterexample trace) of length n = |ΣBDD| in an array as it traverses the graph. It returns false if the function is unsatisfiable, and true otherwise.

Note that the reason we check for the existence of a single satisfying set and not for the whole set SM, ¬ϕ of satisfying sets is because enumerating the entire set requires time pro-portional to the length of the counterexamples times the number of elements in SM, ¬ϕ. (For a propositional formula with n = |ΣBDD|, the time complexity is O(n · |SM, ¬ϕ|).) Consider-ing the counterexample traces may be quite lengthy (i.e. n may be large), and there may be many of them, this is a highly inefficient check, unlikely to be performed in any reasonable

15One of the reasons maintaining a reduced BDD is important is that this algorithm can require exponential time if not [159].

timeframe. Furthermore, the utility of performing such a check is questionable: a single bug may generate any number of counterexamples. For verification purposes, where coun-terexamples may trace through many time steps, it is much more efficient to use the model checker to find a bug, then fix that bug before searching for additional counterexamples.

By changing the domain of the model-checking problem to reasoning over BDD oper-ations from explicit manipuloper-ations of automata, we increase the size of the system we can reason about. Combining concepts from Boolean algebra and graph theory allows us to achieve such a high level of algorithmic efficiency that the performance of symbolic model checking using these techniques is limited mostly by the sizes of the BDDs involved. Table 2.2 compares the two methods for each step in the model-checking problem.