In this blog post, I would like to report on a relatively short but productive project with Jeroen Keiren and Tim Willemse. Let’s call the result of this project the Bisimulation Games tool. Work began on this project in the summer of 2016. In particular, this post covers the project’s set-up and architectural choices, serves in a small part as a promotion of the idea to do more interactive visualization of theoretical concepts in computer science, and also contains a sneak-preview of another project that replicates the most important design choice of this project.

Background

An important aspect of this project is the interactive visualization of games for showing bisimulation relations. We work with Labelled Transition Systems (LTS) here: an abstract representation of the state space of, for example, computer programs. Another example can be seen in the window of the application (above) we have five states: t_0 and t_1, s_2 and c_3 and c_4. Between states we have transitions: these represent observable actions (eat pizza and hack) or internal actions (so-called tau).

David de Frutos-Escrig, Jeroen Keiren and Tim Willemse have worked towards a definition of a game semantics for showing a number of bisimulation relations. In particular, as part of this project, Keiren has implemented game semantics for bisimulation, branching bisimulation and branching bisimulation with explicit divergence as a Haskell program. This program defines the game state space and game state transitions. Using these two definitions one can determine whether, given a vertex, either a winning strategy exists for either the player or for the opponent.

An important part of this project is to reuse this existing implementation. Here, an important design decision was made: to port the Haskell code to Frege, a Haskell implementation for the Java Virtual Machine. This allowed me to write the visualization and interaction with games in the Java AWT/Swing framework. The Frege project can be still considered experimental, as exemplified by a minor compiler bug that was found and fixed during the execution of this project.

Winning strategies

The next step in the project is strategy extraction. The existing Haskell implementation by Keiren provided an implementation for an algorithm for extracting the set of winning vertices for each player of the game: Spoiler (played by the computer) and Duplicator (played by the human operator). For an interactive application, a set of winning vertices is not enough: a human operator can take a step that would ultimately lead to her defeat, even if a winning strategy for Duplicator exists at that point. Thus, an extraction algorithm was developed that, from the set of winning vertices, determines a strategy (assigning to a vertex the best next move) for Spoiler, that is guaranteed to eventually win if a winning strategy for Spoiler exists. This part is not proved and only tested informally, but seems to work fine for the tested examples.

Interactivity

An important aspect is helping human operators discover flaws in their models. The application may act as a “debugger”, showing a step-by-step walkthrough of counter-examples that may help humans understand the model. Let us see an example, that is taken from Korver (see [16] in Games for Bisimulation and Abstraction mentioned earlier).

Suppose the user selects the node 0 (blue, left image) as opponent and A (red, left image) as player vertex. Remark that this is also logged in the message pane, on top of the window. First, the winning vertices are computed by the algorithm by Keiren (and shown in the lower left corner: the expected game outcome if both players play according to a winning strategy). Then a strategy is extracted, and the first step is taken: Spoiler challenges Duplicator to mimic the action “a”, as highlighted in blue.

Consequently, the human operator must choose an internal action (“tau” to B), since the other action (“b” to D) does not match the challenge. Then the Spoiler drops the challenge (second to last message, right image), and challenges Duplicator with a new action: from 0 to 4, mimic action “b”. Since Duplicator sits still in the new state of B, it cannot undo the “tau” step. Hence, the game is concluded: Duplicator has lost, and indeed, no winning strategy for Duplicator exists for these two starting nodes.

Internal Debugging

One interesting problem encountered during the execution of the project was a bug in the Haskell framework code, that I introduced because of a misinterpretation and misunderstanding of Büchi games. The game semantics is defined as a Büchi game. Such games may have infinite plays.

From the perspective of some player, a Büchi objective is to find a strategy of a finite play where the other player loses or an infinite play in which an infinite number of points is earned, or so-called accepting states are visited infinitely often. Observe that if we visit at least one accepting state in a cycle (a Büchi objective), then in an infinite play where the player remains within this cycle, it has visited an infinite number of accepting states.

So to prevent one player from winning, its opponent has to prevent that the player has an infinite play in which it reaches its objective. Thus the opponent must ensure that it either plays a finite game where the other player loses, or an infinite play in which all of the visited states are non-accepting (a co-Büchi objective).

To help finding the source of the problem, a monad was developed that keeps track of function context that is threaded through the functional code. For example, it dumps the current labeling of some vertex set each iteration of a function. Then a GUI was developed that tracks and shows, for each step, the current state of the function. This allowed me to find where the problem was located. The bug was found to have mixed up Büchi objectives and co-Büchi objectives in the complements of winning vertex sets, having not fully understood the duality.

