UP | HOME

A Lutin Tutorial

Table of Contents

1 Forewords

1.1 Motivations: testing reactive programs

./figs/demarche-en.png

Lutin aims at facilitating the writing of SUT environments, seen as non-deterministic reactive machines.

1.2 Lutin in one slide

  • Lustre-like: Dataflow, parallelism, modular, logic time, pre.
  • But not exactly Lustre though
    • Plus
      • Control structure operators (regular expressions)
      • Stochastic (controlled and pseudo-Aleatory)
    • Minus
      • No implicit top-level loop
      • No topological sort of equations

1.3 In order to run this tutorial

  • You will need to install
  • you'll find on the Verimag website

2 Execute Lutin programs

2.1 Stimulate Lutin programs

Before learning the language

The Lutin program command-line interpreter is a stand-alone executable named lutin. You should be able to find it There. From now on, we suppose that lutin is accessible from your path. html.

2.1.1 A program that increments its input

Let's consider the following Lutin program named incr.lut.

node incr(x:int) returns (y:int) = 
     loop [10] y = x+1 

<prompt> lutin incr.lut 

Opening file /home/jahier/lurette/doc/lutin-tuto/incr.lut 
# This is lutin Version 1.54 (9e783b6)
# The random engine was initialized with the seed 817281206
#inputs "x":int 
#outputs "y":int 
#step 1

This is a reactive program that returns its input, incremented by 1, forever. We'll explain why later. For the moment, let's simply execute it using lutin from a shell:

At this stage, lutin waits for an integer input to continue. If we feed it with a 1 (type 1<enter>), it returns 2. If we enter 41, it returns 42. To quit this infinite loop gently, just enter the character q.

<promt> lutin incr.lut
$ 1
1 #outs 2 
#step 2
$ 41
41 #outs 42
#step 3
$ q
q# bye!
#end. 

2.1.2 A program with no input

Consider the one.lut program:

node one() returns (y:int) = 
   loop y = 1

<prompt> lutin -l 5 one.lut 

Opening file /home/jahier/lurette/doc/lutin-tuto/one.lut 
# This is lutin Version 1.54 (9e783b6)
# The random engine was initialized with the seed 931220738
#inputs 
#outputs "y":int 
#step 1
#outs 1 
#step 2
#outs 1 
#step 3
#outs 1 
#step 4
#outs 1 
#step 5
#outs 1 

A Lutin program might have no input at all. In such a case, it might be to helpful to know that the --max-steps (-l for short) allows one to set a maximum number of simulation steps to perform. Try launch lutin one.lut -l 5 on one.lut:

2.1.3 Be quiet

<prompt> lutin -l 5 -quiet one.lut 

1 
1 
1 
1 
1 

The previous simulation basically produces a (wordy) sequence of five "1". To obtain quieter sequence, one can use the -quiet option (-q for short):

2.1.4 Stimulate Lutin programs graphically with luciole

<prompt> lutin -luciole incr.lut 

It is also possible to feed Lutin programs with luciole, a tcl/tk based GUI that was originally crafted for Lustre programs. You will need to have tcl/tk installed to be able to use it.

./Screenshot-Luciole-incr.jpg

The use of Luciole is straightforward. Some useful features that you should try to play with are:

  • The "Compose" mode (accessible via the "Clocks" menu)
  • The "Real Time Clock" mode (accessible via the "Clocks" menu)
  • The Sim2chro display (accessible via the "Tools" menu)

2.2 Store and Display the produced data: sim2chro and gnuplot-rif

node N() returns (y:int) = 
   y = 0 fby loop y = pre y+1

2.2.1 Generate a RIF file

It is possible to store the lutin RIF output into a file using the -rif option.

<prompt> lutin -l 10 -rif ten.rif N.lut ; ls -lh ten.rif 

Produces the ten.rif file N.lut.

2.2.2 Visualize a RIF file

<prompt> cat ten.rif 

The #outs, #in, etc., produced by lutin are RIF directives. RIF stands for "Reactive Input Format"

2.2.3 Visualize a RIF file (bis)

<prompt> cat ten.rif | sim2chrogtk -ecran > /dev/null 

In order to graphically display the content of this .rif file, one can use two tools that are part of the lutin distribution: sim2chro (or sim2chrogtk), and gnuplot-rif (requires gnuplot).

2.2.4 Visualize a RIF file (ter)

<prompt> gnuplot-rif ten.rif 

./png/Screenshot-Gnuplot.png

2.3 Automatic stimulation of Lutin programs

node incr(x:int) returns (y:int) = 
     loop y = x+1 
node decr(y:int) returns (x:int) = 
     x = 42 fby loop x = y-1

