Saturation: An Efficient Iteration Strategy for
Symbolic State–Space Generation?
Gianfranco Ciardo1 , Gerald Lüttgen2 , and Radu Siminiceanu1
1
2
Department of Computer Science, College of William and Mary
Williamsburg, VA 23187, USA {ciardo, radu}@cs.wm.edu
Department of Computer Science, Sheffield University, 211 Portobello Street
Sheffield S1 4DP, U.K. [email protected]
Abstract. We present a novel algorithm for generating state spaces of
asynchronous systems using Multi–valued Decision Diagrams. In contrast
to related work, we encode the next–state function of a system not as a
single Boolean function, but as cross–products of integer functions. This
permits the application of various iteration strategies to build a system’s
state space. In particular, we introduce a new elegant strategy, called
saturation, and implement it in the tool SMART. On top of usually
performing several orders of magnitude faster than existing BDD–based
state–space generators, our algorithm’s required peak memory is often
close to the final memory needed for storing the overall state space.
1
Introduction
State–space generation is one of the most fundamental challenges for many formal verification tools, such as model checkers [13]. The high complexity of today’s
digital systems requires constructing and storing huge state spaces in the relatively small memory of a workstation. One research direction widely pursued
in the literature suggests the use of decision diagrams, usually Binary Decision
Diagrams [7] (BDDs), as a data structure for implicitly representing large sets
of states in a compact fashion. This proved to be very successful for the verification of synchronous digital circuits, as it increased the manageable sizes of
state spaces from about 106 states, with traditional explicit state–space generation techniques [14], to about 1020 states [9]. Unfortunately, symbolic techniques
are known not to work well for asynchronous systems, such as communication
protocols, which particularly suffer from state–space explosion.
The latter problem was addressed in previous work by the authors in the
context of state–space generation using Multi–valued Decision Diagrams [18]
(MDDs), which exploited the fact that, in event–based asynchronous systems,
?
This work was partially supported by the National Aeronautics and Space Administration under NASA Contract No. NAS1–97046 while the authors were in residence
at the Institute for Computer Applications in Science and Engineering (ICASE),
NASA Langley Research Center, Hampton, VA 23681, USA. G. Ciardo and R. Siminiceanu were also partially supported by NASA grant No. NAG-1-2168.
each event updates just a few components of a system’s state vector [10]. Hence,
firing an event only requires the application of local next–state functions and the
local manipulation of MDDs. This is in contrast to classic BDD–based techniques
which construct state spaces by iteratively applying a single, global next–state
function which is itself encoded as a BDD [20]. Additionally, in most concurrency frameworks including Petri nets [23] and process algebras [5], next–state
functions satisfy a product form allowing each component of the state vector to
be updated somewhat independently of the others. Experimental results implementing these ideas of locality showed significant improvements in speed and
memory consumption when compared to other state–space generators [22].
In this paper, we take our previous approach a significant step further by observing that the reachable state space of a system can be built by firing the system’s events in any order, as long as every event is considered often enough [16].
We exploit this freedom by proposing a novel strategy which exhaustively fires
all events affecting a given MDD node, thereby bringing it to its final saturated
shape. Moreover, nodes are considered in a depth–first fashion, i.e., when a node
is processed, all its descendants are already saturated. The resulting state–space
generation algorithm is not only concise, but also allows for an elegant proof
of correctness. Compared to our previous work [10], saturation eliminates a fair
amount of administration overhead, reduces the average number of firing events,
and enables a simpler and more efficient cache management.
We implemented the new algorithm in the tool SMART [11], and experimental studies indicate that it performs on average about one order of magnitude
faster than our old algorithm. Even more important and in contrast to related
work, the peak memory requirements of our algorithm are often close to its final
memory requirements. In the case of the dining philosophers’ problem, we are
able to construct the state space of about 10627 states, for 1000 philosophers, in
under one second on a 800 MHz Pentium III PC using only 390KB of memory.
2
MDDs for Encoding Structured State Spaces
State spaces and next–state functions. A discrete–state model expressed in
b the set of potential states describing
a high–level formalism must specify: (i) S,
b
b
the “type” of states; (ii) s ∈ S, the initial state; and (iii) N : Sb −→ 2S , the
next–state function, describing which states can be reached from a given state
in a single step. In many cases, such as S
Petri nets and process algebras, a model
expresses this function as a union N = e∈E Ne , where E is a finite set of events
and Ne is the next–state function associated with event e. We say that Ne (s) is
the set of states the system can enter when event e occurs, or fires, in state s.
Moreover, event e is called disabled in s if Ne (s) = ∅; otherwise, it is enabled.
The reachable state space S ⊆ Sb of the model under consideration is the
smallest set containing the initial system state s and closed with respect to N ,
i.e., S = {s} ∪ N (s) ∪ N (N (s)) ∪ · · · = N ∗ (s), where “∗ ” denotes the reflexive
and transitive closure. When N is composed of several functions Ne , for e ∈ E,
we can iterate these functions in any order, as long as we consider each Ne often
enough. In other words, i ∈ S if and only if it can be reached from s through
zero or more event firings. In this paper we assume that S is finite; however,
for most practical asynchronous systems, the size of S is enormous due to the
state–space explosion problem.
Multi–valued decision diagrams. One way to cope with this problem is to
use efficient data structures to encode S that exploit the system’s structure. We
consider a common case in asynchronous system design, where a system model
is composed of K submodels, for some K ∈ N, so that a global system state
is a K–tuple (iK , . . . , i1 ), where ik is the local state for submodel k. (We use
superscripts for submodel indices —not for exponentiation— and subscripts for
event indices.) Thus, Sb = S K × · · · × S 1 , with each local state space S k having
some finite size nk . In Petri nets, for example, the set of places can be partitioned
into K subsets, and the marking can be written as the composition of the K
corresponding submarkings. When identifying S k with the initial integer interval
{0, . . . , nk −1}, for each K ≥ k ≥ 1, one can encode S ⊆ Sb via a (quasi–reduced
ordered ) MDD, i.e., a directed acyclic edge-labelled multi-graph where:
– Nodes are organized into K + 1 levels. We write hk.pi to denote a generic
node, where k is the level and p is a unique index for that level. Level K
contains only a single non–terminal node hK.ri, the root, whereas levels K−1
through 1 contain one or more non–terminal nodes. Level 0 consists of two
terminal nodes, h0.0i and h0.1i. (We use boldface for the node indices 0 or 1
since these have a special meaning, as we will explain later.)
– A non–terminal node hk.pi has nk arcs pointing to nodes at level k − 1. If
the ith arc, for i ∈ S k , is to node hk−1.qi, we write hk.pi[i] = q. Unlike
in the original BDD setting [7, 8], we allow for redundant nodes, having all
arcs pointing to the same node. This will be convenient for our purposes, as
eliminating such nodes would lead to arcs spanning multiple levels.
– A non–terminal node cannot duplicate (i.e., have the same pattern of arcs
as) another node at the same level.
Given a node hk.pi, we can recursively define the node reached from it through
any integer sequence γ =df (ik , ik−1 , · · · , il ) ∈ S k × S k−1 × · · · × S l of length
k − l + 1, for K ≥ k ≥ l ≥ 1, as
hk.pi
if γ = (), the empty sequence
node(hk.pi, γ) =
node(hk−1.qi, δ) if γ = (ik , δ) and hk.pi[ik ] = q.
The substates encoded by p or reaching p are then, respectively,
B(hk.pi) = {β ∈ S k × · · · × S 1 : node(hk.pi, β) = h0.1i}
A(hk.pi) = {α ∈ S K × · · · × S k+1 : node(hK.ri, α) = hk.pi}
“below ” hk.pi ;
“above” hk.pi .
Thus, B(hk.pi) contains the substates that, prefixed by a substate in A(hk.pi),
form a (global) state encoded by the MDD. We reserve the indices 0 and 1 at
each level k to encode the sets ∅ and S k × · · · × S 1 , respectively. In particular,
B(h0.0i) = ∅ and B(h0.1i) = {()}. Fig. 1 shows a four–level example MDD and
the set S encoded by it; only the highlighted nodes are actually stored.
!#"$%'&(*)
+-,/.0123245
0 1 2 3
0 0 1 2
0 1 2
0 0 1
0 1
0 0 1 2
0 1 2
0 1
0 1 1
0 1 2
0
0 1 2 1
6879:<;;;'=:;':<;=:*:;;'=
:*::;'=:>':<;=>*;;;'=
>*;':;'=>':;*;=>::;'=
>*>':;'=?;':<;=?::;'=
?*>;;'=?>;:=?*>;>'=
?*>':;'=?>':*:=?*>':>@
1
Fig. 1. An example MDD and the state space S encoded by it.
Many algorithms for generating state spaces using BDDs exist [20], which can
easily be adapted to MDDs. In contrast to those, however, our approach does not
encode the next–state function as an MDD over 2K variables, describing the K
state components before and after a system step. Instead, we update MDD nodes
directly, adding the new states reached through one step of the global next–state
function when firing some event. For asynchronous systems, this function is often
expressible as the cross–product of local next–state functions.
Product–form behavior. An asynchronous system model exhibits such behavior if, for each event e, its next–state function Ne can be written as a cross–
k
product of K local functions, i.e., Ne = NeK × · · · × Ne1 where Nek : S k −→ 2S ,
for K ≥ k ≥ 1. (Recall that event e is disabled in some global state exactly if it
is disabled in at least one component.) The product–form requirement is quite
natural. First, many modeling formalisms satisfy it, e.g., any Petri net model
conforms to this behavior for any partition of its places. Second, if a given model
does not respect the product–form behavior, we can always coarsen K or refine E
so that it does. As an example, consider a model partitioned into four submodels,
3
2
where Ne = Ne4 ×Ne3,2 ×Ne1 , but N 3,2 : S 3 ×S 2 −→ 2S ×S cannot be expressed
3
2
as a product Ne × Ne . We can achieve the product–form requirement by simply
partitioning the model into three, not four, submodels. Alternatively, we may
substitute event e with “subevents” satisfying the product form. This is possible
since, in the worst case, we can define a subevent ei,j , for each i = (i3 , i2 ) and
j = (j 3 , j 2 ) ∈ Ne3,2 (i), with Nei,j (i3 ) = {j 3 } and Nei,j (i2 ) = {j 2 }.
Finally, we introduce some notational conventions. We say that event e depends on level k, if the local state at level k does affect the enabling of e or if it
is changed by the firing of e. Let First(e) and Last(e) be the first and last levels
on which event e depends. Events e such that First(e) = Last(e) = k are said
to be local events; we merge these into a single macro–event λk without violating the product–form
requirement, since we can write Nλk = NλKk × · · · × Nλ1k
S
where Nλkk = {e:First(e)=Last(e)=k} Nek , while Nλl k (il ) = {il } for l 6= k and
il ∈ S l . The set {e ∈ E : First(e) = k} of events “starting” at level k is denoted
by E k . We also extend Ne to substates instead of full states: Ne ((ikS, . . . , il )) =
Nek (ik ) × · · · × Nel (il ), for K ≥ k ≥ l ≥ 1; to sets of states: Ne (X ) = i∈X Ne (i),
S
for X ⊆ S k × · · · × S l ; and to sets of events: NF (X ) = e∈F Ne (X ), for F ⊆ E.
In particular, we write N≤k as a shorthand for N{e:First(e)≤k} .
3
A Novel Algorithm Employing Node Saturation
In the following we refer to a specific order of iterating the local next–state functions of an synchronous system model as iteration strategy. Clearly, the choice
of strategy influences the efficiency of state–space generation. In our previous
work [10] we employed a naive strategy that cycled through MDDs level–by–
level and fired, at each level k, all events e with First(e) = k.
As main contribution of this paper, we present a novel iteration strategy,
called saturation, which not only simplifies our previous algorithm, but also
significantly improves its time and space efficiency. The key idea is to fire events
node–wise and exhaustively, instead of level–wise and just once per iteration.
Formally, we say that an MDD node hk.pi is saturated if it encodes a set of
states that is a fixed point with respect to the firing of any event at its level
∗
or at a lower level, i.e., if B(hk.pi) = N≤k
(B(hk.pi)) holds; it can easily be
shown by contradiction that any node below node hk.pi must be saturated, too.
It should be noted that the routine for firing some event, in order to reveal
and add globally reachable states to the MDD representation of the state space
under construction, is similar to [10]. In particular, MDDs are manipulated only
locally with respect to the levels on which the fired event depends, and, due to the
product–form behavior, these manipulations can be carried out very efficiently.
We do not further comment on these issues here, but concentrate solely on the
new idea of node saturation and its implications.
Just as in traditional symbolic state–space generation algorithms, we use
a unique table, to detect duplicate nodes, and operation caches, in particular
a union cache and a firing cache, to speed–up computation. However, our approach is distinguished by the fact that only saturated nodes are checked in the
unique table or referenced in the caches. Given the MDD encoding of the initial state s, we saturate its nodes bottom–up. This improves both memory and
execution–time efficiency for generating state spaces because of the following
reasons. First, our saturation order ensures that the firing of an event affecting
only the current and possibly lower levels adds as many new states as possible.
Then, since each node in the final encoding of S is saturated, any node we insert
in the unique table has at least a chance of being part of the final MDD, while
any unsaturated node inserted by a traditional symbolic approach is guaranteed
to be eventually deleted and replaced with another node encoding a larger subset
of states. Finally, once we saturate a node at level k, we never need to fire any
event e ∈ E k in it again, while, in classic symbolic approaches, N is applied to
the entire MDD at every iteration.
In the pseudo–code of our new algorithm implementing node saturation,
which is shown in Fig. 2, we use the data types evnt (model event), lcl (local
state), lvl (level), and idx (node index within a level); in practice these are simply
integers in appropriate ranges. We also assume the following dynamically–sized
global hash tables: (a) UT [k], for K ≥ k ≥ 1, the unique table for nodes at level k,
to retrieve p given the key hk.pi[0], . . . , hk.pi[nk −1]; (b) UC [k], for K > k ≥ 1,
the union cache for nodes at level k, to retrieve s given nodes p and q, where
B(hk.si) = B(hk.pi) ∪ B(hk.qi); and (c) FC [k], for K > k ≥ 1, the firing cache
Generate(in s:array[1..K] of lcl ):idx
Build an MDD rooted at hK.ri encoding
NE∗ (s) and return r, in U T [K].
declare r,p:idx ;
declare k:lvl ;
1. p ⇐ 1;
2. for k = 1 to K do
3. r ⇐ NewNode(k); hk.ri[s[k]] ⇐ p;
4. Saturate(k, r); Check (k, r);
5. p ⇐ r; return r;
Saturate(in k:lvl , p:idx )
Update hk.pi, not in U T [k], in–place, to
∗
encode N≤k
(B(hk.pi)).
declare e:evnt;
declare L:set of lcl ;
declare f ,u:idx ;
declare i,j:lcl ;
declare pCng:bool ;
1. repeat
2. pCng ⇐ false;
3. foreach e ∈ E k do
4.
L ⇐ Locals(e, k, p);
5.
while L 6= ∅ do
6.
i ⇐ Pick (L);
7.
f ⇐ RecFire(e, k−1, hk.pi[i]);
8.
if f 6= 0 then
9.
foreach j ∈ Nek (i) do
10.
u ⇐ Union(k−1, f, hk.pi[j]);
11.
if u 6= hk.pi[j] then
12.
hk.pi[j] ⇐ u; pCng ⇐ true;
13.
if Nek (j) 6= ∅ then
14.
L ⇐ L∪{j};
15. until pCng = false;
Union(in k:lvl , p:idx , q:idx ):idx
Build an MDD rooted at hk.si, in U T [k],
encoding B(hk.pi) ∪ B(hk.qi). Return s.
declare i:lcl ;
declare s,u:idx ;
1. if p = 1 or q = 1 then return 1;
2. if p = 0 or p = q then return q;
3. if q = 0 then return p;
4. if Find (U C[k], {p, q}, s) then return s;
5. s ⇐ NewNode(k);
6. for i = 0 to nk −1 do
7. u ⇐ Union(k−1, hk.pi[i], hk.qi[i]);
8. hk.si[i] ⇐ u;
9. Check (k, s); Insert(U C[k], {p, q}, s);
10. return s;
RecFire(in e:evnt, l:lvl , q:idx ):idx
Build an MDD rooted at hl.si, in U T [l],
∗
encoding N≤l
(Ne (B(hl.qi))). Return s.
declare L:set of lcl ;
declare f ,u,s:idx ;
declare i,j:lcl ;
declare sCng:bool ;
1. if l < Last(e) then return q;
2. if Find (F C[l], {q, e}, s) then return s;
3. s ⇐ NewNode(l); sCng ⇐ false;
4. L ⇐ Locals(e, l, q);
5. while L 6= ∅ do
6. i ⇐ Pick (L);
7. f ⇐ RecFire(e, l−1, hl.qi[i]);
8. if f 6= 0 then
9.
foreach j ∈ Nel (i) do
10.
u ⇐ Union(l−1, f, hl.si[j]);
11.
if u 6= hl.si[j] then
12.
hl.si[j] ⇐ u; sCng ⇐ true;
13. if sCng then Saturate(l, s);
14. Check (l, s); Insert(F C[l], {q, e}, s);
15. return s;
Find (in tab, key, out v):bool
If (key, x) is in hash table tab, set v to
x and return true. Else, return false.
Insert(inout tab, in key, v )
Insert (key, v ) in hash table tab, if it does
not contain an entry (key, ·).
Locals(in e:evnt, k:lvl , p:idx ):set of lcl
Return {i ∈ S k :hk.pi[i] 6= 0, Nek (i) 6= ∅},
the local states in p locally enabling e.
Return ∅ or {i ∈ S k : Nek (i) 6= ∅}, respectively, if p is 0 or 1.
Pick (inout L:set of lcl ):lcl
Remove and return an element from L.
NewNode(in k:lvl ):idx
Create hk.pi with arcs set to 0, return p.
Check (in k:lvl , inout p:idx )
If hk.pi, not in U T [k], duplicates hk.qi,
in U T [k], delete hk.pi and set p to q.
Else, insert hk.pi in U T [k]. If hk.pi[0] =
· · · = hk.pi[nk −1] = 0 or 1, delete hk.pi
and set p to 0 or 1, since B(hk.pi) is ∅
or S k × · · · × S 1 , respectively.
Fig. 2. Pseudo–code for the node–saturation algorithm.
for nodes at level k, to retrieve s given node p and event e, where First(e) > k
∗
and B(hk.si) = N≤k
(Ne (B(hk.pi))). Furthermore, we use K dynamically–sized
arrays to store nodes, so that hk.pi can be efficiently retrieved as the pth entry of
the k th array. The call Generate(s) creates the MDD encoding the initial state,
saturating each MDD node as soon as it creates it, in a bottom–up fashion.
Hence, when it calls Saturate(k, r), all children of hk.ri are already saturated.
Theorem 1 (Correctness). Consider a node hk.pi with K ≥ k ≥ 1 and saturated children. Moreover, (a) let hl.qi be one of its children, satisfying q 6= 0 and
l = k − 1; (b) let U stand for B(hl.qi) before the call RecFire(e, l, q), for some
event e with l < First(e), and let V represent B(hl.f i), where f is the value returned by this call; and (c) let X and Y denote B(hk.pi) before and after calling
∗
∗
(Ne (U)) and (ii) Y = N≤k
(X ).
Saturate(k, p), respectively. Then, (i) V = N≤l
By choosing, for node hk.pi, the root hK.ri of the MDD representing the initial
∗
∗
system state s, we obtain Y = N≤K
(B(hK.ri)) = N≤K
({s}) = S, as desired.
Proof. To prove both statements we employ a simultaneous induction on k. For
the induction base, k = 1, we have: (i) The only possible call RecFire(e, 0, 1) immediately returns 1 because of the test on l (cf. line 1). Then, U = V = {()} and
∗
{()} = N≤0
(Ne ({()})). (ii) The call Saturate(1, p) repeatedly explores λ1 , the
only event in E 1 , in every local state i for which Nλ11 (i) 6= ∅ and for which h1.pi[i]
is either 1 at the beginning of the “while L 6= ∅” loop, or has been modified (cf. line 12) from 0 to 1, which is the value of f , hence u, since the call
RecFire(e, 0, 1) returns 1. The iteration stops when further attempts to fire λ 1
∗
do not add any new state to B(h1.pi). At this point, Y = Nλ∗1 (X ) = N≤1
(X ).
For the induction step we assume that the calls to Saturate(k −1, ·) as well
as to RecFire(e, l−1, ·) work correctly. Recall that l = k − 1.
(i) Unlike Saturate (cf. line 14), RecFire does not add further local states to L,
since it modifies “in–place” the new node hl.si, and not node hl.qi describing
the states from where the firing is explored. The call RecFire(e, l, q) can be
resolved in three ways. If l < Last(e), then the returned value is f = q and
∗
(B(hl.qi)) =
Nel (U) = U for any set U; since q is saturated, B(hl.qi) = N≤l
∗
N≤l (Ne (B(hl.qi))). If l ≥ Last(e) but RecFire has been called previously
with the same parameters, then the call Find (FC [l], {q, e}, s) is successful. Since node q is saturated and in the unique table, it has not been
modified further; note that in–place updates are performed only on nodes
not yet in the unique table. Thus, the value s in the cache is still valid
and can be safely used. Finally, we need to consider the case where the
call RecFire(e, l, q) performs “real work.” First, a new node hl.si is created, having all its arcs initialized to 0. We explore the firing of e in each
state i satisfying hl.qi[i] 6= 0 and Nle (i) 6= ∅. By induction hypothesis, the
∗
recursive call RecFire(e, l − 1, hl.qi[i]) returns N≤l−1
(Ne (B(hl−1.hl.qi[i]i))).
S
Hence, when the “while L 6= ∅” loop terminates, B(hl.si) = i∈S l Nel (i) ×
∗
∗
N≤l−1
(Ne (B(hl−1.hl.qi[i]i))) = N≤l−1
(Ne (B(hl.qi))) holds. Thus, all children
of node hl.si are saturated. According to the induction hypothesis, the call
Saturate(l, s) correctly saturates hl.si. Consequently, we have B(hl.si) =
∗
∗
∗
N≤l
(N≤l−1
(Ne (B(hl.qi))) = N≤l
(Ne (B(hl.qi))) after the call.
(ii) As in the base case, Saturate(k, p) repeatedly explores the firing of each
event e that is locally enabled in i ∈ S k ; it calls RecFire(e, k−1, hk.pi[i]) that,
∗
as shown above and since l = k−1, returns N≤k−1
(Ne (B(hk−1.hk.pi[i]i))). Further, Saturate(k, p) terminates when firing the events in E k = {e1 , e2 , . . ., em }
does not add any new state to B(hk.pi). At this point, the set Y encoded
by hk.pi is the fixed–point of the iteration
∗
∗
∗
Y (m+1) ⇐ Y (m) ∪ N≤k−1
(Ne1 (N≤k−1
(Ne2 (· · · N≤k−1
(Nem (Y (m) )) · · · )))),
(0)
∗
initialized with Y ⇐ X . Hence, Y = N≤k (X ), as desired.
2
level
3
2
1
event: l1
∗
∗
0 → 1, 1 → 2, 2 → 0
event: l2
∗
0 → 1, 2 → 1
∗
event: l3
1→0
∗
∗
event: e21
∗
0→1
1→0
event:
0→
0→
0→
e321
1
2
1
3
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2
2
0 1 2 2
0 1 2 2
0 1 2 2
0 1 2 2
0 1 22
2
0 1 2
1
0 1 22
0 1 2 1
0 1 2 1
0 1 2 1 0 1 23
0 1 2 1 0 1 2 3
0 1 2 1
0
1
3 0 1
(a)
2
2 0 1 2
2
1 0 1 2
1
0
1
3
2
1
0
1
0 1 24
(g)
0 1 2
0 1 2
2
3
0 1 2
0 1 2 1
1
(l)
1
(b)
1
(c)
1
1
(d)
(f)
(e)
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2 2
2
0 1 2
0 1 2 3
3
01 2 2 0 1 2
0 1 2 2 0 1 2 3
0 1 2 1 0 1 24
1
0 1 2
01 2 1
0 1 2 1
1
(h)
1
(i)
1
(j)
1
(k)
0 1 2
0 1 2
0 1 2
0 1 2 1 0 1 2 2 0 1 2 3
0 1 2 1 0 1 2 2 0 1 2 3
0 1 2 1 0 1 2 3
0 1 2 1
0 1 2 1
0 1 2 1
1
(m)
1
(n)
1
(o)
Fig. 3. Example of the execution of the Saturate and RecFire routines.
Fig. 3 illustrates our saturation–based state–space generation algorithm on a
small example, where K = 3, |S3 | = 2, |S2 | = 3, and |S1 | = 3. The initial
state is (0, 0, 0), and there are three local events l1 , l2 , and l3 , plus two further
events, e21 (depending on levels 2 and 1) and e321 (depending on all levels).
Their effects, i.e., their next–state functions, are summarized in the table at the
top of Fig. 3; the symbol “∗” indicates that a level does not affect an event. The
MDD encoding {(0, 0, 0)} is displayed in Snapshot (a). Nodes h3.2i and h2.2i are
actually created in Steps (b) and (g), respectively, but we show them from the
beginning for clarity. The level lvl of a node hlvl .idx i is given at the very left
of the MDD figures, whereas the index idx is shown to the right of each node.
We use dashed lines for newly created objects, double boxes for saturated nodes,
and shaded local states for substates enabling the event to be fired. We do not
show nodes with index 0, nor any arcs to them.
– Snapshots (a–b): The call Saturate(1, 2) updates node h1.2i to represent the
effect of firing l1∗ ; the result is equal to the reserved node h1.1i.
– Snapshots (b–f ): The call Saturate(2, 2) fires event l2 , adding arc h2.2i[1]
to h1.1i (cf. Snapshot (c)). It also fires event e21 which finds the “enabling
pattern” (∗, 0, 1), with arbitrary first component, and starts building the
result of the firing, through the sequence of calls RecFire(e21 , 1, h2.2i[0]) and
RecFire(e21 , 0, h1.1i[1]). Once node h1.3i is created and its arc h1.3i[0] is
set to 1 (cf. Snapshot (d)), it is saturated by repeatedly firing event l1 .
Node h1.3i then becomes identical to node h1.1i (cf. Snapshot (e)). Hence,
it is not added to the unique table but deleted. Returning from RecFire on
level 1 with result h1.1i, arc h2.2i[1] is updated to point to the outcome of
the firing (cf. Snapshot (f)). This does not add any new state to the MDD,
since {1} × {0} was already encoded in B(h2.2i).
– Snapshots (f–o): Once h2.2i is saturated, we call Saturate(3, 2). Local event l 3
is not enabled, but event e321 is, by the pattern (0, 0, 0). The calls to RecFire
build a chain of nodes encoding the result of the firing (cf. Snapshots (g–i)).
Each of them is in turn saturated (cf. Snapshots (h–j)), causing first the
newly created node h1.4i to be deleted, since it becomes equal to node h1.1i,
and second the saturated node h2.3i to be added to the MDD. The firing
of e321 (cf. Snapshot (k)) not only adds state (1, 2, 1), but the entire subspace {1}×{1, 2}×S 1 , now known to be exhaustively explored, as node h2.3i
is marked saturated. Event l3 , which was found disabled in node h3.2i at the
first attempt, is now enabled, and its firing calls Union(2, h3.2i[1], h3.2i[0]).
The result is a new node which is found by Check to be the reserved
node h2.1i (cf. Snapshot (m)). This node encoding S2 ×S1 is added as the descendant of node h3.2i in position 0, and the former descendant h2.2i in that
position is removed (cf. Snapshot (n)), causing it to become disconnected
and deleted. Further attempts to fire events l3 or e321 add no more states to
the MDD, whence node h3.2i is declared saturated (cf. Snapshot (o)). Thus,
our algorithm terminates and returns the MDD encoding of the overall state
space ({0} × S 2 × S 1 ) ∪ ({1} × {1, 2} × S 1 ).
To summarize, since MDD nodes are saturated as soon as they are created,
each node will either be present in the final diagram or will eventually become
disconnected, but it will never be modified further. This reduces the amount
of work needed to explore subspaces. Once all events in E k are exhaustively
fired in some node hk.pi, any additional state discovered that uses hk.pi for its
encoding benefits in advance from the “knowledge” encapsulated in hk.pi and
its descendants.
4
Garbage Collection and Optimizations
Garbage collection. MDD nodes can become disconnected, i.e., unreachable
from the root, and should be “recycled.” Disconnection is detected by associating
an incoming–arc counter to each node hk.pi. Recycling disconnected nodes is a
major issue in traditional symbolic state–space generation algorithms, where
usually many nodes become disconnected. In our algorithm, this phenomenon is
much less frequent, and the best runtime is achieved by removing these nodes
only at the end; we refer to this policy as Lazy policy.
We also implemented a Strict policy where, if a node hk.pi becomes disconnected, its “delete–flag” is set and its arcs hk.pi[i] are re–directed to hk−1.0i,
with possible recursive effects on the nodes downstream. When a hit in the union
cache UC [k] or the firing cache FC [k] returns s, we consider this entry stale if the
delete–flag of node hk.si is set. By keeping a per–level count of the nodes with
delete–flag set, we can decide in routine NewNode(k) whether to (a) allocate new
memory for a node at level k or (b) recycle the indices and the physical memory
of all nodes at level k with delete–flag set, after having removed all the entries
in UC [k] and FC [k] referring to them. The threshold that triggers recycling
can be set in terms of number of nodes or bytes of memory. The policy using
a threshold of one node, denoted as Strict(1), is optimal in terms of memory
consumption, but has a higher overhead due to more frequent clean–ups.
Optimizations. First, observe that the two outermost loops in Saturate ensure
that firing some event e ∈ E k does not add any new state. If we always consider
these events in the same order, we can stop iterating as soon as |E k | consecutive
events have been explored without revealing any new state. This saves |E k |/2 firing attempts on average, which translates to speed–ups of up to 25% in our experimental studies. Also, in Union, the call Insert(U C[k], {p, q}, s) records that
B(hk.si) = B(hk.pi) ∪ B(hk.qi). Since this implies B(hk.si) = B(hk.pi) ∪ B(hk.si)
and B(hk.si) = B(hk.si) ∪ B(hk.qi), we can, optionally, also issue the calls
Insert(U C[k], {p, s}, s), if s 6= p, and Insert(U C[k], {q, s}, s), if s 6= q. This
speculative union heuristic improves performance by up to 20%.
5
Experimental Results
In this section we compare the performance of our new algorithm, using both the
Strict and Lazy policies, with previous MDD–based ones, namely the traditional Recursive MDD approach in [22] and the level–by–level Forwarding–
arcs approach in [10]. All three approaches are implemented in SMART [11], a
tool for the logical and stochastic–timing analysis of discrete–state systems. For
asynchronous systems, these approaches greatly outperform the more traditional
BDD–based approaches [20], where next–state functions are encoded using decision diagrams. To evaluate our saturation algorithm, we have chosen a suite of
examples with a wide range of characteristics. In all cases, the state space sizes
depend on a parameter N ∈ N.
– The classic N queens problem requires to find a way to position N queens on
a N ×N chess board such that they do not attack each other. Since there will
be exactly one queen per row in the final solution, we use a safe (i.e., at most
one token per place) Petri net model with N × N transitions and N rows,
one per MDD level, of N + 1 places. For 1 ≤ i, j ≤ N , place pij is initially
empty, and place pi0 contains the token (queen) still to be placed on row i of
the chess board. Transition tij moves the queen from place pi0 to place pij ,
in competition with all other transitions til , for l 6= j. To encode the mutual
exclusion of queens on the same column or diagonal, we employ inhibitor
arcs. A correct placement of the N queens corresponds to a marking where
all places pi0 are empty. Note that our state space contains all reachable
markings, including those where queens n to N still need to be placed, for
any n. In this model, locality is poor, since tij depends on levels 1 through i.
– The dining philosophers and slotted ring models [10, 25] are obtained by
connecting N identical safe subnets in a circular fashion. The MDD has
N/2 MDD levels (two subnets per level) for the former model and N levels
(one subnet per level) for the latter. Events are either local or synchronize
adjacent subnets, thus they span only two levels, except for those synchronizing subnet N with subnet 1, which span the entire MDD.
– The round–robin mutex protocol model [17] also has N identical safe subnets
placed in a circular fashion, which represent N processes, each mapped to
one MDD level. Another subnet models a resource shared by the N processes,
giving raise to one more level, at the bottom of the MDD. There are no local
events and, in addition to events synchronizing adjacent subnets, the model
contains events synchronizing levels n and 1, for 2 ≤ n ≤ N + 1.
– The flexible manufacturing system (FMS) model [22] has a fixed shape, but is
parameterized by the initial number N of tokens in some places. We partition
this model into 19 subnets, giving rise to a 19–level MDD with a moderate
degree of locality, as events span from two to six levels.
Fig. 4 compares three variants of our new algorithm, using the Lazy policy or the
Strict policy with thresholds of 1 or 100 nodes per level, against the Recursive
algorithm in [22] and the Forwarding algorithm in [10]. We ran SMART on a
800 MHz Intel Pentium III PC under Linux. On the left column, Fig. 4 reports
the size of the state space for each model and value of N . The graphs in the
middle and right columns show the peak and final number of MDD nodes and
the CPU time in seconds required for the state–space generations, respectively.
For the models introduced above, our new approach is up to two orders of
magnitude faster than [22] (a speed–up factor of 384 is obtained for the 1000 dining philosophers’ model), and up to one order of magnitude faster than [10] (a
speed–up factor of 38 is achieved for the slotted ring model with 50 slots). These
results are observed for the Lazy variant of the algorithm, which yields the best
runtimes; the Strict policy also outperforms [22] and [10]. Furthermore, the
gap keeps increasing as we scale up the models. Just as important, the saturation algorithm tends to use many fewer MDD nodes, hence less memory. This
is most apparent in the FMS model, where the difference between the peak and
Model & size
Queens
N
S
6 1.53·102
7 5.52·102
8 2.06·103
9 8.39·103
10 3.55·104
11 1.67·105
Dining phil.
N
S
100 4.97·1062
200 2.47·10125
400 6.10·10250
600 1.51·10376
800 3.72·10501
1000 9.18·10626
Peak & final MDD nodes
70000
Generation time (sec.)
1000
lazy
strict(1)
strict(100)
forwarding
recursive
final
60000
50000
40000
10
30000
1
20000
0.1
10000
0
0.01
6
7
8
9
10
11
6
6000
1000
5000
100
4000
N
5
10
25
50
75
100
FMS
S
2.90·106
2.50·109
8.54·1013
4.24·1017
6.98·1019
2.70·1021
8
9
10
11
200
400
600
800
1000
10
1
2000
0.1
1000
0
0.01
200
400
600
800
1000
10000
70000
1000
60000
50000
100
40000
10
30000
20000
1
10000
0
0.1
10 15 20 25 30 35 40 45 50
Round robin
N
S
10 2.30·104
25 1.89·109
50 1.27·1017
100 2.85·1032
150 4.82·1047
200 7.23·1062
7
3000
80000
Slotted ring
N
S
10 8.29·109
20 2.73·1020
30 1.04·1031
40 4.16·1041
50 1.72·1052
lazy
strict(1)
strict(100)
forwarding
recursive
100
45000
40000
35000
30000
25000
20000
15000
10000
5000
0
10 15 20 25 30 35 40 45 50
1000
100
10
1
0.1
0.01
10
50
100
150
200
10
60000
10000
50000
1000
40000
100
30000
10
20000
1
10000
0.1
0
0.01
10 20 30 40 50 60 70 80 90 100
50
100
150
200
10 20 30 40 50 60 70 80 90 100
Fig. 4. State–space sizes, memory consumption, and generation times (a logarithmic
scale is used on the y-axis for the latter). Note that the curves in the upper left diagram
are almost identical, thus they visually coincide.
the final number of nodes is just a constant, 10, for any Strict policy. Also
notable is the reduced memory consumption for the slotted ring model, where
the Strict(1) policy uses 23 times fewer nodes compared to [22], for N = 50.
In terms of absolute memory requirements, the number of nodes is essentially
proportional to bytes of memory. For reference, the largest memory consumption
in our experiments using saturation was recorded at 9.7MB for the FMS model
with 100 tokens; auxiliary data structures required up to 2.5MB for encoding
the next–state functions and 200KB for storing the local state spaces, while the
caches used less than 1MB. Other SMART structures account for another 4MB.
In a nutshell, regarding generation time, the best algorithm is Lazy, followed by Strict(100), Strict(1), Forwarding, and Recursive. With respect to memory consumption, the best algorithm is Strict(1), followed by
Strict(100), Lazy, Forwarding, and Recursive. Thus, our new algorithm
is consistently faster and uses less memory than previously proposed approaches.
The worst model for all algorithms is the queens problem, which has a very large
number of nodes in the final representation of S and little locality. Even here,
however, our algorithm uses slightly fewer nodes and is substantially faster.
6
Related Work
We already pointed out the significant differences of our approach to symbolic
state–space generation when compared to traditional approaches reported in the
literature [20], which are usually deployed for model checking [12]. Hence, for a
fair comparison, we should extend our algorithmic implementation to that of a
full model checker first. Doing this is out of the scope of the present paper and
is currently work in progress.
The following paragraphs briefly survey some orthogonal and alternative approaches to improving the scalability of symbolic state–space generation and
model–checking techniques. Regarding synchronous hardware systems, symbolic
techniques using BDDs, which can represent state spaces in sublinear space, have
been thoroughly investigated. Several implementations of BDDs are available;
we refer the reader to [27] for a survey on BDD packages and their performance. To improve the time efficiency of BDD–based algorithms, breadth–first
BDD–manipulation algorithms [4] have been explored and compared against
the traditional depth–first ones. However, the results show no significant speed–
ups, although breadth–first algorithms lead to more regular access patterns of
hash tables and caches. Regarding space efficiency, a fair amount of work has
concentrated on choosing appropriate variable orderings and on dynamically re–
ordering variables [15].
For asynchronous software systems, symbolic techniques have been investigated less, and mostly only in the setting of Petri nets. For safe Petri nets, BDDbased algorithms for the generation of the reachability set have been developed
in [25] via encoding each place of a net as a Boolean variable. These algorithms
are capable of generating state spaces of large nets within hours. Recently, more
efficient encodings of nets have been introduced, which take place invariants into
account [24], although the underlying logic is still based on Boolean variables.
In contrast, our work uses a more general version of decision diagrams, namely
MDDs [18, 22], where more complex information is carried in each node of a diagram. In particular, MDDs allow for a natural encoding of asynchronous system
models, such as distributed embedded systems.
For the sake of completeness, we briefly mention some other BDD–based techniques exploiting the component–based structure of many digital systems. They
include partial model checking [3], compositional model checking [19], partial–
order reduction [2], and conjunctive decompositions [21]. Finally, also note that
approaches to symbolic verification have been developed, which do not rely on
decision diagrams but instead on arithmetic or algebra [1, 6, 26].
7
Conclusions and Future Work
We presented a novel approach for constructing the state spaces of asynchronous
system models using MDDs. By avoiding to encode the global next–state function as an MDD, but splitting it into several local next–state functions instead,
we gained the freedom to choose the sequence of event firings, which controls the
fixed–point iteration resulting in the desired global state space. Our central contribution is the development of an elegant iteration strategy based on saturating
MDD nodes. Its utility is proved by experimental studies which show that our
algorithm often performs several orders of magnitude faster than most existing
algorithms. Equally important, the peak size of the MDD is usually kept close
to its final size.
Regarding future work, we plan to employ our idea of saturation for implementing an MDD–based CTL model checker within SMART [11], to compare
that model checker to state–of–the–art BDD–based model checkers, and to test
our tool on examples that are extracted from real software.
Acknowledgments. We would like to thank the anonymous referees for their
valuable comments and suggestions.
References
[1] P. Aziz Abdulla, P. Bjesse, and N. Eén. Symbolic reachability analysis based on
SAT-solvers. In TACAS 2000, vol. 1785 of LNCS, pp. 411–425. Springer-Verlag,
2000.
[2] R. Alur, R.K. Brayton, T.A. Henzinger, S. Qadeer, and S.K. Rajamani. Partialorder reduction in symbolic state-space exploration. In CAV ’97, vol. 1254 of
LNCS, pp. 340–351. Springer-Verlag, 1997.
[3] H.R. Andersen, J. Staunstrup, and N. Maretti. Partial model checking with ROBDDs. In TACAS ’97, vol. 1217 of LNCS, pp. 35–49. Springer-Verlag, 1997.
[4] P. Ashar and M. Cheong. Efficient breadth–first manipulation of binary decision
diagrams. In ICCAD ’94, pp. 622–627. Computer Society Press, 1994.
[5] J.A. Bergstra, A. Ponse, and S.A. Smolka. Handbook of Process Algebra. Elsevier
Science, 2000.
[6] A. Biere, A. Cimatti, E.M. Clarke, and Y. Zhu. Symbolic model checking without
BDDs. In TACAS ’99, vol. 1579 of LNCS, pp. 193–207. Springer-Verlag, 1999.
[7] R.E. Bryant. Graph-based algorithms for Boolean function manipulation. IEEE
Trans. on Comp., 35(8):677–691, 1986.
[8] R.E. Bryant. Symbolic Boolean manipulation with ordered binary-decision diagrams. ACM Comp. Surveys, 24(3):393–418, 1992.
[9] J.R. Burch, E.M. Clarke, K.L. McMillan, D.L. Dill, and L.J. Hwang. Symbolic
model checking: 1020 states and beyond. Inform. & Comp., 98(2):142–170, 1992.
[10] G. Ciardo, G. Lüttgen, and R. Siminiceanu. Efficient symbolic state–space construction for asynchronous systems. In ICATPN 2000, vol. 1639 of LNCS, pp.
103–122. Springer-Verlag, 2000.
[11] G. Ciardo and A.S. Miner. SMART: Simulation and Markovian Analyzer for
Reliability and Timing. In IPDS ’96, p. 60. IEEE Computer Society Press, 1996.
[12] A. Cimatti, E. Clarke, F. Giunchiglia, and M. Roveri. NuSMV: A new symbolic
model verifier. In CAV ’99, vol. 1633 of LNCS, pp. 495–499. Springer-Verlag,
1999.
[13] E.M. Clarke, O. Grumberg, and D. Peled. Model Checking. MIT Press, 1999.
[14] R. Cleaveland, J. Parrow, and B. Steffen. The Concurrency Workbench: A
semantics-based tool for the verification of finite-state systems. TOPLAS,
15(1):36–72, 1993.
[15] M. Fujita, H. Fujisawa, and Y. Matsunaga. Variable ordering algorithms for ordered binary decision diagrams and their evaluation. IEEE Trans. on Computer–
Aided Design of Integrated Circuits and Systems, 12(1):6–12, 1993.
[16] A. Geser, J. Knoop, G. Lüttgen, B. Steffen, and O. Rüthing. Chaotic fixed point
iterations. Techn. Rep. MIP-9403, Univ. of Passau, 1994.
[17] S. Graf, B. Steffen, and G. Lüttgen. Compositional minimisation of finite state
systems using interface specifications. Formal Asp. of Comp., 8(5):607–616, 1996.
[18] T. Kam, T. Villa, R.K. Brayton, and A. Sangiovanni-Vincentelli. Multi-valued
decision diagrams: Theory and applications. Multiple-Valued Logic, 4(1–2):9–62,
1998.
[19] K. Larsen, P. Pettersson, and W. Yi. Compositional and symbolic model-checking
of real-time systems. In RTSS ’95, pp. 76–89. Computer Society Press, 1995.
[20] K.L. McMillan. Symbolic Model Checking: An Approach to the State-explosion
Problem. PhD thesis, Carnegie-Mellon Univ., 1992.
[21] K.L. McMillan. A conjunctively decomposed Boolean representation for symbolic
model checking. In CAV ’96, LNCS, pp. 13–24. Springer-Verlag, 1996.
[22] A.S. Miner and G. Ciardo. Efficient reachability set generation and storage using
decision diagrams. In ICATPN ’99, vol. 1639 of LNCS, pp. 6–25. Springer-Verlag,
1999.
[23] T. Murata. Petri nets: Properties, analysis and applications. Proc. of the IEEE,
77(4):541–579, 1989.
[24] E. Pastor and J. Cortadella. Efficient encoding schemes for symbolic analysis of
Petri nets. In DATE ’98, pp. 790–795. IEEE Computer Society Press, 1998.
[25] E. Pastor, O. Roig, J. Cortadella, and R.M. Badia. Petri net analysis using
Boolean manipulation. In ICATPN ’94, vol. 815 of LNCS, pp. 416–435. SpringerVerlag, 1994.
[26] M. Sheeran and G. Stålmarck. A tutorial on Stålmarck’s proof procedure for
propositional logic. Formal Methods in System Design, 16(1):23–58, 2000.
[27] B. Yang, R.E. Bryant, D.R. O’Hallaron, A. Biere, O. Coudert, G. Janssen, R.K.
Ranjan, and F. Somenzi. A performance study of BDD–based model checking. In
FMCAD ’98, vol. 1522 of LNCS, pp. 255–289. Springer-Verlag, 1998.
© Copyright 2026 Paperzz