I am working on a program correction/synthesis project. My task is to obtain an error trace (counter-example), locate it in the complete state space, and repair the model at that location. I want to implement this as a NuSMV extension.
I have been debugging NuSMV to understand and explore its source code. So far, I have found my way to the creation of the BDD FSM (compile.c line 520). I am trying to find a way to traverse the bdd in order to gain programmatic access to the state space, and thus perform my corrective work upon the model. I have not yet been able to understand the recursive exploration functions NuSMV uses to verify properties via the bdd fsm.
I would like to know how i can traverse the bdd structure so I can visualise it through tools like dot. I would also like to know if such or smilar visualisations have already been made (I have searched but come up empty). Secondly, I would like to verify whether the direction I have taken up is the right one, or if there is a better way to obtain the complete state space of a given model, and explore it, especially with regards to a counter-example obtained through NuSMV.
This is an answer about ways to work with binary decision diagrams (BDDs) using CUDD other than via NuSMV, so focusing on the question's second part.
About symbolic algorithms for investigating a state space, the paper "Algorithmic verification of linear temporal logic specifications" by Kesten, Pnueli, and Raviv (ICALP '98, DOI: 10.1007/BFb0055036) is a good introduction, which covers counterexample construction.
One possibility for visualizing BDDs is working in Python, using Cython bindings to CUDD:
This approach uses
dd
, which can be installed usingpip
, withpip install dd
. Documentation can be found here. Looking at the (internal) moduledd._abc
may also be informative (this serves as a specification; the name "abc" alludes to abstract base classes in Python). (Pure Python suffices for smaller problems, and CUDD is useful for larger problems).There are two kinds of traversals relevant to the question:
These are discussed separately below.
Traversing the graph of a BDD
Depth-first traversal is more common than breath-first when working with BDDs. For the interface of
dd.cudd
anddd.autoref
such a traversal is:Complemented edges need also to be taken into account when working with BDDs (using CUDD or similar libraries). The attribute
u.negated
provides this information.The function
dd.bdd.copy_bdd
is a pure Python example of traversing a BDD. This function manipulates BDDs directly through a "low level" interface that is wrapped bydd.autoref
to look the same asdd.cudd
.Traversal of a state graph
The script
dd/examples/reachability.py
shows how to compute from which states a given set of states can be reached in a finite number of steps.The package
omega
is more convenient thandd
for developing BDD-based algorithms related to system behaviors. The scriptomega/examples/reachability_solver
demonstrates a reachability computation usingomega
.A basic example of forward reachability using
omega == 0.3.1
follows:Comparing
omega
withdd
:omega
supports variables and constants, and integer values for these (the relevant module isomega.symbolic.temporal
). Variables represent state changes, for examplex
andx'
. Constants remain unchanged through a system's behavior.dd
supports only Boolean-valued variables (omega
uses Boolean-valued variables to represent integer-valued variables, and so represent predicates as BDDs viadd
; the bitblasting is done in the moduleomega.logic.bitvector
).Several fixpoint operators are implemented in
omega.symbolic.fixpoint
. These operators can be used for model checking or temporal synthesis. The moduleomega.logic.past
includes translations of temporal operators that are relevant to symbolic model checking (also known as temporal testers).Documentation of
omega
can be found here.Further comments
I used the term "step" above to refer to a pair of consecutive states that represents a change of state allowed by the specification. The TLA+ language, and steps, flexible and rigid variables, and other useful concepts are described in Leslie Lamport's book Specifying Systems.
A collection of formal verification software is listed at https://github.com/johnyf/tool_lists/.
In my experience, working at the Python level, with only the BDD manager at the C level, is an efficient approach that leads to readable code, and clearer algorithms.