<prompt> lurettetop -l 20 -rp "sut:lutin:incr.lut:incr" -rp "env:lutin:decr.lut:decr" 

./png/chronogrammes.png

geluck-echec.jpg

  • I've bought 2 electronic chess games
  • connected one to another
  • And now I have peace

3 The Language

The aim of this tutorial is to be complementary to the Reference Manual. The idea here is to present the language via examples. If you want precise definitions of the various language statements syntax and semantics

3.1 Back to programs of Section 1

3.1.1 Let's come back to the Lutin programs mentioned so far.

node incr(x:int) returns (y:int) = 
     loop [10] y = x+1 

3.1.2 We said said That the first one, incr.lut increments

its input by one. Let's explain why.

3.1.3 Those programs illustrate the 2 kinds of expressions we have in Lutin.

  • constraint expressions (y = x+1) that asserts facts outputs variables.
  • trace expression (loop <te>) that allows one to combine constraint expressions.

The behavior of a satisfiable constraint expression is to produce as outputs one of its possible solutions during one logical instant (i.e.

3.2 Non deterministic programs

3.2.1 A First non-deterministic program

node trivial() returns(x:int; f:real ; b:bool) =
  loop true

<prompt> lutin -l 10 -q trivial.lut 

<prompt>  lutin -l 10 -q trivial.lut
129787787 89770690.47 f 
-202124065 60018934.21 t 
-74063075 -224861409.44 t 
-120351222 37052102.10 t 
-84618074 238667445.54 t 
186689955 -188684349.70 t 
211369478 -253941536.69 t 
-135268511 52434720.53 t 
113139954 241863809.02 t 
-114970522 -70907151.75 t 

The programs we've seen so far were deterministic because all the constraint expression were affectations (i.e., equalities between an input and an output variables). As a matter of fact, writing non-deterministic is even simpler: you just need to write no constraint at all! Indeed, observe how this trivial.lut program behaves:

3.2.2 It is possible to set the variable range at declaration time,

as done in trivial2.lut:

node trivial() returns(x:int  [-100;100]; 
                       f:real [-100.0;100.0];b:bool)=  
   loop true


<prompt> lutin -l 10 -q trivial2.lut 

71 42.08 f 
-61 27.28 f 
-49 -64.96 f 
44 63.08 t 
-15 73.06 f 
0 -74.39 f
87 -1.58 f 
-20 89.62 f 
3 -84.20 t 
89 28.64 f 

3.3 Non deterministic programs (cont)

As you've just seen, a Lutin program is by default chaotic. To make it less chaotic, one has to add constraints which narrow the set of possible behaviors. When only one behavior is possible, the program is said to be deterministic. When no behavior is possible, the program stops.

Now consider the range.lut program:

node range() returns (y:int) = loop 0 <= y and y <= 42

<prompt> lutin range.lut -l 10 -q 

The constraint expression 0 <= y and y <= 42 has several solutions. One of those solutions will be drawn and used to produce the output of the current step.

<prompt> lutin range.lut -l 10 -q
24 
38 
12 
30 
5 
5 
40 
8 
26 
39 

Lutin constraints can only be linear, which means that the set of solutions is a union of convex polyhedra. Several heuristics are defined to perform the solution draw, that are controlled by the following options:

  • --step-inside (-si): draw inside the polyhedra (the default)
  • --step-vertices (-sv) draw among the polyhedra vertices
  • --step-edges (-se): promote edges
<prompt> lutin range.lut -l 10 -q --step-vertices 

42 
0 
42 
42 
0 
42 
42 
0 
0 
42 

the same.

Answer

3.4 Non deterministic programs (cont)

  • A 3D non-deterministic example

Of course constraints can be more complex

node polyhedron() returns(a,b,c:real) =
   loop (0.0 < c and c  < 4.0 and 
         a + 1.0 * b > 0.0 and 
         a + 1.0 * b - 3.0 < 0.0 and 
         a - 1.0 * b < 0.0 and 
         a - 1.0 * b + 3.0 > 0.0)


<prompt> lutin polyhedron.lut -l 1000 -q > poly.data;\\ 
echo 'set point 0.2;set term wxt persist;splot "poly.data" using 1:2:3'| gnuplot 

However, constraint expressions should be linear. For example, x * x >0 is rejected by the current solver. But of course, x * y > 0 is ok if x (or y) is an input.

gnuplot-rif is not designed to display 3d data; however, one can generate and visualize data generated by this program using the 3D plotting facilities of gnuplot like that:

  • One can observe the effect of –step-edges

and –step-vertices ) options on the repartition of generated points

