State Space Exploration
Graph Exploration
State space exploration is an instance of graph exploration. Consider a directed graph
and take a node . We assume there is a function
that returns the
successor nodes of a vertex. An abstract algorithm for exploring the graph starting from vertex
is
Algorithm: Graph exploration
Event points
There are many different applications of state space exploration. The Boost Graph Library [Siek02] uses a clever idea to separate such applications from the exploration itself. It is done by distinguishing event points in the algorithm that the user can respond to by means of callback functions. For our purposes we select the following events:
is invoked when a state is encountered for the first time |
|
is invoked on every transition |
|
is invoked on a state right before its outgoing transitions are being explored |
|
is invoked on a state after all of its outgoing transitions have been explored |
The events are named in terms of states and transitions instead of vertices and edges, since this is closer to our application domain. The exploration algorithm with event points included looks like this:
Algorithm: Graph exploration with event points
Applications
Many applications can be easily expressed in terms of the given event points.
Deadlock checking
With deadlock checking we are looking for states that have no outgoing transitions.
By introducing one boolean variable we can implement deadlock
checking as follows. The callback functions are printed as comments in gray.
Algorithm: Deadlock checking implemented using event points
Search strategies
Exploration can be done with different search strategies. We describe three of them: breadth-first, depth-first and highway. They mainly differ in the order in which the elements of the todo set are processed. In breadth-first search nodes at the present depth are explored before nodes at a higher depth. In depth-first search the highest-depth nodes are explored first. Highway search is a variant that uses a breadth-first search, but it only explores a part of the state space.
In all three cases the list is stored in a double ended queue. We use the slicing
operator to denote parts of a list. For example,
corresponds to the sublist
.
Breadth-first search
Algorithm: Breadth-first search
Depth-first search
Algorithm: Depth-first search
Highway search
In highway search (see [EGWW09]) a breadth first search is done, with the restriction that at most
states are put in the todo list for each level. The variable
maintains the
number of states in the todo list corresponding to the current level, and the variable
counts how many elements have been added corresponding to the next level. Once
reaches
the maximum value
, elements are being overwritten randomly.
Note
The specification below deviates from the published version of highway search in the
sense that overwritten elements are added to the set . To avoid this,
the structure of the algorithm needs to be changed significantly.
Algorithm: Highway search
In the algorithm of [EGWW09], the set stores todo elements corresponding to the
current level, and the set
stores todo elements corresponding to the next level.
The algorithm above uses only one list
that stores both of them. At each iteration of
the while loop the first
elements of the
list belong to the current level,
and the remaining elements belong to the next level. Furthermore, the algorithm above contains only
one application of a random generator, compared to two applications in the original version. The
element
is chosen randomly in the range
. There is an
probability that this value is in the range
. If
is inside the
range, the element in the
list with index
(counting from the end) is
overwritten. This behaviour matches with the published version.
Cycle detection
For cycle detection the event points in Event points are insufficient. In [Siek02] the following recursive depth first algorithm is given:
Algorithm: Recursive cycle detection algorithm as specified in Boost
The code in Boost uses an iterative version:
Algorithm: Iterative cycle detection algorithm as implemented in Boost
For our purposes we rewrite this as:
Algorithm: Recursive cycle detection
Algorithm: Iterative cycle detection
Whenever the event is triggered, a cycle is found.
Untimed state space exploration
Consider the following untimed linear process specification , with initial state
.
This linear process is a symbolic representation of a state space, or labeled transition system
(LTS). The previously described graph exploration algorithms can be applied to explore a state space.
Let be a rewriter. An algorithm for untimed state space exploration is
Algorithm: Untimed LPS exploration
The set is computed using the Enumerate algorithm. This computation may be expensive.
Hence the condition
is first rewritten, since if it evaluates to
the computation of
can be skipped.
Timed state space exploration
Consider the following timed linear process specification , with initial state
.
Note that the time tag is optional. If it is omitted, the corresponding action
may happen at an arbitrary time. In timed state space exploration, care is taken that on every trace
the time tags are increasing. In order to achieve that, a time stamp is recorded for each state in
the state space. We use the notation
to denote the state
with
associated time stamp
. An algorithm for timed state space exploration is
Algorithm: Timed LPS exploration
Stochastic state space exploration
Consider the following stochastic linear process specification , with initial state
.
where and
are stochastic distributions.
We define a stochastic state as a set
with
a sequence of
non-zero probabilities that sum up to 1, and
a sequence of states.
The function
is used to compute a stochastic state from
its symbolic representation.
Algorithm: Computation of a stochastic state
The set is computed using the Enumerate algorithm.
An algorithm for stochastic state space exploration is
Algorithm: Stochastic LPS exploration
Caching
The computation of the set of solutions in
is expensive.
Therefore it may be a good idea to cache these solutions. Caching can be done locally (i.e. using a
separate cache for each summand), or globally. This leads to the following variants of the
algorithm. We assume that
is a function that computes free variables of an expression.
Let
be the set of process parameters (i.e. the elements of
).
Local caching
In the local caching algorithm for each summand a mapping
is maintained.
The cache key is comprised of the actual values of the process parameters that appear in the
condition
.
Algorithm: LPS exploration with local caching
Global caching
In the global caching algorithm one mapping is maintained. To achieve this, the condition
of the summands is added to the cache key. If many summands share the same condition, global caching
may be beneficial. In practice this doesn’t seem to happen much.
Algorithm: LPS exploration with global caching
In this algorithm is a mapping, with
.
We use the notation
to denote the unique element
such that
.
Confluence Reduction
Confluence reduction (see [GP00], [Blo01] and [BP02]) is an on-the-fly state space exploration
method that produces a reduced state space. For confluence reduction we assume that the set of
summands is partitioned into a set
of ‘regular’ summands, and a set
of confluent
-summands. The confluent
-summands are
used to determine a unique representative state that is reachable via confluent
steps.
This is done using the graph algorithm
. This leads to the
following variant of the algorithm:
Algorithm: LPS exploration with confluence reduction
As suggested in [BP02] Tarjan’s strongly connected component (SCC) algorithm (see [Tar72]) can be used to compute a unique representative.
Tarjan’s SCC algorithm
A recursive implementation of Tarjan’s strongly connected components algorithm that uses four global
variables ,
,
and
. The helper function
computes the connected component reachable from node
. In
this function it is assumed that the function call
returns the
successor states of
in a deterministic order.
Algorithm: Tarjan’s Strongly Connected Component Algorithm
Input: : A graph with nodes
and edges
.
Output: : A sequence containing all strongly connected components of the graph.
Algorithm: Helper function StrongConnect
Input: : An element of
.
A side effect of a call is that
contains the connected
components that have been found.
FindRepresentative
Due to properties of confluent -summands, there is always only one terminal strongly
connected component, i.e. a strongly connected component without outgoing edges. Furthermore, the
first strongly connected component reported by Tarjan’s algorithm is always terminating. For our
implementation of
we prefer to use an iterative version of
Tarjan’s SCC algorithm. The reason for this is that an iterative version can be more easily
interrupted once the first SCC has been found. The algorithm description in [Oqv] has been used
as a model for our solution.
Algorithm: Find a unique representative node in a graph
References
Jeremy Siek, Lie-Quan Lee and Andrew Lumsdale. The Boost Graph Library: User Guide and Reference Manual. Addison-Wesley, 2002.
Tom A. N. Engels, Jan Friso Groote, Muck van Weerdenburg and Tim A. C. Willemse. Search algorithms for automated validation. Journal of Logic and Algebraic Programming, 78(4):274–287, 2009.
Jan Friso Groote and Jaco van de Pol. State Space Reduction Using Partial tau-Confluence. In MFCS, LNCS 1893, pages 383–393. Springer, 2000.
Stefan Blom. Partial t-confluence for Efficient State Space Generation, 2001.
Stefan Blom and Jaco van de Pol. State Space Reduction by Proving Confluence. In CAV, LNCS 2404, pages 596–609. Springer, 2002.
Robert Tarjan. Depth first search and linear graph algorithms. SIAM Journal on Computing, 1(2), 1972.
Jesper Öqvist. Iterative Tarjan Strongly Connected Components in Python. https://llbit.se/?p=3379. Accessed 2019-03-26.