The same debugging monad (and the corresponding table UI with minor adaptions) were later reused to debug the strategy extraction algorithm. These tools seemed to be of good use during the development.

Execution

The project was executed in 4 weeks. This short timespan ensured that only the most important aspects were touched. However, to make the project more worthwhile in the (near) future, it seems useful to explore even more bisimulation relations.

Although the current version of the tool is a fun toy to show simple models, there are numerous downsides: it is slow in execution (Frege is not the most efficient Haskell implementation), it may contain bugs or flaws in the GUI code, and the strategy extraction may be wrong. To extend the tool for more relations, one has to adapt both the GUI code and the simulation framework. And, finally, only I have worked on the GUI part of the application, so the code is ugly or unclear for others to work with.

However, it seems that the architectural choice (of writing the core of the application in Haskell/Frege and use only Java for interactivity and visualization) is a good one, for several reasons: first, the system is naturally split in two parts and needs to have a well-defined interface between these parts. That increases clarity of thought. Secondly, Haskell seems a better suited language for defining state spaces and transitions (especially thanks to the List monad) than Java. And Java has a lot of client installations and includes a GUI toolkit out-of-the-box. With this choice one has the best of both worlds.

Indeed, to verify this architectural choice, I have worked on a “spiritual successor” of this project over the past few weeks, that culminated in a new tool. The new tool, let’s call it the Distributed Algorithms tool, allows users to explore all possible executions of some built-in algorithms on a user-defined network. This project was supervised by Wan Fokkink.

The framework behind the Distributed Algorithms tool allows several basic distributed algorithms (for example Cidon’s traversal algorithm, see image) to be defined using a simple process algebraic framework. This framework allows one to specify the algorithm in terms of a state space, a message space and a local transition function with basic actions: sending, receiving, internal. Compare this with the game state space, game state transitions of the Bisimulation Games tool.

The user interface of the Distributed Algorithms tool is more advanced than the user interface of the Bisimulation Games tool. The Distributed Algorithms tool allows users to graphically construct networks from scratch, whereas in the Bisimulation Games tool users need to define networks textually in an external text editor. However, the Bisimulation Games tool might be perceived as easier to use. Another difference is that for the Bisimulation Games tool, one cannot undo a step: a user has to redo every action from scratch to revisit a particular choice. By allowing users to control the flow of time and undo choices, as is possible in the Distributed Algorithms tool, it is easier for users to explore different scenarios.

In the Distributed Algorithms tool, more effort and focus than before went into cleaner and better organized GUI code. Also with this project, the bulk of work is done within 4 weeks. However, one of the downsides of the above architectural choice became apparent: there is significant overhead in programming time spent translating Java objects to and from Frege’s compiled Haskell objects.

Concluding words

All in all, I believe that these visualization and simulation projects are fun ways of exploring theoretical concepts. A visualization is often burdensome to write, but I believe it pays off the effort in the long run. Visualization tools allows users (including its authors) to explore theoretical concepts on a different level. It may be tedious for some concepts to work out examples manually, and visualizing them solves that problem. Interactivity makes the concepts even more alive: it allows users to experiment with the theory without knowing the formalization behind it, and may serve as an easy introduction into a new field of study.

As a programmer, the practical nature of the tools required a good understanding of the subject matter, and some creativity to correctly implement and visualize them. As was mentioned, errors were made because of misunderstanding or misinterpretation. It seems one has to be patient (e.g. by building debugging tools) to solve one’s own problems. At the same time, one needs a certain amount of rigor to prevent introducing problems in the first place.

Finally, the choice to work with both the Java language and the Haskell language is an interesting one. I hope to work more on these kinds of projects in the near future: short in duration, diving deep in theory, and (re)presenting the results interactively as a tool that is hopefully pleasant to use.

About the course

Anyone who has ever designed an embedded system or a communication protocol involving several components executing simultaneously knows that such software is inherently susceptible to bugs. Typical problems include race conditions, deadlocks, and unexpected interplay between different components. The parallel nature of these systems makes it notoriously hard to detect such bugs using testing (timing, e.g., plays a crucial role). This course is designed to provide an introduction to the problems that arise in the design of such systems. It provides ways to model such systems and reason about them.

Related posts

The course has been in the making for some time, and I reported on the experiences in setting it up here.