<prompt> lutin polyhedron.lut -l 1000 -quiet -se > poly-se.data 
<prompt> lutin polyhedron.lut -l 1000 -quiet -sv > poly-sv.data 
<prompt> echo 'set pointsize 0.2 ; set terminal wxt persist; splot "poly-se.data" using 1:2:3'| gnuplot
<prompt> echo 'set pointsize 0.8 ; set terminal wxt persist; splot "poly-sv.data" using 1:2:3'| gnuplot

hint: use the mouse inside the gnuplot window to change the perspective.

./png/Screenshot-Gnuplot-se.png

3.5 Non deterministic programs (cont)

Constraint may also depend on inputs. Try to play the range-bis.lut program:

node range_bis(i:int) returns (y:int) = 
   loop 0 <= y and y <= i

<prompt> lutin range-bis.lut -luciole 

The major difference with range.lut is that the constraint expression 0 <= y and y <= i is not always satisfiable. If one enters a negative value, that program will stop.

Question: modify the range-bis.lut program (with the concept introduced so far) so that when a negative input is provided, it returns -1.

Answer

3.6 Controlled non-determinism: the choice operator

choice.lut

node choice() returns(x:int) =
   loop {
      | x = 42
      | x = 1
}

<prompt> lutin -l 10 -q choice.lut 

42 
42 
42 
1 
42 
42 
42 
42 
1 
1 

In the previous programs, it is not possible to control the non-determinism. It is possible to change the drawing heuristics, but that's all. In order to control the non-determinism, one as to use the choice operator (|).

When executing the choice.lut, the Lutin interpreter performs a fair choice among the 2 satisfiable constraints (x = 42 and x = 1).

It is possible to favor one branch over the other using weight directives (:3):

node choice() returns(x:int) =
   loop {
      |3: x = 42
      |1: x = 1
}

In choice2.lut, x=42 is chosen with a probability of 3/4.

<prompt> lutin -l 10000 -q choice2.lut | grep 42 | wc -l 

7429

nb: "|" is a shortcut for "|1:".

nb 2: having 3 choices with a weight of 1, that does not necessarily means that each branch is chosen with a probability of 1/3. Indeed, the choice in done among satisfiable constraints. For instance in choice3.lut below, not all the branches of the alternative can be satisfiable.

3.7 Controlled non-determinism: the choice operator

node choice3(b:bool) returns(x:int) =
   loop {
      | x = 1 and b 
      | x = 2 and b 
      | x = 3 and not b
}

<prompt> lutin -luciole choice3.lut 

3.8 Local variables

Sometimes, it is useful to use auxiliary variables that are not output variables. Such variables can be declared using the exist/in construct. Its use is illustrated in the truesinceninstants.lut program:

let n = 3
node ok_since_n_instants(b:bool) returns (res:bool) =
  exist cpt: int = n in 
     loop { 
       cpt = (if b then (pre cpt-1) else n) and 
       res = (b and (cpt <= 0)) 
     }

<prompt> lutin true\_since\_n\_instants.lut -luciole 

It is possible to set its previous value at declaration time as for interface variables. Here, the previous value of local variable cpt is set to the constant n, which is bound to 3. This local variable is used to count the number of consecutive instants where the input b is true. cpt is reset to n each time b is false, and decremented otherwise. The node returns true when cpt is smaller or equal than 0.

3.9 Local variables again

The previous example was deterministic (it was actually a Lustre program with an explicit loop), the local variable was a simple (de)counter.

Local variables can also plain random variables, as illustrated the local.lut program:

node local() returns(x:real = 0.0) =
  exist target : real in 
  loop {
    0.0 < target and target < 42.0 and x = pre x 
    fby
    loop [20] { x = (pre x + target) / 2.0 and 
                target = pre target }
  }

At first step, the local variable target is chosen randomly between 0.0 and 42.0, and x keeps its previous value (0); then, during 20 steps, the output x comes closer and closer to target (x = (pre x + target) / 2.0), while target keeps its previous value. After 20 steps (loop [20])), another value for target is chosen, and so on so forth (because of the outer loop).

<prompt> lutin local.lut -l 100 -rif local.rif ; gnuplot-rif local.rif 

png/Screenshot-Gnuplot-local.png

  • Question: modify the previous program so that x reaches the target after a damped oscillation

like in the following screen-shot:

png/Screenshot-Gnuplot-localbis.png

Answer

3.10 Distribute a constraint into a scope: assert

Now let's consider a slightly different version of the previous program where n is an input of the node. Since the current version of the Lutin interpreter is not able to set the previous value of a variable with an input value (this restriction might change in the future), we need to write for the first instant a constraint that does not involve pre cpt.

Consider for instance the truesinceninstants2.lut program:

