A Lutin Tutorial
Table of Contents
- 1 Forewords
- 2 Execute Lutin programs
-
3 The Language
- 3.1 Back to programs of Section 1
- 3.2 Non deterministic programs
- 3.3 Non deterministic programs (cont)
- 3.4 Non deterministic programs (cont)
- 3.5 Non deterministic programs (cont)
- 3.6 Controlled non-determinism: the choice operator
- 3.7 Controlled non-determinism: the choice operator
- 3.8 Local variables
- 3.9 Local variables again
-
3.10 Distribute a constraint into a scope:
assert - 3.11 External code
- 3.12 Combinators
- 3.13 Combinators (cont)
- 3.14 Combinators (cont)
- 3.15 Exceptions
- 3.16 Exceptions (cont)
- 3.17 Exceptions (cont)
- 3.18 About exceptions
-
3.19 Parallelism:
&>
-
4 A new operator
run/in - 5 Advanced examples
1 Forewords
1.1 Motivations: testing reactive programs
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
-
Plus
1.3 In order to run this tutorial
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.
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
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
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"
- 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.
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.
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.
3.6 Controlled non-determinism: the choice operator
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
- Question: modify the previous program so that x reaches the target after a damped oscillation
like in the following screen-shot:
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 Xconstraint all the time. Indeed in Lutin, if one says nothing about a variable, it is chosen randomly. -
The
assert/inconstruct 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>.
-
Question : Rewrite the truesinceninstants2.lut using the
assert/inconstruct and avoid code duplication.
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
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
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:
-
aandbbe uncontrollable variables (e.g., inputs or memories) -
xandyshould be controllable variables (e.g., outputs or locals) -
in the scope of such a
run/in,xandybecomes 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/inconstruct 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 ofcompute_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 } )
<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).
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'
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
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 }
(**************************************************************************) (* 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