Ahmed discusses the problem of concurrent data structures. In general, you want to implement such data structures in a library using as much concurrency as possible. However, you want to provide a simple interface to the clients that provide the ordinary, atomic operations (client view). Providing naive solutions with coarse grain locking are unacceptable; therefore, in practice, execution intervals of different library routines may overlap, yet implementations must provide this client view.

Typically, verification of libraries providing concurrent data structures is done using observation refinement. Filipovic et al. showed in 2010 that observation refinement is implied by linearizability (due to Herlihy and Wing, 1990) — Ahmed notes that this result was accepted as folklore for over a decade. The problem with using this approach is that, even when given a finite number of threads and a regular specification, checking linearizability is in EXPSPACE (Alur et al. 1996). In 2014 Had Hamza showed that it is also EXPSPACE hard. Even the simpler case of checking linearizability of a single execution is NP-complete (Gibbons and Korach, 1997), and if you have an unbounded number of threads, even with finite-state threads, linearizability is undecidable (proof is by reduction of reachability in 2-counter machines).

Given the high complexity of the observation refinement problem, Ahmed proposes a different approach. He defines a notion of history for libraries, \(H(L)\) is the history for a library \(L\). History inclusion is equivalent to observation refinement. Furthermore, \(L_1\) is linearisable w.r.t. \(L_2\) iff \(H(L_1)\) is included in \(H(L_2)\) when \(L_2\) is atomic. Not that these equivalences immediately give rise to the result that computing history inclusion is computationally hard. Therefore the idea of doing verification behind, and we focus on efficient detection of observation refinement violations to find bugs. This can be done using an under-approximation schema.