node ok_since_n_instants(b:bool;n:int)returns(res:bool)=  
  exist cpt: int in
  cpt = n and res = (b and (cpt <= 0)) 
  fby 
     loop { 
       cpt = (if b then (pre cpt-1) else n) and 
       res = (b and (cpt <= 0))  
     }

  • One flaw is that res = (b and (cpt<=0)) is duplicated.

    This occurs very often, for example when you want to a variable to keep its previous value during several steps, and need to write boring X = pre X constraint all the time. Indeed in Lutin, if one says nothing about a variable, it is chosen randomly.

  • The assert/in construct has been introduced in Lutin to avoid such code duplication.
  • assert <ce> in <te><te'>,

    where <te'>= <te>[c/c and ce]\(_{\forall c \in \mathcal{C}onstraints(te)}\)

i.e., where the trace expression <te'> is obtained from the trace expression <te> by substituting all the constraint expressions <c> appearing in <te> by the constraint expression <c> and <ce>.

3.11 External code

3.11.1 Lutin program can call any function defined in a shared library

extern sin(x: real) : real
let abs(x:real) : real = if x < 0.0 then -x else x
node bizzare() returns (res: real) = 
  exist x,eps: real in
  res = 0.0 and eps = 0.0 fby 
  loop     abs(eps - pre eps) < .02
       and x = pre x + 0.5 + pre eps
       and res = sin(pre x)

<prompt> lutin -L libm.so -l 200 ext-call.lut -rif f.rif;gnuplot-rif f.rif 

3.12 Combinators

3.12.1 A combinator is a well-typed macro that eases code reuse.

One can define a combinator with the let/in statement, or just let for top-level combinators.

3.12.2 A simple combinator

The combinator.lut program illustrates the use of combinators:

let within(x, min, max: int): bool = 
  (min <= x) and (x <= max)

node random_walk() returns (y:int) =
  within(y,0,100) fby loop within(y,pre y-1,pre y+1)

<prompt> lutin -l 100 combinator.lut -rif walk.rif ; gnuplot-rif walk.rif 

png/Screenshot-walk.png

3.13 Combinators (cont)

3.13.1 A combinator that needs memory

If one wants to access to the previous value of a variable inside a combinator, one has to declare in the combinator profile that this variable is a reference using the ref keyword, as illustrated in the up-and-down.lut program:

let within(x, min, max: real): bool =  
    (min <= x) and (x <= max)
let up  (delta:real; x : real ref) : bool =    
    within(x, pre x, pre x + delta)
let down(delta:real; x : real ref) : bool = 
    within(x, pre x - delta, pre x)
node up_and_down(min,max,d:real) returns (x:real) =
   within(x, min, max) fby
   loop {
        | loop { up  (d, x) and pre x < max } 
        | loop { down(d, x) and pre x > min } 
       }

<prompt> lutin -luciole up-and-down.lut 

The combinator up (reps down) constraints the variable x to be between its previous value and its previous value plus (resp minus) a positive delta. The node up-and-down, after an initialization step where x is drawn between min and max, chooses (fairly) to go up, or to go down. It it chooses to go up, it does so as long as x is smaller than max; then it goes down, until min, and so on.

Question: what happens if you guard the up combinator by x<max instead of pre x < max?

3.14 Combinators (cont)

3.14.1 Trace Combinators

let myloop(t:trace) : trace = loop try loop t

Here we restart the loop from the beginning whenever we are blocked somewhere inside t. (myloop.lut)

let myloop(t:trace) : trace = loop try loop t
node use_myloop(reset:bool) returns(x:int) =
  myloop(
      x = 0 fby 
      assert not reset in
      x = 1 fby 
      x = 2 fby
      x = 3 fby
      x = 4
  )

<prompt> lutin -luciole myloop.lut 

Each step you set reset to true, the output equals to 0.

3.15 Exceptions

3.15.1 Global exceptions can be declared outside the main node:

exception ident

3.15.2 or locally within a trace statement:

exception ident in st

3.15.3 An existing exception ident can be raised with the statement:

raise ident

3.15.4 An exception can be caught with the statement:

catch ident in st1 do st2

If the exception is raised in st1, the control immediatelly passes to st2. If the "do" part is omitted, the statement terminates normally.

3.16 Exceptions (cont)

  • The predefined Deadlock exception can only be catched
catch Deadlock in st1 do st2

try st1 do st2

  • When a trace expression deadlocks, the Deadlock exception is raised. In fact, this exception is internal and cannot be redefined nor raised by the user. The only possible use of the Deadlock in programs is one try to catch it:
  • If a deadlock is raised during the execution of st1, the control passes immediately to st2. If st1 terminates normally, the whole statement terminates and the control passes to the sequel.

