Rodin User and Developer Workshop 2013 - Tutorial

Here are the information we have shown you at the tutorial. If you want to follow this tutorial on your computer you will have to install ProB 2.0 and start an animation. For this tutorial we use a simple scheduler example. Maybe you have to clean the project after importing.

This page was updated in May of 2015 to reflect changes made to the ProB 2.0 API.

Please note that no matter which installation method you chose, you need to fetch the latest Prolog binaries. The easiest way is to type upgrade "latest" into the ProB 2.0 Groovy console.

In the following we will intermix some explanations with FAQ style code snippets. We assume that you execute all the code snippets. Some of the snippets rely on the previous execution of other snippets.

Preparation

Import the Scheduler project.

Start an animation for Scheduler0

Perform some random animation steps

Type trace = animations.getCurrentTrace() into the Groovy console. This accesses the current animation and saves it in the trace variable.

Type s = trace.getStateSpace() to save a reference to the state space that is associated with trace

The model

A model gives you access to the static properties of a Rodin development. Such as contexts, machines, constants, variables, invariants, events, etc. A model is a graph of components and their relationship. Except for Graph handling there are no shared methods between formalisms. The main difference between EventBModel and ClassicalBModel are the artifacts and relationships they can contain. An EventB Model consists of Machines and Contexts and the only relationships are refinement and sees.

The State Space

The state space represents the whole currently known world for an animation. It is lazily explored. Information about the state space is stored in State objects which can be queried to inspect information about any given state. These State objects are cached and can be accessed via their string identifier.

How do we retrieve the state space for a given model?

s = m.getStateSpace()

In Rodin, you should try to explore the state until your trace contains the massakr event. This event breaks the invariant of the system. Otherwise the next command will return an empty set.

How can I access the current state from an animation?

state = trace.getCurrentState()

Does the state that violate the invariant?

state.getInvariantOk()

Can we get the value of a variable in that state?

state.eval("active")

Can we randomly "execute" events?

y = state.anyEvent()

Can we "execute" a specific event?

Yes we can! We will figure out how it is done in a moment.

How do we find out which events are enabled?

state.getOutTransitions()

What are the elements of this list?

state.getOutTransitions().first().getClass()

Now let's get one of these Transition objects

trans = state.getOutTransitions()[1]

What is the name of the transition?

n = trans.getName()

What are the actual parameters?

params = trans.getParams()

What is the formal parameter name?

e = mch.getEvent(trans.getName())

Finally we can "execute" the Event using the perform method of the StateId object. At this point you need to chose an event that you want to execute. It has to be enabled in the state, but you can provide additional guards to restrict the parameters. For instance if the event del is enabled and we want to delete process P2 the command is

state.perform("del",["r=P2"])

We try not to intertwine different aspects of the system. That is why we had to get the formal parameter from the model's representation, the enabled operations from the state, and the detail information from the Transition object. This design principle was taken from Rich Hickey's Simple made easy talk.

However, this doesn't prevent us (or you!) from adding convenience functions!

You can write your own set of convenience functions in a groovy file and run it at the beginning.

run new File("myAwesomeScript.groovy")

Traces

A trace represents a path through the state space. It can move forward and backward through the Trace and can be extended with a new transition. Traces are immutable, yet creating new traces is efficient because of structural sharing.

How can we track a trace of events?

t = new Trace(s)

What is the current state of the trace?

t.getCurrentState()

What are the enabled events in the current state?

t.getNextTransitions()

How can we "execute" an event?

t = t.add(t.getNextTransitions().first())

How can we produce a random trace?

def randTrace(t,n) {
def nt = t;
n.times { nt = nt.anyEvent() }
nt
}

Let's run it!

randTrace(t,20)

How can go back in time?

t = t.back()

How can we go forward in time?

t = t.forward()

If we go back in time, the trace keeps future states. If we change a decision in the past, the trace drops the future. It behaves in the same way your browser history does.

Evaluation

Evaluation is done by passing an instance of the interface IEvalElement to an evaluator. Each formalism has its own descendant of IEvalElement. They apply a parser to a String

How can we create an EventB formula?

f1 = "active \\/ waiting" as EventB

The escaping of the backslash is unfortunatly required because the formula is contained in a Java String.

And how do we create a classical B formula?

f2 = "active \\/ waiting" as ClassicalB

How can we evaluate the formulas for state x?

x.eval(f1)

What have we received?

x.eval(f1).getClass()

ProB's Prolog engine does not make a difference between EventB and classical B. Only the parsers are different. Event B Formulas are parsed by Rodin. Classical B formulas are parsed by ProB's parser.

Ok, we can evaluate a formula for a state. Anything else that evaluates formulas?

t.eval(f1)

Traces evaluate a formula for each state of the trace. They return a list of results.

This piece of code introduces four functions to simplify handling of formulas.
The first line overrides the & operator for Strings and allows us to conjoin two predicates as Strings, e.g., "1<4" & "x>y" evaluates to "(1<4) & (x>y)". The second line implements a function not that wraps a predicate into a negation. The third line adds an implies method to the class String. "1<2".implies("3<4") results in "(1<2) => (3<4)". The last line converts a list of predicates into a conjunction. In Groovy collect means map and inject means reduce.

Notification and UI Access

Clients can register themself to receive a notification if an animation step occured, new states were discovered or the model has changed. The client has to implement one of the Listener interfaces from the de.prob.statespace package.

ProB 2.0 was built on top of the same commands as ProB 1.0. Most of the commands are usable with only minor changes. ProB 2.0 can be extended in the same way as ProB 1.0.

To access the user interface, ProB 2.0 injects two special objects into the console, animations and api.

animations is an Instance of AnimationSelector, api is an instance of Api. The selector maintains lists of Traces and State Spaces. The trace shown in the UI is marked as the current trace. The Api object is used to load models. Most likely we will rename this class and instance in the future to something more meaningful, e.g., loader.