Histories can be weakened in the sense that \(h_1\) is weaker than \(h_2\) (\(h_1 \leq h_2\)) iff \(h_1\) has less constraints than \(h_2\). If \(h_1 \leq h_2\) and \(h_2\) is in \(H(L)\) for library \(L\) then \(h_1\) is in \(H(L)\) too (on a high-level, we do ignore some details here). Now you can define an approximation \(A_k(h)\), such that \(A_k(h) \leq h\) for all \(k\), an for some \(k\) it is the case that \(h \leq A_k(h)\) (i.e. there is an approximation that returns the original history. Now, suppose that we have a history \(h\) such that \(A_k(h)\) in \(H(L_1)\) (so \(h\) in \(H(L_1)\)), and \(A_k(h)\) is not in \(H(L_2)\). Then we know that \(h\) is not in \(H(L_2)\), and we have found a counter example with respect to history inclusion using the abstraction, thus we discovered a bug. The abstraction (bounding) concept is based on the notion of interval-length, and ultimately checking is done using counting. For details I refer you to Ahmed’s slides.

Model: state machine with positive counters; edges have guards and updates over counters. Context-freeness proof is done in a number of steps. First semi-linear sets are considered (union of linear sets) A semi-linear set is stratifiable iff it is the union of linear sets with a stratified set of periods. A theorem by Ginsburg, 1966, relates context freeness of languages to stratifiable semilinear sets. For a subclass of semilinear sets, integral polyhedra, the stratifiability problem is coNP complete.

In flat counter systems, constraints can be expressed as integral polyhedra, so, the question whether the language of S is context-free is equivalent to the question whether the corresponding integer polyhedron is stratifiable.

Flat counter systems are interesting as an object of study for complexity reasons. There is a parallel with VAS systems, where flat VAS are used as a skeleton to solve the complexity for the general case.

Separation logic is used for reasoning about heaps. Unbounded heaps are described using systems of inductive definitions. A nice example of an inductive definition is that of the doubly linked list using the \(DLL(h,p,t,n) = …\) predicate. The inductive definitions the approach allows have some restrictions, yet it is shown how these restrictions can be lifted (sometimes leading to incompleteness, however).

The approach proposed is to reduce entailment \(\varphi \models \psi\) to checking \(L(A_{\varphi}) \subseteq L(A_{\psi})\), where \(A_{\varphi}\) and \(A_{\psi}\) are tree automata recognising unfolding trees of the inductive definitions \(\varphi, \psi\). The tree automata are deterministic, and the construction is based on tiles such that every predicate \(P\) is represented by a single tile, and a single tile maps to a single TA state \(q_P\). Soundness follows directly from the construction. For completeness canonisation and rotation closure is used. The described approach is EXPTIME complete.

In this talk, Peter and Rob consider the correctness of a routing protocol in wireless mesh networks (WMNs). In practice WMNs are unreliable and have poor performance. One of the causes for this could be the underlying routing protocols. As an example, they study an ad hoc on-demand distance vector protocol. On a high level, the protocol works as follows: if route is needed route request is broadcast; if a node receives such a request, and has no information it forwards the request; if a node has information about a destination a route reply is sent back in unicast; if unicast fails or a link break is detected a route error is sent using a group cast message. The description of the protocol relies on a routing table.

Properties that are relevant for the correctness of such a protocol are: route correctness, loop freedom, route discovery, packet delivery. In the verification they describe here, they consider loop freedom: you do not keep sending around packets indefinitely. The verification is performed using the following steps:

Formal specification made in the process algebra AWN (process algebra suited for specifying routing protocols). AWN extends process algebras with data structures, network-specific primitives such as (local) broadcast and (conditional) unicast, and it has a layered structure, allowing processes, nodes, networks of nodes and encapsulation. The specification consists of 6 processes, totalling about 150 lines of specification (the original standard is 40 pages of English prose).

Pen-and-paper proof of loop freedom: 20 pages, using 40 invariants (state/transition invariants talking about one or more nodes).

Mechanized proof: formalised Isabelle/HOL. One part is mechanization of process algebra AWN (ITP’14). Mechanization of the loop-freedom proof consists of 360 lemmas of which 40 are invariants, with the usual mechanisation overhead.

Node properties are often straightforward to prove.

Network properties are harder because the describe relations between multiple nodes; stating the properties alone in a machine readable way is already hard.

The total effort of doing the mechanised verification is estimated to be 1 person-year, of which about 8 months were devoted to the formalisation of AWN, and about 4 months were for the verification of the routing protocol.

One of the most interesting parts of the talk is the reflection on the mechanisation process (or, did they waste their time?) Here they observe that, when verifying variants of the protocol — that appear naturally due to different interpretations of the natural language standard — a lot of time can be saved. They illustrate this observation by reporting on variants they verified: in 5 variations in which the lemmas and invariants that were formulated were preserved, the formalisation and proofs could be fixed very easily (in a matter of hours). You are not always lucky, though, since if the lemmas do not hold in their original form any longer, you will need more expertise to fix the proofs, and the fixing is not so straightforward as in the other cases.

Cache coherence protocols gives shared memory view to multiple processors with individual caches and main memory. A well-known example of a cache coherence protocol is that for guaranteeing exclusive access in the German protocol. In this case, concurrent requests may lead to contention, and eventually to deadlock (where system-wide deadlock, or s-deadlock is considered).

Given a protocol P(N) with N an unbounded number of caches, the problem addressed is to verify freedom of s-deadlock on it. Prior work in this area mainly applies to mutual exclusion (no two caches have exclusive access at the same time); in that case, counterexamples typically involve just two caches because the property has a two-index property. For deadlock freedom, all caches are involved, hence counter examples might involve all caches. This makes that approaches that work in the mutual exclusion setting do not generally carry over to the setting of checking absence of s-deadlock.

Sharad describes a nice approach to verifying s-deadlock, which is based on the following idea. Establish a set of invariants that are stronger than s-deadlock, that do allow for verification using existing approaches. Once such invariants are established s-deadlock immediately follows. In order to establish such invariants, he uses flows (these are high-level descriptions of protocols). The invariants that are determined are such that some flow is enabled at all times, immediately implying absence of s-deadlock.

To determine such invariants, a looping process is started, where initially a number of straightforward invariants is given (that do not necessarily hold). These invariants are verified using model checking on a small number of agents. If the invariants hold for this small number of agents, then they proceed to verifying that they also hold for arbitrary N. If the invariants do not hold, the user is asked to refine the invariants, and the process repeats.

One of the open questions, and in my opinion a very important one, is whether invariants can automatically be refined when they fail during the model checking phase.

Today I have talked about our paper “Liveness Analysis for Parameterised Boolean Equation Systems” at ATVA’14 (which is currently being held in Sydney). Let’s take a look at what this work is about.

To start with, a parameterised Boolean equation system (PBES) is a system of fixed point equations that can, e.g., be used in model checking modal mu-calculus properties, check behavioural equivalence between systems, or solve Datalog queries.

So, in short, we have a system of mutually recursive fixed point equations, where the variables are parameterised with variables ranging over abstract data types; the right hand sides range over formulas over these data types and first order variables. In general we are interested in the solution to a specific instance of a variable, like \(X(1,1,1,1)\) in the example above.

Typically, we solve PBESs using instantiation into Boolean equation systems (BESs) or parity games (both are equivalent). The main motivation for instantiating is that it is easy to automate. However, this can easily lead to an infinite BES. For instance, in the example above, \(X(2,2,1,1)\) depends on \(Y(2,2,v+1,1)\) for infinitely many values of \(v\).

In our paper, we present a static analysis technique for PBESs that can be used to overcome such problems. In the example, we can observe that parameter \(k\) to the equation for \(Y\) is only relevant when \(j \neq 2\), but in the infinity in the example, we only reach \(Y\) with \(j = 2\), hence if we look closely, we can see that it is sound to replace \(Y(i,j,m+k,k)\) by \(Y(i,j,1,k)\)! This is exactly what the approach in the paper establishes in three steps:

We analyse the structure of the PBES to determine a set of control flow parameters. Using these control flow parameters we build a control flow graph.

In the next step, we label the nodes in the control flow graph with those data parameters that are possibly relevant at that control flow location, and use backwards reachability to propagate relevance. All parameters that occur not in the labelling of a particular control flow location are not relevant (dead) at that location.

Once we have established this labelling, we can use the information to reset dead variables in every recursion in the PBES.

The first analysis that we present treats all control flow parameters simultaneously. However, this approach suffers from an exponential blow-up. We therefore also present an approximate analysis that considers separate control flow graphs that does not suffer from this blow-up, yet closely approximates the global analysis.

This PBES has a finite instantiation, hence we can get an arbitrarily large reduction using this approach. Also in practice we have seen large reductions in size, and dramatic speedups for model checking, while the added overhead is limited.

After four years of hard work, I defended my PhD thesis, of which the summary is included below, on 17 September. I experienced the day of the defence as a great day. It was very nice to see so many of my family, friends and colleagues. Thank you!

Summary

Advanced Reduction Techniques for Model Checking

Modern-day software systems are highly complex, and typically consist of a number of in- teracting components running in parallel. Verification methods aim to prove correctness of such systems. Model checking is a commonly used verification technique, in which a model of the system under investigation is created. Subsequently, it is checked whether this model satisfies certain properties.

Model checking suffers from the infamous state space explosion problem: the number of states in a system grows exponentially in the number of parallel components. Part of the blow-up is also due to the presence of data in descriptions of model checking problems. Typically model checkers construct the full state space, causing the approach to break down already for a small number of parallel components. Properties are then checked on the state space, either directly, or by first combining the state space with a property, leading to, for example, a Boolean equation system or a parity game. The latter step leads to a further blow up.

In this thesis techniques are developed to counter this state space explosion problem in (parameterised) Boolean equation systems and parity games. The main contributions are as follows:

A structural operational semantics is presented that can equip Boolean equation systems with arbitrary right hand sides with a graph structure, called structure graph. Classical graph structures for analysing Boolean equation systems typically restrict right hand sides to purely conjunctive or disjunctive form, essentially redu- cing the equation system to a parity game. We use our graphs to study the effects of this restriction on right hand sides, and show that, in the context of bisimulation, this reduction never poses a disadvantage.

We investigate the reductions that can be achieved using strong bisimulation reduction of structure graphs, and we investigate idempotence identifying bisimulation. We show that, although it indeed identifies idempotence in a restricted subset of equation systems, it does not live up to its name for full-blown structure graphs.

The insights we gain by studying structure graphs motivate further investigation of weaker equivalences in the setting of parity games. Since the winner in parity games is determined by the infinitely often recurring priorities, intuitively, finite stretches of similar vertices can be compressed. We define the notions of stuttering equivalence and governed stuttering equivalence, and show that they preserve the winner in a parity game.

A new set of benchmarks for parity games is developed due to the unavailability of standard benchmarks. These benchmarks subsume the examples that are used for performance evaluation of solving algorithms in the literature. We provide a description of the benchmarks, and we analyse their characteristics.The efficacy of our equivalences for reducing parity games is evaluated using this set of benchmarks. It is shown that large reductions are possible, even reducing parity games to a single vertex with a self-loop. Average reductions of about 50% are achieved. Sometimes the technique allows solving parity games which cannot be solved directly; in general, however, the timing results do not show a clear advantage.

Finally, we move from a posteriori reduction of parity games, to static analysis tech- niques for parameterised Boolean equation systems, that allow the a priori reduc- tion of the induced parity games. Essentially, we present a heuristic that performs a live variable analysis. We show that this analysis is more effective at reducing the complexity than existing techniques, i.e. the reductions that we obtain are larger. Correctness of our analysis is shown using a generalisation of the equivalences that we introduced for parity games and Boolean equation systems.