3.17 Exceptions (cont)

node toto(i:int) returns (x:int)=
  loop {
    exception Stop in
    catch Stop in
       loop [1,10] x = i fby raise Stop fby x = 43
    do x=42
   }

<prompt> lutin except.lut -luciole 

except.lut

Note that the 43 value is never generated (if i<>43 of course).

3.18 About exceptions

  • Very (too?) powerful mechanism
  • Can be used to build complex trace operators
  • But should they used to program?

3.19 Parallelism: &>

node n() returns(x,y:int) = {
      loop { -10 < x and x < 10 }
   &> y = 0 fby loop { y = pre x } } 

<prompt> lutin -luciole paralel.lut 

<te1> &> <te2> executes the trace expression <te1> and <te2> in parallel.

&>st1 &>... &>stn where the first &> can be omitted. This statement executes in parallel all the statements st1, …, stn. All along the parallel execution each branch produces its own constraint; the conjunction of these local constraints gives the global constraint. If one branch terminates normally, the other branches continue. The whole statement terminates when all branches have terminated.

nota bene: this construct can be expensive because of:

  • the control structure: such a product is equivalent to an automata product, which, in the worst case, can be quadratic;
  • the data: the polyhedron resolution is exponential in the dimension of the polyhedron.

Use the run/in construct instead if performance is a problem.

4 A new operator run/in

4.1 Cheap parallelism: Calling Lutin nodes run/in

The idea is the following: when one writes:

run (x,y) := foo(a,b) in

in order to be accepted the following rules must hold:

  • a and b be uncontrollable variables (e.g., inputs or memories)
  • x and y should be controllable variables (e.g., outputs or locals)
  • in the scope of such a run/in, x and y becomes uncontrollable.

nb : it is exactly the parallelism of Lustre, with an heavier syntax. In Lustre, one would simply write

(x,y)=foo(a,b);

Moreover in Lutin, the order of equations matters.

4.2 Cheap parallelism: Calling Lutin nodes run/in

  • The run/in construct is another (cheaper) way of executing code in parallel
  • The only way of calling Lutin nodes.
  • Less powerful: constraints are not merged, but solved in sequence
include "N.lut"
include "incr.lut"
node use_run() returns(x:int) =
  exist a,b : int in
  run a := N() in
  run b := incr(a) in
  run x := incr(b) in
    loop true

<prompt> lutin -l 5 -q run.lut -m use\_run 

2 
3 
4 
5 
6 

The program run.lut illustrates the use of the run/in statements: This program uses nodes defined in N.lut and incr.lut.

Another illustration of the use of run can be found in the Wearing sensors exemple.

4.3 Why does the run/in statement makes Lutin usable?

Using combinators and &>, it was already possible to reuse code, but run/in is

  • Much more efficient: polyhedra dimension is smaller
  • Mode-free (args can be in or out) combinators are error-prone

5 Advanced examples

5.1 Wearing sensors

The sensors.lut program that makes extensive use fo the run statements.

-- Simulate sensors that wears out
node temp_simu_alt (T:real) returns (Ts:real) =
  exist eps : real [-0.1;0.1] in
      Ts = T + eps
   fby
      loop { 
          |10: Ts = T + eps     -- working    
          |1:  loop Ts = pre Ts -- not working    
        }

node temp_simu(T:real) returns (Ts:real) =
  exist eps : real [-0.1;0.1] in
       Ts = T + eps
   fby loop [30,50] Ts = T + eps -- working
   fby loop         Ts = pre Ts  -- not working  

node main(Heat_on : bool) returns (T, T1, T2, T3 : real; T1v,T2v,T3v:bool) = 
  let delta = 0.2 in
  exist T1_cst, T2_cst, T3_cst : bool = false in
  loop {
    -- init
        T= 7.0 and T1 = T and T2 = T and T3 = T and
        T1v and T2v and T3v
    fby
      let newT = pre T + (if Heat_on then delta else -delta) in
      assert T = newT in
      assert (T1v = (T1 = pre T1)) in
      assert (T2v = (T2 = pre T2)) in
      assert (T3v = (T3 = pre T3)) in

      run T1 := temp_simu(newT) in
      run T2 := temp_simu(newT) in
      run T3 := temp_simu(newT) in
          loop { not (T1 = pre T1 and T2 = pre T2 and T3 = pre T3) }
         -- force to start again from the beginning when the 3 sensors are broken
    }

<prompt> lutin sensors.lut -m main -luciole 

<prompt> lutin sensors.lut -m main -luciole

