CS1950Y Lecture 19: Model Checking
March 21, 2018


Last class, we talked about linear temporal logic, or LTL. This logic is used to describe the behavior of a system over time, as its state changes. This type of logic is tough to work with in a tool like Alloy; since we have to use atoms to model states and there are a limited number of atoms, we can only look a set number of steps ahead.

In TLA+, we have a little more freedom. Lets say we have some predicate P, and we want to know whether it is always true that in any path through the space of possible states, eventually P will be true. We can write the proposition []<> P to encode this. TLA+ will look through all the possible paths through the state space, and check whether at every point of each path, P is true at some future point. Notice that there is an implicit quantification over possible paths that occurs at the very beginning of this process, turning our proposition into something like: \A path \in paths : []<> P. TLA+ will always add this universal quantifier over paths to any temporal formula you ask it to check.

But is this always the behavior that we want? Or are there systems for which we want only to check whether some path displaying a certain characteristic exists?

Consider the case of UI design for some critical system, like the interface onboard a place. We certainly want there to be a way for the pilot, through some sequence of button-pushes and lever-pulls, to be able to deploy the landing gear. However, it would be pretty disastrous if every single possible series of interactions with the plane interface put down the gear. This is an example of a case where existential quantification over paths would be really useful!

Today, we'll delve into a new logic which allows for increased flexibility in terms of quantifiers and our temporal operators [] and <>. We have the following four operators to work with:

Does this new language allow us to express all the same ideas we could express before? In fact it does not. We'll illustrate with an example, using the state diagram reproduced below:

P not P P

The formula we are trying to reproduce in our new logic is \A path : paths, <>[] P. That is, for every possible path through the system, that path eventually reaches a point where P is always true after that point. This formula is true of the state diagram above. One possible path through the system cycles endlessly in the initial state, where P is always true. Every other path cycles for some amount of time in this initial state, and then passes through the second state to the third state, where it again cycles forever. Since P is true in the third states, all these paths also satisfy our formula.

Now, how might we rewrite \A path : paths, <>[] P using the four new quantified temporal operators? The obvious candidate is \A<>\A[] P, but it turns out that this formula is not actually true of the state diagram above.

What \A<>\A[] P means is that all paths eventually reach a point where all paths starting from that point only travel through states where P is true. This is patently false in our diagram. Consider the path which cycles through the first state infinitely; at every step of this path, the possible paths from that point include paths through the second state, where P is not true.

So it turns out there are things we can express in our first logic which cannot be expressed in our second.

Leaving aside our two logics for a second, let's return to TLA+. We've written a few temporal formulas at this point, and you may have noticed that our Spec always contains the conjunct "[]Next_vars". What exactly does this mean? The box operator we've already discussed: it checks whether a condition is globally true across all states. So then, what is "Next_vars"? It's actually just syntactic sugar for (Next \/ vars' = vars). That is, we allow every state to either (1) conform to Next (which means the following state will be the result of one of the transitions we've encoded), or (2) be identical to the next state. This latter type of behavior, where variables remain constant between two adjacent states is also known as "stuttering".

At first glance, stuttering can seem pretty much useless. If we're modeling state transitions, why would we every want to allow for the lack of a transition?

There are a few reasons. Firstly, stuttering allows us to represent finite traces as infinite traces which just stutter after reaching some end point. Secondly, if we are trying to compose multiple related models, we can let one model stutter while the other proceeds, and vice versa.