Presentation of PyNuSMV¶
PyNuSMV is a Python interface to NuSMV, allowing to use NuSMV as a Python library. It is composed of several classes representing NuSMV data structures and providing functionalities on these data. This page describes the goals behind PyNuSMV and the architecture of the library and, covers its limitations.
Goals¶
The main goal of PyNuSMV is to provide a Python interface for NuSMV functionalities. This interface can be used as a library of functions on models, BDDs, SAT solvers and other data structures of NuSMV.
One subgoal is to provide all the functionalities of NuSMV at the Python level, e.g. calling the bdd_and
function on two bdd_ptrs
. This is achieved by using SWIG, a wrapper generator, to generate a wrapper for every function of NuSMV. Thanks to this wrapper, there are no restrictions to calling NuSMV functions and using its data structures. On the other hand, no barriers are set to forbid erroneous behaviors or to help the user.
Another subgoal is to provide a Python-like library to access the main data structures and functions of NuSMV: FSM, BDD, parser, model checking algorithms, simulation, etc. For example, providing a class BDD
with a built-in operator &
, such that bdd1 & bdd2
computes bdd_and(bdd1, bdd2)
. This library would contain the error mechanisms required to ensure the correct usage of NuSMV.
In summary, PyNuSMV has two main goals:
- providing a complete Python interface for NuSMV functions and data structures;
- providing a Python-like interface to some major data structures and functionalities.
Architecture¶
PyNuSMV is composed of three main layers. The first layer is NuSMV. The second layer is called the lower interface; it contains all the functions of NuSMV, at Python level, wrapped by SWIG. The third layer is called the upper interface; it contains the Python-like functionalities built upon the lower interface.
NuSMV¶
The version of NuSMV used in PyNuSMV is the version 2.5.4. NuSMV code has been kept unchanged, except for very small details; the details of which can be seen (and is explained) in the several .patch files we apply before building the NuSMV sources. For instance:
- some functions and macro declarations have been commented because they were defined twice;
- some static keywords have been removed to allow exporting the functions;
- an assertion check was removed from src/cmd/cmdMisc.c since it made the NuSMV initalization/deinitialization impossible.
Lower Interface¶
The lower interface is composed of a set Python modules generated by SWIG. For every NuSMV package, i.e. for every sub-directory in the src/ directory of NuSMV, there is a SWIG interface and a Python module that provide wrappers for functions and data structures of the package. This section briefly discusses the structure and content of the lower interface and presents its limitations.
Structure¶
The structure of the lower interface is a copy of the one of NuSMV. Let’s consider as a NuSMV package any sub-directory of the src/ directory of NuSMV sources. For example, NuSMV contains the mc/ and fsm/bdd/ packages. The structure of the lower interface is the same. The lower interface is located in the pynusmv_lower_interface
Python package. Every NuSMV package gets its PyNuSMV package. For example, the prop/ NuSMV package is wrapped into the pynusmv_lower_interface.nusmv.prop
Python package; the compile/symb_table/ NuSMV package is wrapped into the pynusmv_lower_interface.nusmv.compile.symb_table
package.
Furthermore, every wrapped function is automatically documented by SWIG with the corresponding C function signature. It allows the developer to know what types of arguments the wrapped function takes.
Content¶
The goal of the lower interface is to provide a wrapper for every function of NuSMV. In practice, for every package, only the set of functions that are considered as public are provided. This means that, for every package, all the headers are exported, except the ones with a name ending with Int.h, _int.h or _private.h (these are explicitly referred to as ‘internal’ or ‘private’ headers in the NuSMV codebase).
Limitations¶
The lower interface has some limitations. First, it does not wrap all the functions, but only the ones present in the public headers, as described in the previous section.
Furthermore, there are some exceptions:
- the utils/lsort.h header is not wrapped because SWIG cannot process it.
- A set of functions, from different packages, are not wrapped because they have no concrete implementation so far.
Upper Interface¶
The upper interface is composed of Python classes representing data structures of NuSMV as well as additional modules giving access to main functionalities that do not belong to a data structure, like CTL model checking. Each instance of these classes contains a pointer to the corresponding NuSMV data structure and provides a set of methods on this pointer. This section explains the way all pointers to data structures are wrapped, how the memory is managed and presents an overview of the classes and modules currently defined.
Wrapping pointers¶
Every pointer to a NuSMV data structure is wrapped into a Python class that is a subclass of the PointerWrapper
class. This class contains a _ptr
attribute (the wrapped pointer) and implements the __del__
destructor. All the other functionalities are left to subclasses. This provides a uniform way of wrapping all NuSMV pointers.
Garbage Collection¶
In PyNuSMV, we distinguish two types of pointers to NuSMV data structures: the pointers that have to be freed and the ones that do not. For example, a pointer to a BDD has to be freed after usage (with bdd_free
) while a pointer to the main FSM do not, because NuSMV frees it when deinitializing.
In addition to the wrapped pointer, the PointerWrapper class contains a flag called _freeit
that tells whether the pointer has to be freed when destroying the wrapper. If needed, the destructor calls the _free
method, that does the work. The _free
method of PointerWrapper
class does nothing. It is the responsibility of subclasses to reimplement this _free
method if the pointer has to be freed. In fact, PointerWrapper
cannot say how to free the pointer since the NuSMV function to call depends on the wrapped pointer (BDDs have to be freed with bdd_free
, other pointers need other functions).
Furthermore, we define the following conventions:
- wrappers containing pointers that do not have to be freed do not have to reimplement the
_free
method. - pointers that do not have to be freed can be shared between any number of wrappers. Since these pointers are not freed, there is no problem.
- wrappers containing pointers that have to be freed must reimplement the
_free
method to free the pointer when needed. - there must exist at most one wrapper for any pointer that has to be freed. This ensures that the pointer will be freed only once.
- if no wrapper is created to wrap a pointer, it is the responsibility of the one who got the pointer to free it.
By following these conventions, PyNuSMV can manage the memory and free it when needed.
Thanks to the specific _free
method implementations, pointers can be correctly freed when the wrapper is destroyed by Python. But pointers must not be freed after deinitializing NuSMV. So we need a way to free every pointer before deinitializing NuSMV.
To achieve this garbage collection, PyNuSMV comes with a specific module pynusmv.init
that allows to initialize and deinitialize NuSMV, with the init_nusmv
and deinit_nusmv
functions. Before using PyNuSMV, init_nusmv
must be called; after using PyNuSMV, it is necessary to deinitializing NuSMV by calling deinit_nusmv
. Furthermore, init_nusmv
creates a new list in which every newly created PointerWrapper
(or subclass of it) is registered. When deinit_nusmv
is called, all the wrappers of the list are freed before deinitializing NuSMV. This ensures that all NuSMV data pointers wrapped by PyNuSMV classes are freed before deinitializing NuSMV.
Classes and Modules¶
PyNuSMV is composed of several modules, each one proposing some NuSMV functionalities:
init
contains all the functions needed to initialize and close NuSMV. These functions need to be used before any other access to PyNuSMV.glob
provides functionalities to read and build a model from an SMV source file.model
provides functionalities to define NuSMV models in Python.node
provides a wrapper to NuSMV node structures.fsm
contains all the FSM-related structures like BDD-represented FSM, BDD-represented transition relation, BDD encoding and symbols table.prop
defines structures related to propositions of a model; this includes CTL specifications.dd
provides BDD-related structures like generic BDD, lists of BDDs and BDD-represented states, input values and cubes.parser
gives access to NuSMV parser to parse simple expressions of the SMV language.mc
contains model checking features.exception
groups all the PyNuSMV-related exceptions.utils
contains some side functionalities.sat
contains classes and functions related to the operation and manipulation of the different sat solvers available in PyNuSMV.bmc.glob
serves as a reference entry point for the bmc-related functions (commands) and global objects. It defines amongst other the functionbmc_setup
wich must be called before using any of the BMC related features + the classBmcSupport
which acts as a context manager and frees you from the need of explicitly callingbmc_setup
.bmc.ltlspec
contains all the functionalities related to the bounded model checking of LTL properties: from end to end property verification to the translation of formulas to boolean expressions corresponding to the SAT problem necessary to verify these using LTL bounded semantics of the dumping of problem to file (in DIMACS format).bmc.invarspec
contains all the functionalities related to the verification of INVARSPEC properties using a technique close to that of SAT-based bounded model checking for LTL. (See Niklas Eén and Niklas Sörensson. “Temporal induction by incremental sat solving.” for further details).bmc.utils
contains bmc related utility functions.be.expression
contains classes and functions related to the operation and manipulation of the boolean expressions.be.fsm
contains classes and functions related to PyNuSMV’s description of an FSM when it is encoded in terms of boolean expressions.be.encoder
provides the boolean expression encoder capabilities that make the interaction with a SAT solver easy.be.manager
contains classes and functions related to the management of boolean expressions (conversion to reduced boolean circuits. Caveat: RBC representation is not exposed to the upper interface).collections
impements pythonic wrappers around the internal collections and iterator structures used in NuSMV.wff
encapsulates the notion of well formed formula as specified per the input language of NuSMV. It is particularly useful in the scope of BMC.trace
defines the classes Trace and TraceStep which serve the purpose of representing traces (executions) in a PyNuSMV model.sexp.fsm
contains a representation of the FSM in terms of simple expressions.
Limitations¶
PyNuSMV has some limitations. Two major ones are the exposed functionalities and error management.
Exposed functionalities¶
Since the upper interface of PyNuSMV is written by hand, it needs some work to implement its functionalities (compared to the lower interface generated with SWIG) and therefore, the framework might be missing some functionalities. If one such functionality is of interest to you, feel free to either hack the code yourself or get in touch with us to get some help.
Error Management¶
NuSMV can react in various ways when an error occurs. It can output a message at stderr
and returns an error flag, e.g. when executing a command. It also integrates a try/fail mechanism using lonjmp
functionalities. And it can also abruptly exit using the exit()
function.
For now, there is little error management in PyNuSMV. When possible, the try/fail mechanism has been used to avoid NuSMV to completely exit()
when there is an error. Instead, exceptions are raised, with sometimes error messages from NuSMV. In some cases, errors are correctly raised but a message is printed at stderr
by NuSMV itself. Some future work on PyNuSMV includes a better error management.