5.2 Waiting for the stability of a signal

  • One of the target application of Lutin is the programming of test scenario of control-command system. In this context, it is often very useful to wait for the stability of a variable (typically, coming from a sensor) before changing again the value associated to a command.
  • Defining and checking the stability of a variable (in particular in presence of noise) is not that easy.
    • One definition could be that a variable is stable if it remains within an interval during a certain amount of time. More precisely:

A variable V is (s,n)-stable if there exists an interval I of size s, and an instant i such that, for all x in [i,i+n], V(x) is in I.

∃ i, ∃ I, st |I| = s, ∀ x ∈ [i, i+n] x(n) ∈ I

  • In order to implement such a definition, we need to manage n variables; and in order to get parametric code, we'd need arrays. In any case, this is quite expensive (O(n)). Here we propose a lighter version of this test, that is based on the following idea: we test for the stability of the variable during a small amount of instants and hence obtain an interval I; then, we simply check that the variable remains in I for the n-m+1 remaining instants. This is less expensive (O(m) versus O(n)), but of course it is not optimal in the sense that in some cases, we might find the stability a little bit later.
  • We now propose a Lutin and a Lustre implementation of this idea. The Lutin program is fully deterministic, but interestingly enough, one might find it easier (or not…) to read than the Lustre equivalent program. This is because Lustre is a purely dataflow programming language, while Lutin do have explicit control flow operators (fby, loop). In the Lustre version of compute_ref, we need to encode a 2-states automaton. Such an encoding makes the temporal behavior of the program less explicit.

5.2.1 The Lutin version

We now paraphrase the isstable.lut Lutin version of this algorithm.

First we define a few constants:

let small_delay = 3
let big_delay = 5
let epsilon = 3.0

and a few macros:

let min(x, y : real) : real = if x < y then x else y
let max(x, y : real) : real = if x > y then x else y
let myloop(t:trace) : trace = loop try loop t

Then we define the node compute_ref that search for (epsilon, small_delay)-stability. This node returns a value of reference vref that is equal to the middle of I of a suitable. It also returns a boolean valid that becomes true as soon as v is (epsilon, small_delay)-stable, and that remains true as long as v remains in I. Note that I is re-initialiazed as soon as it becomes bigger that epsilon.

node compute_ref(v: real) returns (vref: real; valid:bool) =
  exist vmin, vmax, diff : real in
  let init = (vmin = v) and (vmax = v) and (vref = v) and (diff = 0.0) and not valid in
  let update =
            vmin = min(v, pre vmin)
       and  vmax = max(v, pre vmax)
       and (vref = ((vmax + vmin) / 2.0))
       and (diff = ((vmax - vmin) / 2.0))
  in 
   myloop (
     init fby
     assert update and diff <= epsilon in
       loop [small_delay] not valid  fby
       loop valid
   )


The node is_stable rests on the compute_ref node. When valid becomes true, it waits big_delay instants, and returns true as long as valid remains true.

node is_stable(v:real) returns (res:bool) =
  exist vref : real in
  exist valid : bool in
  run vref, valid := compute_ref(v) in

  myloop (
     loop { not valid and not res } fby
     loop [big_delay] { valid and not res } fby  
     strong loop      { valid and     res } 
  )

The Lutin version

<prompt> lutin -luciole is\_stable.lut -m is\_stable 

5.2.2 The Lustre version

Now, let's write the same thing in Lustre, just for the fun of it (and also because the notion of stability can be useful when writing test oracles, that we traditionally write in Lustre).

The Lustre version

5.3 The Crazzy rabbit

  • The rabbit serves as an environment for a caml program

that displays its position in a graphical windows

  • The rabbit remains in its field, and avoids a moving obstable
  • The rabbit changes it speed an trajectory from times to times
<prompt> lurettetop -rp "sut:ocaml:rabbit.cmxs:" -rp 'env:lutin:rabbit.lut:-main:rabbit:-L:libm.so:-loc' 

crazy-rabbit/ud.lut

let between(x, min, max : real) : bool = ((min < x) and (x < max))

node up(init, delta:real) returns( x : real) = 
   x = init fby loop { between(x, pre x, pre x + delta) }

node down(init, delta:real) returns( x : real) = 
   x = init fby loop { between(x, pre x - delta, pre x) }

node up_and_down(min, max, delta : real) returns (x : real) =   
   between(x, min, max)
 fby
   loop {
        | run x := up(pre x, delta)   in loop { x < max }
        | run x := down(pre x, delta) in loop { x > min }
       }

node main () returns (x :  real) =
    run x:= up_and_down(0.0, 100.0, 5.0)

crazy-rabbit/moving-obstacle.lut

crazy-rabbit/rabbit.lut

include "ud.lut"
include "moving-obstacle.lut"

node rabbit_speed (low, high:real) returns (Speed: real) =
   exist Delta, SpeedLow, SpeedHigh: real in
   let draw_params() =
      between(Delta, 0.5, 1.0) and
      between(SpeedLow, 0.0, low) and between(SpeedHigh, 1.0, high)
    in
    let keep_params() = 
       Delta = pre Delta and SpeedLow = pre SpeedLow and 
       SpeedHigh = pre SpeedHigh
    in
       {
          &> loop { draw_params() fby loop ~100: 10 { keep_params() } } 
          &> Speed = 1.0 fby 
             run Speed := up_and_down(pre SpeedLow, pre SpeedHigh, pre Delta) 
       }
extern sin(x:  real) : real
extern cos(x:  real) : real

-- extern printint(i:int):unit 

exception Pb

node rabbit (x_min, x_max, y_min, y_max : real)
returns(x, y, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y:  real ; freeze:bool) =
   exist Speed, Alpha, Beta : real in
   let keep_position() = (x = pre x and y = pre y) in
   let draw_params() = between(Alpha, -3.14, 3.14) and between(Beta, -0.3, 0.3)
   in
   -- The beginning
   run Speed := rabbit_speed(5.0, 50.0) in
   run p1x,p1y, p2x,p2y, p3x,p3y, p4x,p4y := obstacle(x_min, x_max, y_min, y_max) in
   let line() =
     x = (pre x + Speed * cos(pre Alpha)) and
     y = (pre y + Speed * sin(pre Alpha)) and
     Alpha = pre Alpha and
    -- And he always avoids the obstacle
     not is_inside(x,y,p1x,p1y,p2x,p2y,p3x,p3y,p4x,p4y)
   in
   let escape () = 
      try
         between(x, pre x - 21.0, pre x + 21.) and
         between(y, pre y - 21.0, pre y + 21.) and
         not is_inside(x,y,p1x,p1y,p2x,p2y,p3x,p3y,p4x,p4y)
     do raise Pb 
   in
   let curve() =
     x = (pre x + Speed * cos(pre Alpha)) and
     y = (pre y + Speed * sin(pre Alpha)) and
     Alpha = pre Alpha - Beta and Beta = pre Beta and
     not is_inside(x,y,p1x,p1y,p2x,p2y,p3x,p3y,p4x,p4y)
   in
   let spiral() =
     x = (pre x + Speed * cos(pre Alpha)) and
     y = (pre y + Speed * sin(pre Alpha)) and
     Alpha = pre Alpha - Beta and Beta = pre Beta- 0.02 and
     not is_inside(x,y,p1x,p1y,p2x,p2y,p3x,p3y,p4x,p4y)
   in
   assert
       -- The rabbit always remains in its playground
        between(x, x_min, x_max) and
        between(y, y_min, y_max)
   in
   assert (freeze = is_inside(x,y,p1x,p1y,p2x,p2y,p3x,p3y,p4x,p4y)) in
      draw_params() fby
      try loop {
--         try
                { draw_params() &> escape() } fby
--                { draw_params() and keep_position() } fby
                {
                  |1: loop [0,40] line()  -- forward straigth ahead for a while
                  |3: loop [0,40] curve() -- forward by turning for a while
                  |3: loop [0,60] spiral() -- forward by turning for a while

                }
--          do escape() 
        -- nb: if it is inside the obstacle, it keeps its position until it can move
     } 



crazy-rabbit/rabbit.ml

(**************************************************************************)
(* Utilities *)

let draw x y =
(*   if Sys.argv.(3) = "dot" then *)
(*     Graphics.plot (truncate x) (truncate y) *)
(*   else *)
    Graphics.lineto (truncate x) (truncate y)

open Data
let get_float var sol =  
  match (try List.assoc var sol with Not_found -> assert false) 
  with (F f) -> f | _ -> 
    Printf.printf "*** error when parsing float\n";
    flush stdout;
    assert false

let get_bool var sol =  
  match (try List.assoc var sol with Not_found -> assert false) 
  with (B b) -> b | _ -> 
    Printf.printf "*** error when parsing bool\n";
    flush stdout;
    assert false


(**************************************************************************)
let _ = 
  Graphics.open_graph " ";  Graphics.clear_graph ();  
  Graphics.set_window_title "The Crazy Rabbit travel - test";  
  Graphics.moveto 100 100
(*   ignore (Graphics.read_key ()) *)
;;

(**************************************************************************)

let init  _ = ()
let kill  _ = ()

type var_type = string

let (inputs :(string * string) list) = [
  "x", "real";  
  "y", "real";  
  "p1x", "real";  
  "p1y", "real";  
  "p2x", "real";  
  "p2y", "real";  
  "p3x", "real";  
  "p3y", "real";  
  "p4x", "real";  
  "p4y", "real";
  "freeze", "bool"
]

let mems_i = []

let mems_o = [
  "x_min", Data.F 0.0;  "x_max", Data.F 100.0;  
  "y_min", Data.F 0.0;  "y_max", Data.F 100.0
]

let (outputs :(string * string) list) = 
  [
    "x_min", "real";  "x_max", "real";  
    "y_min", "real";  "y_max", "real"
  ]

let cross_product(ux,uy,vx,vy)  = (ux*.vy-.uy*.vx);;


let image = ref (Graphics.get_image 0 0 (Graphics.size_x ()) (Graphics.size_y ()))


let cross_product(ux,uy,vx,vy) = (ux*.vy-.uy*.vx)
let is_inside(px,py,p1x,p1y,p2x,p2y,p3x,p3y,p4x,p4y) = 
  let p1p_x = px-.p1x in
  let p1p_y = py-.p1y in
  let p2p_x = px-.p2x in
  let p2p_y = py-.p2y in
  let p3p_x = px-.p3x in
  let p3p_y = py-.p3y in
  let p4p_x = px-.p4x in
  let p4p_y = py-.p4y in
  let p2p1_x = p1x-.p2x in
  let p2p1_y = p1y-.p2y in
  let p1p4_x = p4x-.p1x in
  let p1p4_y = p4y-.p1y in
  let p4p3_x = p3x-.p4x in
  let p4p3_y = p3y-.p4y in
  let p3p2_x = p2x-.p3x in
  let p3p2_y = p2y-.p3y in
    cross_product(p2p1_x, p2p1_y, p2p_x, p2p_y) < 0.0 &&
    cross_product(p1p4_x, p1p4_y, p1p_x, p1p_y) < 0.0 &&
    cross_product(p4p3_x, p4p3_y, p4p_x, p4p_y) < 0.0 &&
    cross_product(p3p2_x, p3p2_y, p3p_x, p3p_y) < 0.0   

let (step :  Data.subst list -> Data.subst list)=
  fun rabbit_outs -> 
    let x_min = (F (float_of_int 0))
    and x_max = (F (float_of_int (Graphics.size_x ())))
    and y_min = (F (float_of_int 0))
    and y_max = (F (float_of_int (Graphics.size_y ()))) in
    let bounds = [("x_min", x_min);("x_max",x_max);("y_min",y_min);("y_max",y_max)] in 


    (* Drawing the new point onto the Graphics window *)
    let x = get_float "x" rabbit_outs
    and y = get_float "y" rabbit_outs
    and p1x = get_float "p1x" rabbit_outs
    and p2x = get_float "p2x" rabbit_outs
    and p3x = get_float "p3x" rabbit_outs
    and p4x = get_float "p4x" rabbit_outs
    and p1y = get_float "p1y" rabbit_outs
    and p2y = get_float "p2y" rabbit_outs
    and p3y = get_float "p3y" rabbit_outs
    and p4y = get_float "p4y" rabbit_outs
    and freeze = get_bool "freeze" rabbit_outs
    in
      if 
        (is_inside(x,y,p1x,p1y,p2x,p2y,p3x,p3y,p4x,p4y))
      then
        (
          ignore (Graphics.read_key ())
        );

    let p1xI = truncate p1x
    and p1yI = truncate p1y
    and p2xI = truncate p2x
    and p2yI = truncate p2y
    and p3xI = truncate p3x
    and p3yI = truncate p3y
    and p4xI = truncate p4x
    and p4yI = truncate p4y
    in
    let point_list = [(p1xI,p1yI);(p2xI,p2yI);(p3xI,p3yI);(p4xI,p4yI)] in 
    let poly = Array.of_list point_list in



      Graphics.draw_image !image 0 0;
      draw x y; 
      image := Graphics.get_image 0 0 (Graphics.size_x ()) (Graphics.size_y ());
      if freeze then (
        Graphics.set_color (Graphics.rgb 255 0 100);
        Graphics.fill_poly poly
      )
      else (
        Graphics.set_color (Graphics.rgb 0 0 0);
        Graphics.draw_poly poly
      );      
      bounds

let _ = 
  OcamlRM.add_inputs "rabbit.cmxs" inputs;
  OcamlRM.add_outputs "rabbit.cmxs" outputs;
  OcamlRM.add_kill "rabbit.cmxs" kill;
  OcamlRM.add_step "rabbit.cmxs" step;
  OcamlRM.add_mems "rabbit.cmxs" mems_i mems_o



Date: 2011-12-08 11:38:34 CET

Author: Erwan Jahier

Org version 7.5 with Emacs version 23

Validate XHTML 1.0