x

Université Pierre & Marie Curie Maîtrise d'informatique

Programmation
Spécification et certification du logiciel
Notes de cours

P. Manoury

2000-01








1  Spécifications algébriques

Tout programmeur sait ce qu'est une spécification et tout programmeur a manipulé des spécifications. Celles-ci sont, en géneral, réduite àla simple expression de nom de fonctions accompagnées de leur type. Ces spécifiactions sont rassemblées dans des fichiers particuliers dits d'interface.

Par exemple, en faisant abstraction de certaines scories (ci-dessous le __P), on trouve, dans stdio.h les déclarations suivantes :
extern int getc __P ((FILE *));
extern int getchar __P ((void));
extern char* gets __P ((char*));
/* etc ... */
Ces trois déclarations régissent l'emploi des fonctions correpondantes et donnent une indication sur leur rôle. L'information contenue dans ces déclaration est une information de type. Par exemple, le fonction getc appliquée àun descripteur de fichier (argument de type FILE *) a pour valeur un entier (type int).

D'autres langages utilisent un langage de type différent qui s'apparente, extérieurement, aux formules du calcul propositionnel. Par exemple, on trouvera dans le fichier d'interface du module préchargé du langage O'CAML les déclarations :
val read_line : unit -> string
val read_int : unit -> int
val read_float : unit -> float
Ces trois déclarations aussi donnent trois fonctions de lecture sur l'entrée standard.



Type de données abstrait

En programmation, les fichiers d'interface sont en général organisés de façon àrassembler les informations concernant un même sujet ou un même objet. Ces unités constituent des modules.

Toujours pour rester dans le monde ML, on trouve dans la distribution d'O'CAML les fichierslist.mli, stack.mli, queue.mli qui contiennent les primitives de créations et de manipulation de structures de données linéaires usuelles : les listes, les piles, les files d'attente.

Analysons quelques éléments du fichier stack.mli :
type 'a t

val create: unit -> 'a t

val push: 'a -> 'a t -> unit

val pop: 'a t -> 'a

val clear : 'a t -> unit

val length: 'a t -> int
La première ligne que nous avons reproduite introduit le nom de type t pour les piles (Vu de l'extérieur, le nom complet est Stack.t). Dans le monde de la spécification algébrique, on utilise le terme sorte plutôt que celui de type. Le 'a de la déclaration indique que le type des piles est paramétrépar le type indéterminé (appelé 'a)de ses éléments. Cette première ligne permet d'énoncer les suivantes qui utiliseront le nom dit type introduit. Les quatre lignes suivantes donnent des fonctions privilégiées sur les piles permettant leur création ou leur destructuration. La dernière donne l'information de longueur. Ces quelques fonctions sont les seules dont nous disposions pour manipuler kes piles dans nos programmes. L'interface ne dit rien quand àla représentation des piles : le type t est déclaré sans être défini. L'interface stack.mli définit un type abstrait.

Néanmoins, avec ces seules informations, on peut écrire une expression ayant pour valeur une pile contenant les entiers 1, 2 3 et 4 : push 1 (push 2 (push 3 (push 4 (create())))). On dira aussi que l'expression dénote une valeur.

L'interface telle que nous l'avons résumée ci-dessus ne dit rien non plas quand au comportement des opérateurs déclarés en dehors de leur type. Le savoir que nous en avons vient d'ailleurs. Ici, des commentaires qui accompagnent les déclarations :
(* This module implements stacks (LIFOs), with in-place modification. *)

type 'a t
        (* The type of stacks containing elements of type ['a]. *)

val create: unit -> 'a t
        (* Return a new stack, initially empty. *)
val push: 'a -> 'a t -> unit
        (* [push x s] adds the element [x] at the top of stack [s]. *)
val pop: 'a t -> 'a
        (* [pop s] removes and returns the topmost element in stack [s],
           or raises [Empty] if the stack is empty. *)
val clear : 'a t -> unit
        (* Discard all elements from a stack. *)
val length: 'a t -> int
        (* Return the number of elements in a stack. *)
Ces commentaires sont partie intégrante de la spécification du module Stack. On les retrouvent dans la documentation qui accompagne la distribution du langage; le manuel de référence.

Ce que nous voulons faire est de donner un statut formel aux commentaires décrivant les opérateurs : remplacer l'expression en langue naturelle par une expression en langue formelle ou mathématique. Notre première approche sera celle des spécification algébrique oùles opérateurs sont décrits par des systèmes d'axiomes équationnels.



Spécifications équationnelles

Une spécification équationnelles est un ensemble d'identités de valeurs entre expressions. Par exemple, l'identité entre l'élément retourné par pop et le dernier empilés'écrit comme l'équation :

(pop (push x s)) = x

Mais cet opérateur souffre d'un grave défaut pour se préter facilement àla spécification algébrique : il fait deux choses àla fois ; il retourne l'élément en sommet de pile et le retire de la pile. La spécification algébrique se préte mal àl'expression des effets de bord, aussi considèrerons nous plutôt une description purement fonctionnelle des opérateurs. Chaque opération est unique et chaque symbole dénote une unique opération. Le symbole topdésignera l'élément en sommet de pile et popl'opération de retrait elle-même.

Sort: stack

Uses: int, elt

Symbols:
 create :
® stack
 push : elt, stack
® stack
 top : stack
® elt
 pop : stack
® stack
 clear : stack
® stack
 length : stack
® int
La figure ci-dessus décrit les premiers composants de la spécification des piles. Ces éléments constituent l'en-tête de nos spécifications. Il faut maintenant en donner les corps : les équations devant être satisfaites.



Axioms: " s:stack; x:elt

 (top (push x s))
= x

 (pop (push x s))
= s

 (length create)
= 0

 (length (push x s))
= 1+(length s)

 (length (clear s))
= 0
Les variables servant àexprimer les axiomes sont déclarées avec leur sorte derrière le mot-clé Axioms.

Il convient de faire quelques remarques sur cet ensemble d'équations.

Des listes

Voici la structue linéaire classique des listes. Nous en donnons quelques opérateurs puis nous verrons comment déduire d'autres égalités àpartir de celles posées en axiome.
Sort: list

Uses: elt, int

Symbols:
 nil :
® list
 cons : elt, list
® list
 length : list
® int
 hd : list
® elt
 tl : list
® list
 append : list, list
® list
 rev : list -> list
 rev_append : list, list -> list


Axioms: " xs, ys:list; x:elt

 (length nil)
= 0
 (length (cons x xs))
= 1+(length xs)

 (hd (cons x xs))
= x

 (tl (cons x xs))
= xs

 (append nil ys)
= ys
 (append (cons x xs) ys)
= (cons x (append xs ys))

 (rev nil)
= nil
 (rev (cons x xs))
= (append (rev xs) (cons x nil))

 (rev_append nil ys)
= ys
 (rev_append (cons x xs) ys)
= (rev_append xs (cons x ys))

Theorems: " xs, ys, zs:list

 
1. (length (append xs ys)) = (length xs) + (length ys)

 
2. (append (append xs ys) zs) = (append xs (append ys zs))

 
3. (rev_append xs ys) = (append (rev xs) ys)

 
4. (rev xs) = (rev_append xs nil)
L'égalité 4. est intéressante du point de vue implantation puisqu'elle donne un moyen d'obtenir le miroir d'une liste en utilisant une récursion terminale.

Le preuve de 1. est immédiate par induction sur la liste xs : La preuve de 2. qui donne l'associativité de l'opérateur de concaténation appendest tout aussi immédiate par induction sur xs(en exercice).

La preuve de 3. est une induction un peu plus rusée : on montre par induction sur la liste xs que " ys:list. (rev_append xs ys) = (append (rev xs) ys). La présence de la quantification dans la formule àdémontrer est primordiale car elle sera aussi présente dans l'hypothèse d'induction et en permettra l'utilisation. L'égalité 4. est une conséquence de 3.



Équations conditionnelles

Une équation conditionnelles est une expression de la forme : c1, .., cn Þ eoùles c1, .., cnsont des expressions booléennes et eune équation. Les expressions boolénnes sont appelées préconditions de l'équation e. On interprète ces sortes d'équation selon le sens usuel du connecteur propositionnel d'implication : si les conditions sont satisfaites (i.e. c1=true, .., cn=true) alors l'équation doit aussi l'être.

Les équations conditionnelles permettent des restreindre le domaine d'application de certaines équations. Par exemple, on peut spécificier un opérateur d'accès au ième éleément d'une liste en précisant le domaine de validité de l'indice :


Extends: list

Symbols:
 nth : list, int
® elt

Axioms: " xs:list; x:elt; i:int

 (nth (cons x xs) 0)
= x
 (0
< i), (i £ (length xs)) Þ (nth xs i) = (nth (tl xs) (i-1))
Remarquons que nos préconditions ont pour conséquence que la liste xsn'est pas vide (puisque qu'elle est de longueur non nulle).

On a, au passage, introduit dans nos spécifications le nouveau mot clé Extends qui indique que l'on étend une sorte (ici, list).

Les équations conditionnelles servent également àexprimer des alternatives comme dans la spécification suivante de l'opérateur de test d'appartenance :


Extends: list

Symbols:
 mem : elt, list
® bool

Axioms: " xs:list; x,y:elt

 (mem x nil)
= false

 (mem x (cons x xs))
= true

 (x
¹y) Þ (mem y (cons x xs)) = (mem y xs)

Listes ordonnées

Nous voulons exprimer ici la propriété d'ordonnancement des éléments d'une liste. Il faut pour cela disposer d'une relation d'ordre sur les éléments de la sorte indéterminée elt. C'est ce que nous faisons en introduisant l'usage des mots clés Assume: et with.

La propriété d'ordonnancement sera spécifiée par l'opérateur booléen sorted.

Extends: list

Assume:
 (
<) : elt, elt ® bool
 (
£) : elt, elt ® bool

with: " e,e1,e2,e3:elt
 (e
< e) = true
 (e1
< e2) Þ (e2 < e1) = false
 (e1
< e2), (e2 < e3) Þ (e1 < e3) = true
 (e1
£ e2) = (not (e2 < e1))

Symbols:
 sorted : list
® bool

Axioms: " xs:list; x,y:elt

 (sorted nil)
= true
 (sorted (cons x nil))
= true
 (x
£ y) Þ (sorted (cons x (cons y xs))) = (sorted (cons y xs))
 (y
< x) Þ (sorted (cons x (cons y xs))) = false
On peut alors utiliser le formalisme des spécifications algébriques pour décrire un algorithme de tri simple : le tri par insertion. On montrera ensuite sa correction.

Extends: list

Symbols:
 ins : elt, list
® list
 ins_sort : list
® list

Axioms: " xs:list; x,y:elt

 (ins x nil)
= (cons x nil)
 (x
£ y) Þ (ins x (cons y xs)) = (cons x (cons y xs))
 (y
< x) Þ (ins x (cons y xs)) = (cons y (ins x xs))

 (ins_sort nil)
= nil
 (ins_sort (cons x xs))
= (ins (ins_sort xs))

Theorems: " xs:list

 
1. (sorted (ins_sort xs)) = true
Le théorème de correction de l'algorithme exprime que la valeur obtenue par l'opérateur ins_sortest une liste ordonnée. sa démonstration requiert un lemme auxiliaire qui exprime la propriété d'invariance de l'opérateur d'insertion inspour la propriété d'ordonnancement. Ce lemme s'énonce :
Extends: list

Lemma: " xs:list; x:elt

 
2. (sorted xs) Þ (sorted (ins x xs)) = true
Pour démontrer ce lemme d'invariance, on utilise un autre lemme auxiliaire correspondant àl'un des cas d'induction de la démonstration de 2. :
Extends: list

Lemma: " xs:list; x,y:elt

 
2.1 (x £ y), (sorted (cons x xs)) Þ (sorted (cons x (ins y xs))) = true
Preuve de 2.1: on montre
" x,y:elt. (x £ y), (sorted (cons x xs)) Þ (sorted (cons x (ins y xs))) = true

par induction sur la liste xs.

2  Assertions

Les spécifications algébriques permettent de spécifier des données a priori peu fonctionnelles telles que les tableaux.

En fait, on peu avoir une idée de ce que pourrait être la spécification d'une sorte vecten consultant l'interface du module Array d'Objective Caml :
Module Array: array operations

val length : 'a array -> int

      Return the length (number of elements) of the given array. 

val get: 'a array -> int -> 'a

      Array.get a n returns the element number n of array a. The first
      element has number 0. The last element has number Array.length a
      - 1. Raise Invalid_argument "Array.get" if n is outside the
      range 0 to (Array.length a - 1). You can also write a.(n)
      instead of Array.get a n.

val set: 'a array -> int -> 'a -> unit

      Array.set a n x modifies array a in place, replacing element
      number n with x. Raise Invalid_argument "Array.set" if n is
      outside the range 0 to Array.length a - 1. You can also write
      a.(n) <- x instead of Array.set a n x.

val make: int -> 'a -> 'a array
val create: int -> 'a -> 'a array

      Array.make n x returns a fresh array of length n, initialized
      with x. All the elements of this new array are initially
      physically equal to x (in the sense of the ==
      predicate). Consequently, if x is mutable, it is shared among
      all elements of the array, and modifying x through one of the
      array entries will modify all other entries at the same time.
Il s'en dégage essentiellement une opération de création, une opération d'accès, une opération de modification et une information de longueur ou cardinalité. Traduisons cela dans notre formalisme :


Sort: vect

Uses: int, elt

Symbols:
 length : vect
® int
 get : vect, int
® elt
 set : vect, int, elt
® vect
 make : int, elt
® vect

Axioms: " v:vect; e:elt; n,i,j:int

 (length (make n e))
= n

 (length (set v i e))
= (length v)

 (0
£ i), (i < (length v)) Þ (get (make n e) i) = e

 (0
£ i), (i < (length v)) Þ (get (set v i e) i) = e

 (0
£ i), (i < (length v)), (0 £ j), (j < (length v)), (i ¹ j)
  
Þ (get (set v i e) j) = (get v j)
On a essentiellemnt abstrait la vision de tableaux en termes de ses constructeurs : makeet set. On a restreint la pertinance de l'opération d'accès setpar des équations conditionnelles. Un vecteur, du point de vue algébrique est l'histoire de sa création et de ses modifications.

Pour alléger l'écriture, on définit l'appartenance au domaine d'indice des vecteurs :


Extends: vect

Symbols:
 indom : int, vect
® bool

Axioms: " v:vect; i:int

 (indom i v)
= (and (0 £ i) (i < (length v)))

Le plus petit indice de l'élément maximal d'un tableau

Pour finir donnons un dernier exemple de spécification d'algorithme : la recherche du plus petit indice de lélément maximal d'un tableau.

La spécification suivante définit complétement abstraitement ce qu'est ce plus petit indice (imax), puis donne une fonction de calcul dont on pourra montrer qu'elle calcule cette valeur (find_imax).


Extends: vect

Assume:
 (
<) : elt, elt ® bool
 (
£) : elt, elt ® bool

with: " e,e1,e2,e3:elt
 (e
< e) = true
 (e1
< e2) Þ (e2 < e1) = false
 (e1
< e2), (e2 < e3) Þ (e1 < e3) = true
 (e1
£ e2) = (not (e2 < e1))

Symbols:
 imax : vect
® int
 loop : int, int, vect
® int
 find_imax : vect
® int

Axioms: " v:vect; i,m:int; e:elt

 /*
imax.1 */
 (indom (imax v) v)
= true

 /*
imax.2 */
 (indom i v)
Þ (get v i) £ (get v (imax v))

 /*
imax.3 */
 (indom i v), ((get v (imax v)) = (get v i))
Þ (imax v) £ i

 (loop m (length v) v) = m

 (0
£ i), (i < (length v)), (indom m v), (get(v,m) < get(v,i))
  
Þ (loop m i v) = (loop i i+1 v)

 (0
£ i), (i < (length v)), (indom m v), (get(v,i) £ get(v,m))
  
Þ (loop m i v) = (loop m i+1 v)

 ((length v)
¹ 0) Þ (find_max v) = (loop 0 1 v)

Theorems: " v:vect

 ((length v)
¹ 0) Þ (find_max v) = (imax v)
La démonstration du théorème de correction de find_maxréclame, en fait, la correction de l'appel àloop :


Theorems: " v:vect

 ((length v)
¹ 0) Þ (loop 0 1 v) = (imax v)
Pour obtenir cette correction, il faut observer qu'en fait, ce n'est pas loopelle-même qui est correcte, mais son emploi àpartir de bonnes valeurs. Par exemple (en utilisant la notation OCAML des tableau) (loop 1 2 [|5;4;3;2;1;0|])ne donne pas le résultat escompté. En revanche, si le premier argument contient la valeur du plus petit indice de l'élément maximal parmi les indices déjàexplorés, on obtiendra bien le résultat escomptéen poursuivant la recherche.


Pour exprimer, le lemme qui nous donnera notre théorème, nous avons besoin d'une variante renforcée de la spécification de imax : l'opérateur bimaxqui donne le plus petit indice de l'élément maximal des éléments contenus dans le tableau avant un certain indice :


Extends: vect

Symbols:
 bimax : int
® vect ® int


Axioms: " v:vect; i,j,m:int

 (0
< i) Þ (indom (bimax i v) v) = true

 (0
£ j), (j < i) Þ (get v j) £ (get v (bimax i v))

 (0
£ j), (j < i), ((get v (bimax i v)) = (get v j))
  
Þ (bimax i v) £ j

Lemma: " v:vect; i,m:int

 ((length v)
¹ 0), (0 £ i), (i £ (length v))
  
Þ (loop (bimax i v) i v) = (imax v)
L'induction permettant de mener àbien cette preuve est la même que celle qui permet d'établir la terminaison de loop : la distance de l'indice iau dernier indice de vdécroît àchaque appel récursif.

Soit n=(length v), on prouve notre lemme par induction sur n-i :

Assertions

Nous allons àprésent utiliser la spécification ci-dessus pour établir la correction d'un programme impératif calculant l'indice de l'élément maximal.

On peut donner en Objective Caml une version impérative de notre fonction de recherche de l'indice de l'élément maximal :
let find_max v =
 let m = ref 0 in
 let i = ref 1 in
  while !i < Array.lenth v do
   if v.(!m) < v.(!i) then m := i;
   incr i
  done;
  !m 
;;
Affirmer la correction de ce programme. c'est affirmer que la valeur retournée par (find_max v)est celle de (imax v). On peut indiquer cette égalitésous forme d'un commentaire insérédans le texte du programme :
let find_max v =
 let m = ref 0 in
 let i = ref 1 in
  while !i < Array.lenth v do
   if v.(!m) < v.(!i) then m := i;
   incr i
  done;
  !m (* !m = (imax v) *)
;;
De tels commentaires s'appellent des assertions. Un assertion affirme que la formule qu'elle contient est vraie àl'endroit du programme oùelle est insérée. Cette spacialitédes assertions vient du style impératif de programmation : un programme impératif est une suite d'instructions.

Intuitivement, ce programme est correct car la boucle whileconserve la propriétéd'invariance que mcontient toujours l'indice de l'élément maximal parmi ceux explorés. En d'autres termes, on a, tout au long du programme !m = (bimax !i v).

On peut alors préciser la raison de la correction en détaillant les assertions vérifiées àchaque étape du calcul :
let find_max v =
 let m = ref 0 in
 let i = ref 1 in 
  (* a1 : !m = (bimax !i v) *)
  while !i < Array.length v do
   (* a2 : (!i < Array.length v) & (!m = (bimax !i v)) *)
   if v.(!m) < v.(!i) then m := i;
   (* a3 : !m = (bimax !i+1 v) *)
   incr i
   (* a4 : !m = (bimax !i v) *)
  done;
  (* a5 : (!i = Array.lenth v) & (!m = (bimax !i v)) *)
  !m 
  (* a6 : !m = (imax v) *)
;;
Examinons brièvement chacune de nos assertions :

3  Théorie axiomatique des programmes

On peut se convaincre intuitivement que la fonction find_maxsatisfait l'ensemble des assertions qui commentent son code. Mais pourquoi se contenter de si peu lorsque l'on peut formaliser la relation entre le langage de programmation et le langage de spécification ?

Une formalisation de ce rapport est connu sur le nom de logique de Hoare. Les énoncés de cette logique sont des triplets de la forme [P] C [Q] oùP et Q sont des formules du langage de spécification et C une expression du langage de programmation. La formule P est appelée précondition et Q, postcondition.
Intuitivement, les formules P et Q expriment les contraintes et propriétés que doivent satisfairent les variables du programme C. La précondition P décrit l'état des variables avant exécution de C et Q, l'état ou, le résultat, obtenu aprés l'exécution de C. La relation entre pré- et post- condition est définie par induction sur les règles de construction du langage de programmation.

Nous considérons un tout petit noyau des langages de programmation impératifs :
Instruction vide SKIP
Affectation x := e
Séquence C1 ; C2
Conditionnelle If e then C1 else C2
Boucle While e do C

On présente les définitions sous forme de règles de déduction :
 

[P] SKIP [P]
 
 

[P[e/x]] x := e [P]
 
[P] C1 [Q]   [Q] C2 [R]

[P] C1 ; C2 [R]
 
[PÙ e=true] C1 [Q]     [PÙ e=false] C2 [Q]

[P] If e then C1 else C2 [Q]
 
[PÙ e=true] C [P]

[P] While e do C [PÙ e=false]
L'ensemble de ces règles décrivant le comportement des instruction du langage de programmation vis-à-vis des états exprimés par les formules est complétépar une dernière règle autorisant des transformations purement logiques des pré- et post- conditions :
PÞ P'   [P'] C [Q']   Q'Þ Q

[P] C [Q]
En scindant cette règle en deux, on obtient les deux règles dérivées suivantes :
PÞ P'   [P'] C [Q]

[P] C [Q]
Affaiblissement de la précondition
 
[P] C [Q']   Q'Þ Q

[P] C [Q]
Renforcement de la postcondition


find_max revisité

Reformulons dans le langage axiomatiséci-dessus la fonction find_max :
m := 0;
i := 1;
While i < (length v) do begin
 If v(m) < v(i) then m := i else SKIP;
 i := i+1
end
Nous voulons montrer que si vest un tableau non vide alors, aprés exécution de find_max, la variable mcontient la valeur de (imax v). C'est ce qu'exprime le triplet :
[(length v)¹0] find_max [m=(imax v)]
Le programme, simple, find_maxest constituéd'une initialisation (C0) suivie d'une boucle (While e do C). Nous voulons établir [P] C0; While e do C [Q]. Àce shéma de programme correspond le schéma de preuve suivant :
[P] C0 [I]    
IÙ ¬ e Þ Q    
[IÙ e] C [I]

[I] While e do C [IÙ ¬ e]

[I] While e do C [Q]

[P] C0; While e do C [Q]
Suivant ce schéma de preuve, pour établir la correction de notre programme, il nous faut donc trouver une formule I (dit invariant) tel que :
  1. [P] C0 [I]
  2. IÙ ¬ e Þ Q
  3. [IÙ e] C [I]

Preuve de correction (partielle) de find_max

Nous avons déjàvu l'invariant nécessaire ànotre preuve, il s'agit de l'égalitém = (bimax i v). Passons donc aux trois étapes définies ci-dessus :
  1. Il faut montrer que :
    [(length v)¹0] m:=0; i:=1 [m = (bimax i v)]
    Il est facile de vérifier que 0 = (bimax 1 v) est vrai. On a donc trivialement l'implication :
    (length v)¹0 Þ 0 = (bimax 1 v)
    En utilisant la règle d'affaiblissement de la précondition, on obtient la séquence d'initialisation avec :
    [0=(bimax 1 v)] m := 0 [m = (bimax 1 v)] et [m = (bimax 1 v)] i := 1 [m = (bimax i v)]
    cqfd
  2. En utilisant les équations de bimax, on peut montrer que si i ³ (length v)alors (bimax i v) = (imax v). D'où
    m = (bimax i v) Ù ¬(i < (length v)) Þ m = (imax v)
  3. Vient maintenant le gros morceau : montrer que l'invariant est conservépar le corps de la boucle. C'est àdire (en écrivant es triplets verticalement) :
    [ m = (bimax i v) Ù i < (length v)]
    If v(m) < v(i) then m := i else SKIP;i := i+1
    [ m = (bimax i v)]
    Ou encore, par les règles de la séquence et de l'affectation :
    [ m = (bimax i v) Ù i < (length v)]
    If v(m) < v(i) then m := i else SKIP;
    [ m = (bimax i+1 v)]
    i := i+1
    [ m = (bimax i v)]
    La seconde partie de la séquence est immédiate, reste àvoir :
    [ m = (bimax i v) Ù i < (length v)]
    If v(m) < v(i) then m := i else SKIP;
    [ m = (bimax i+1 v)]
    Par la règle de l'alternative, il faut montrer que (a)
    [ m = (bimax i v) Ù i < (length v) Ù v(m) < v(i)]
    m := i
    [ m = (bimax i+1 v)]
    et que (b)
    [ m = (bimax i v) Ù i < (length v) Ù v(m) ³ v(i)]
    SKIP;
    [ m = (bimax i+1 v)]
    1. de m = (bimax i v)et v(m) < v(i), on peut déduire que i = (bimax i+1 v). Et, par règle de l'affectation, on a
      [i = (bimax i+1 v)] m := i [m = (bimax i+1 v)]
      On obtient le résulat attendu par affaiblissement de la précondition.
    2. de m = (bimax i v)et v(m) ³ v(i), on peut déduire que m = (bimax i+1 v). On a alors le résultat attendu par la règle du SKIPet affaiblissement de la précondition.
    cqfd

4  Raffinement

L'axiomatique de Hoare-Floyd permet de vérifier a posteriori qu'un programme donnéstatisfait une spécification exprimée en terme de préet post conditions. L'idée du raffinement est d'obtenir la correction des programmes a priori en régentant leur construction àpartir de leur spécification. La dérivation des programmes obéit àun certain nombre de règles qui permettent d'introduire petit-à-petit les constructions usuelles des langages de programmation. On élimine ainsi progressivement toutes les formulations non calculatoires des spécifications pour obtenir un code exécutable. Chaque règle est conçue de façon àgarantir la correction au sens de Hoare-Floyd (nous verrons comment ci-dessous).

La discipline du raffinement conçoit en fait les programmes comme un classe particulière de spécification. On obtient ainsi un langage mélangeant des spécifications sous forme de préet post conditions (notées [P, Q]) et des constructions des langages de programmations. Ainsi,on peut obtenir des expressions comme :
If B then [P1, Q1] else [P2, Q2]
On voit donc les structures de controle des langages de programmations comme des combinateurs de spécifications. De façon générale, on définit l'ensemble des spécifications : La relation de raffinement etre deux spécification S1 est S2 est notée :
S1Ê S2
Cette relation devra-t-être monotone par rapport aux combinateurs : si S1Ê S'1 et S2Ê S'2 alors La monotonicitépermet un raffinememnt modulaire des spécifications.



Règles de raffinement

Ne rien faire (Sk)
[P, P] Ê SKIP

Affectation (As)
[Q[e/x], Q] Ê x := e

Séquence (Sq)
[P, Q] Ê [P, R] ; [R, Q]

Variable locale (Vr)
[P, Q] Ê {Var x1.. xn. [P,Q] }
x1.. xn n'apparaissent ni dans P ni dans Q.

Combinant ces trois dernières règles, on peut dériver le programme d'échange de deux valeurs comme suit :
Soient N et M deux valeurs entières et x, y deux variables
[x=NÙ y=M, x=MÙ y=N] Ê {Var z . [x=NÙ y=M, y=NÙ x=M] }
  Ê {Var z .
      [x=NÙ y=M, z=NÙ y=M] ;
      [z=NÙ y=M, y=NÙ x=M] }
  Ê {Var z .
      z := x ;
     [z=NÙ y=M, y=NÙ x=M] }
  Ê {Var z .
      z := x ;
      [z=NÙ y=M, z=NÙ x=M] ;
      [z=NÙ x=M, y=NÙ x=M] }
  Ê {Var z .
      z := x ;
      x := y ;
      [z=NÙ x=M, y=NÙ x=M] }
  Ê {Var z .
      z := x ;
      x := y ;
      y := z }
Les deux règles suivantes permettent du raffinememnt purement logique, sans contrepartie algorithmique.
Précondition (Wk)
[P, Q] Ê [R, Q] si PÞ R

Postcondition (St)
[P, Q] Ê [P, R] si RÞ Q

L'application des règles d'affaiblissemnt de la précondition et de renforcement de la postcondition est soumise àune condition : vérifier la validitéd'une formule. On appelle de telles conditions des obligations de preuve.

Affectation bis (Ab)
Losrque la précondition implique la postcondition modulo une substitution, on obtient, par affaiblissement une deuxième règle pour l'affectation :
[P, Q] Ê x := e si PÞ Q[e/x]
En effet
[P, Q] Ê [Q[e/x], Q] (Wk)
  Ê x := e (As)

Initialisation (Ai)
En utilisant (Wk), (As) et (Sq) on obtient une autre règle dérivée (Ai) permettant l'initialisation d'une variable :
[P, Q] Ê x := e ; [PÙ x=e, Q]
En effet :
[P, Q] Ê [PÙ e=e, Q] (Wk)
  Ê [PÙ e=e, PÙ x=e] ; (Sq)
    [PÙ x=e, Q]
  Ê x := e ; (As)
    [PÙ x=e, Q]

On complète notre jeu de règle par les structures de contrôles de base.
Conditionnelle (If)
[P, Q] Ê If B then [PÙ B, Q] else [PÙ ¬ B, Q]

Boucle (Wh)
[I, IÙ ¬ B] Ê While B do [PÙ BÙ e=n, IÙ e<n] si PÙ B Þ e ³ 0
Dans cette dernière règle, n est une valeur entière et e une expression qui soint làpour garantir la terminaison de la boucle.



Exemple

Voici comment on peut dériver une boucle calculant la division euclideinne de deux entiers. On sait que si X et Y sont deux entiers leur quotient est Q et leur reste R si l'on a : X = (Y× Q) + R. Il faut de surcroît que R £ Y et, comme précondition, que Y > 0.

[Y > 0, X = (Y× Q) + R Ù R £ Y] Ê R := X ; (Ai)
    [Y>0Ù R=X , X = (Y× Q) + R Ù R £ Y]
  Ê R := X ; Q := 0 ; (Ai)
    [Y>0Ù R=XÙ Q=0 , X = (Y× Q) + R Ù R £ Y]
  Ê R := X ; Q := 0 ;  
    [Y>0Ù X = (Y× Q) + R , X = (Y× Q) + R Ù R £ Y] (Wk)
  Ê R := X ; Q := 0 ;  
    [Y>0Ù X = (Y× Q) + R , X = (Y× Q) + R Ù ¬(Y £ R)] (St)
  Ê R := X ; Q := 0 ;  
    While (Y £ R) do (Wh)
      [Y>0Ù X = (Y× Q) + R Ù Y £ R Ù R=n,
       Y>0Ù X = (Y× Q) + R Ù R<n]
  Ê R := X ; Q := 0 ;  
    While (Y £ R) do
      [Y>0Ù X = (Y× (Q+1) + (R-Y) Ù Y £ R Ù R=n, (Wk)
       Y>0Ù X = (Y× Q) + R Ù R<n]
  Ê R := X ; Q := 0 ;  
    While (Y £ R) do
      [Y>0Ù X = (Y× (Q+1) + (R-Y) Ù Y £ R Ù R=n,
       Y>0Ù X = (Y× Q) + (R-Y) Ù Y £ R Ù R=n] (Sq)
      [Y>0Ù X = (Y× Q) + (R-Y) Ù Y £ R Ù R=n,
       Y>0Ù X = (Y× Q) + R Ù R<n]
  Ê R := X ; Q := 0 ;  
    While (Y £ R) do
      Q := Q+1 ; (As)
      [Y>0Ù X = (Y× Q) + (R-Y) Ù Y £ R Ù R=n,
       Y>0Ù X = (Y× Q) + R Ù R<n]
  Ê R := X ; Q := 0 ;  
    While (Y £ R) do
      Q := Q+1 ; R = R-Y (Ab)
Nous donnons ci-dessous la liste des obligations de preuves (résumées) dans l'ordre de leur apparition au cours de la dérivation :
  1. X=RÙ Q=0 Þ X=(Y× Q)+R
  2. R£ Y Þ ¬(Y£ R)
  3. X = (Y× Q) + R Þ X = (Y× (Q+1) + (R-Y)
  4. Y>0Ù X = (Y× Q) + (R-Y) Ù R=n Þ X = (Y× Q) + (R-Y) Ù R-Y < n

4.1  Hoare-Floyd et le raffinement

On peut justifier la correction des règles basiques de raffinement en terme de logique de Hoare-Floyd. Pour cela, on interprète une spécification comme l'ensemble des programmes qui la satisfont, au sens de Hoare-Floyd. Pour l'écrire, on pose :
[P, Q] = {  C  |  |-[P] C [Q]  }
où|-[P] C [Q] signifie que le triplet [P] C [Q] est dérivable en logique de Hoare.

L'interprétation passe aux combinateurs de la façon suivante : On montre alors que si S est un raffinement de [P,Q] alors pour tout programme C appartenant à(l'interprétation) de S, le triplet [P] C [Q] est dérivable en logique de Hoare. C'est àdire :
[P,Q] Ê S Þ " CÎ S. |-[P] C [Q]

On montre l'implication en raisonnant par cas sur la règle de raffinememnt appliquée :
Ne rien faire
on a directement [P] SKIP [P].

Affectation
on a aussi [Q[e/x]] x := e [Q].

Séquence
soit C1 ; C2Î [P,R] ; [R,Q], on a par définition, C1Î [P,R] et C2Î [R,Q]. D'où, |-[P] C1 [R] et |-[R] C2 [Q], et donc |-[P] C1 ; C2 [Q].

Variable
soit Var x. CÎ Var x. [P,Q], on a, par définition CÎ [P,Q], et donc |-[P] C [Q], par défintion. On a donc |-[P] Var x.C [Q], puisque x ne n'apparaiît pas dans P ni dans Q.

Conditionnelle
soit If B then C1 else C2Î If B then [PÙ B, Q] else [PÙ ¬ B, Q]. On a, par définition que C1Î [PÙ B, Q] et C2Î[PÙ ¬ B,Q]. D'où|-[PÙ B] C1 [Q] et |-[PÙ ¬ B] C2 [Q]. Et donc |-[P] If B then C1 else C2 [Q].

Boucle
soit While B do CÎ While B do [PÙ BÙ n=e, PÙ e<n]. On a CÎ [PÙ BÙ n=e, PÙ e<n], c'est-à-dire |-[PÙ BÙ n=e] C [PÙ e<n]. D'où|-[P] While B do C [PÙ ¬ B].

5  Spécification ensembliste

Une alternative àla formalisation en termes de spécification équationnelle est l'utilisation de la richesse et de la généralitéde la théorie des ensembles.

Il existe des présentations axiomatiques rigoureuse de la théorie des ensembles. Néanmoins, nous pourrons nous contenter ici d'une présentation intuitive.



Langage ensembliste

La relation de base entre ensembles est la relation d'appartenance notée
xÎ y
pour << x appartient ày >> ou << x est une élément de y >>.

Sur la base de cette relation primitive, on exprime l'égalitéentre ensembles en posant l'axiome, dit d'extensionnalité, suivant :
x = y Û (" z. zÎ x Û zÎ y)
Et d'autres termes, deux ensemble sont égaux si et seulement si ils possèdent exactement les mêmes éléments.

D'autres notions ensemblistes utiles, comme l'inclusion peuvent être définies au sens usuel :
xÍ y º (" z. zÎ x Þ zÎ y)
Cette façon usuelle de définir s'oppose àla définition axiomatique de l'égalitéen ce sens que la formule xÍ y syntaxiquement équivalente àsa définition et peut être remplacée àla manière dont un préprocesseur remplace une macro, alors que l'égalitéx=y est donnée comme logiquement équivalente àune formule. On ne peut remplacer une égalité, mais on peut raisonner dessus en utilisant la formule équivalente.

Nous rappelons ci-dessous quelques constructions ensemblistes utiles en donnant, pour chacune, un axiome qui les caractérise :
  notation axiome
Ensemble vide : Ø " x. xÏØ
 
Couples (x,y) (x,y) = (x',y') Û x=x' Ù y=y'
 
Produit X× Y zÎ X× Y Û $ xÎ X. $ yÎ Y. z=(x,y)
 
Union XÈ Y zÎ XÈ Y Û zÎ X Ú zÎ Y
 
Ensemble des parties P(X) zÎ P(X) Û zÍ X
 
Schéma de compréhension {xÎX  |  j } yÎ{xÎX  |  j }Û yÎ X Ù j [y/x]
 

Enfin, d'autres notions sont définies en utilisant le schéma de compréhension :
  notation définition
Intersection XÇ Y {zÎX  |  zÎ Y }
 
Différence X\Y {xÎX  |  zÏY }
 


Relations binaires et fonctions

Une relation binaire entre un ensemble X et un ensemble Y associe des éléments de X avec des élément de Y. Cette association peut être representée comme un couple (x,y) oùxÎ X et yÎ Y. On peut alors définir l'ensemble des relations binaires entre deux ensembles :
X« Y º P(X× Y)
On peu ainsi parler d'une relation R entre (éléments de) X et Y comme un élément de X« Y et noter RÎ X« Y. Si deux éléments x et y sont dans la relation R, on se donne la notation infixe usuelle en posant :
x 
 
R

 y º (x,y)Î R

Rappelons ce que sont les domaine et codomaine d'une relation :
dom(R) º {xÎX  |  $ yÎ Y. x  
 
R

  y }
ran(R) º {yÎY  |  $ xÎ X. x  
 
R

  y }

Les fonctions sont une catégorie particulière de relations binaires : celles qui associe au plus un élément du codomaine au élément de leur domaine. En tant que relations, on pourra aussi parler de l'ensemble des fonctions entre X et Y en distinguant les fonctions partielles (notées X¬® Y) des fonction totales (notées X® Y). Voici comment on définit ces deux ensembles :
X¬® Y º
{fÎX« Y  |  " xÎdom(f).$! yÎ Y. x  
 
f

  y }
X® Y º {fÎX¬® Y  |  dom(f) = X }

Si f est une fonction, on se donne la notation usuelle d'application en posant l'axiome :
f(x)=y Û (x,y)Î f
Ce ne peut être une simple définition, car l'expression f(x)=y suppose l'existence de y.

On définit, sur les relations biniare un certain nombre d'opérateurs utiles dans le cadre de la spécification. Cette dernière opération est massivement utilisée lorsque l'on veut modifier en un point la valeur d'une fonction. En notant x|® y le couple (x,y), on a :
ì
í
î
fÅ{x|® y }(x) = y
fÅ{x|® y }(z) = f(z) si z¹x


Arithmétique

On se donne l'arithmétique (c'est possible).



5.0.1  Les suites

Avec notre langage ensembliste, on peut définir une structure linéaire générique, modèle mathématique de structures de données que sont les listes, les tableaux, les files d'attentes, etc.

Une suite d'éléments de X, notée seq  X, est une fonction partielle d'un intervalle 1..n dans x :
seq  X º {S : IN1¬® X | $ n:IN. dom S = 1..n }
Le ième élément de S est simplement S(i) (i.e l'image de i par S). Notez que si iÏdom S cet élément n'est pas défini.

On note :
#S la longueur (i.e. le nombre d'éléments) de la suite S ;
áñ la suite vide (i.e. l'ensemble vide) ;
á a1, .., anñ la suite composées des éléments a1, .., an
  (i.e. la fonction {1|® a1, .., n |® an}).

Il sera souvent utile, on définit l'ensemble des suites non vides par :
seq1  X º {s:seq  X | s¹áñ }


Le langage Z

Le langage Z se base sur le langage ensembliste pour offrir un format de spécification de fonctions ou d'opérations.



Défintions axiomatiques

Les fonctions sont définies de façon axiomatiques : on introduit un symbole et on donne une formule énonçant les propriétés essentielles de ce symbole. Le format de définition axiomatique est :
nom : type
formule

Voici quelques exemples de fonctions manipulant des suites.

Concaténation :
^ : seq  X × seq  X ® seq  X
" s1, s2Îseq  X. " iÎIN1.
  (i £ #s1Þ s1^ s2(i) = s1(i)) Ù
  (i > #s1Þ s1^ s2(i) = s2(i-#s1))

Sous suite :
sub : seq  X × IN × IN ® seq  X
" sÎseq  X. " i,jÎ IN. " kÎdom S Ç i..j.
  sub(s,i,j)(k) = s(k)

Suites vues comme des listes :
head:seq1  X® X
" sÎseq1  X.
  head(s) = s(1)
 
tail:seq1  X® seq  X
" sÎseq1  X.
  tail(s) = sub(s,2,#s)



Suites vues comme des files d'attentes :
last:seq1  X® X
" sÎseq1  X.
  last(s) = s(#s)
 
front:seq1  X® seq  X
" sÎseq1  X.
  front(s) = sub(s,1,#s-1)


Schémas d'opérations

Un schéma d'opération est, àla base un triplet constituéd'un nom, d'un ensemble de déclarations et d'une formule. Le format général d'un schéma d'opération est le suivant :
Nom
 
declarations
 
 
formule
 
Les déclarations sont de la forme x:Xx est une variable et X une expression ensembliste.

Ainsi considéré, un schéma représente un état : n-uplet de valeurs (les variables déclarées) possédant certaines propriétés (la formule). Cette formule peut exprimer des relations entre les variables constituant l'état. C'est pourquoi un schéma d'opération s'utilise aussi pour établir une relation entre un état avant (l'opération) et un état aprés (l'opération).

Par exemple, on définira l'opération de retournement d'une liste comme une opération Rev reliant une suite s àune suite s' oús' est le miroir de s :
 
Rev
 
 
 
s, s' : seq  X dom(s) = dom(s') Ù
" iÎ dom(s'), s'(i) = s(#s-i+1)

Cette façon de noter un état avant et aprés avec une apostrophe est devenue l'usage aussi l'a-t-on généralisée aux schemas.

Si le schéma S est  
S
 
x:T
 
 
j
 
  alors le schéma S' est  
S'
 
x':T
 
 
j [x'/x]
 

Un schéma peut se résumer àde simples déclarations auquel cas on laisse vide la section formule :
Nom
 
declarations
 
 
 

Les schémas peuvent se combiner de plusieurs manières : Et utilisant la combinaison par conjonction, on définit une schéma général de relation avant-aprés :
D S º SÙ S'


Un exemple

Les éléments de spécification qui suivent concernent la partie l'API de CICS qui permet de gérer les files d'attentes temporaires Temporary Storage Queue lors d'échanges de données entre divers sites d'un système d'information. L'ensemble de la spécification est donnée par schémas. Cet exemple est tiréde << Specifying the IBM CICS Application Programming Interface >>, par Steve King, in Specification Case Studies, Ian Hayes ed., Prentice Hall, deuxième édition, 1993.

Les données
Nous manipulerons des files d'attentes contenant des suites d'octets. On pose :
BYTE == 0..255
TSElem == seq  BYTE
Les files d'attentes du système (TSQ) sont modélisées par des suites (ar) et un pointeur (l'entier p) sur le dernier élément de la file ayant fait l'objet d'une opération de lecture ou d'écriture (voir infra) :
 
TSQ
 
 
 
ar:seq  TSElem
p:IN p £ #ar
On définit l'état initial d'une file d'attente :
 
TSQ_Initial
 
 
 
TSQ ar=áñ Ù p=0
Notez que la conjonction p £ #arÙ p=0, qui vient de l'inclusion de TSQ, est cohérente.

Les opérations
On spécifie les opérations d'ajout et de retrait d'un élément par les schémas Append0 et Remove0
 
Append0
 
 
 
D TSQ
from?:TSElem
item!:IN ar' = ar^áfrom?ñ Ù
item! = #arÙ
p' = p
   
 
Remove0
 
 
 
D TSQ
item!:TSElem p < #ar Ù
p' = p+1 Ù
into! = ar(p') Ù
ar' = ar
Le ? de from et le ! de item! sont conventionnels. Ils indiquent les << entrées-sorties >> du schéma. C'est àdire les valeurs dont il faut disposer pour l'opération (ici, l'éléent àajouter from?) et les valeurs données àl'issue de l'opération (ici, le nouveau nombre d'élément item!). Ces marques doivent être considérées comme des commentaires pour une implentation future. Ils n'ont pas de sens au niveau logique.

L'opération de retrait Remove0 a une précondition : le pointeur p ne doit pas être en fin de file. C'est ce qu'exprime p < #ar. En fin d'opération, il aura étéincrémenté : p' = p+1.

Les deux opérations suivantes utilisent les files comme des tableaux dont on peut modifier (Write0) ou consulter (Read0) une case :
 
Write0
 
 
 
D TSQ
item?:IN
from?:TSElem item?Î 1..#ar Ù
ar' = arÅ{item?|® from?} Ù
p' = p
   
 
Read0
 
 
 
D TSQ
item?:IN
into!:TSElem item?Î 1..#ar Ù
into! = ar(item?) Ù
p' = itemÙ
ar' = ar

Les erreurs
Nous avons vu qu'une opération comme Remove0 supposait une précondition. Si celle-ci n'est pas réalisée, il faut que l'opération le signale en positionnant un staut d'exécution. Pour ce, on se donne l'ensemble énumérésuivant :
OpStatus = {Success, ItemErr, NoSpace }
dont les éléments sont des constantes abstraites.

On donne dans un schéma les éléments communs àtoute opération sur le statut d'exécution :
 
ERROR
 
 
 
D TSQ
report! : OpStatus q TSQ' = q TSQ
L'opérateur q appliquéàun schéma donne le n-uplet de tous les noms de variables que contient le schéma. On l'utilise ici pour signifier que toutes des variables de TSQ doivent êtres égales àcelle de TSQ' (i.e. ar'=ar et p'=p).

Pour chaque statut possible, on donne une opération spécifique :
 
NoneLeft
 
 
 
ERROR p = #ar Ù
report! = ItemErr
 
 
OutOfBounds
 
 
 
ERROR
item?:IN item?Ï1..#ar Ù
report! = ItemErr
 
 
OutOfSpace
 
 
 
ERROR report! = NoSpace
 
Successful
 
 
 
report! : OpStatus report! = Success
Remarquez que les opérations NoneLeft et OutOfBounds expriment une condition pour que report! prenne une valeur donnée alors que OutOfSpace ne le fait pas. En effet, dans l'état actuel de la spécification, on ne dispose d'aucune information sur la taille de l'espace disponible pour stocker les files d'attente. La seule chose que l'on puisse faire, c'est de prévoir la possibilitéd'un débordement de l'espace de stockage disponible.

Redéfinitions avec gestion d'erreurs
Maintenant que les erreurs prévisibles ont étérépertoriées et spécifiées, on redéfinit les opérations sur les files d'attente en intégrant la gestion du statut d'exécution. On obtient ainsi par simple combinaison logique des schémas les opérations :
     
Append º (Append0 Ù Successful) Ú OutOfSpace
     
Remove º (Remove0 Ù Successful) Ú NoneLeft
     
Write º (Write0 Ù Successful) Ú OutOfBound Ú OutOfSpace
     
Read º (Read0 Ù Successful) Ú OutOfBound
     
Les schémas obtenus se lisent simplement : ou l'opération a réussi et son satut est Success, ou l'opération a échouéavec l'un des statut d'erreur associé.



6  La méthode B

La méthode B définit, comme Z, un format de spécification : la machine abstraite. Elle reprend le concept de raffinement en l'adaptant aux machines abstraites. Mais contrairement àce que nous avons présenté, B ne fournit pas un jeu prédéfini de règles de raffinemen a priori correctes, mais un mécanisme de vérification a posteriori engendrant des obligations de preuves. Enfin, le langage de spécification, s'il est basésur un langage ensembliste, enrichi celui-ci d'une construction dédiée àla spécification de programmes : les substitutions généralisées.



Substitutions généralisées

Les substitutions généralisées sont, en fait un langage d'instrutions (au sens usuel des langages impératifs) plus ou moins abstraites. En tant que substitutions, on peut les appliquer àun terme, une formule, voire une substitution, pour obtenir un nouveau terme, une nouvelle formule, voire substitution...

On note [S]Y l'application de la substitution S àl'expression, la formule ou la substitution Y. L'application des substitution est définie par induction sur S. Lorsque S est une substitution simple (voir ci-dessous), son application est définie par induction sur Y.

Substitution simple
La substitution de base est la substitution usuelle notée :
x := E
L'application de la substitution simple x := E est définie de façon usuelle sur les expressions et les formules :
[x := E]x º E
[x := E]y º y si x¹y
[x := E]F(E1, ..., En) º F([x := E]E1, ..., [x := E]En)  
[x := E]{xÎX  |  j } º {xÎX  |  j }  

Substitution parallèle
Si S1 et S2 sont deux substitutions simples, on note
S1|| S2
la substitution appliquant en même temps et de façon indépendante, S1 et S2.

On généralise la substitution parallèle en substitution multiple
x1, ..., xn := E1, ..., En
Soient z2, ..., zn des variables toutes différentes entre elles, différentes des x1, ..., xn et n'apparaissant dans aucunes des E1, ..., En, F on pose :
[x1, ..., xn := E1, ..., En]F º [zn := En]... [z2 := E2][x1 := E1] [x2 := z2]... [xn := zn]F  
Pour faire court, on dira que les zi sont des nouvelles variables. On a recours au renommage pour éviter que les xi pouvant apparaître dans les E2, ..., En ne soient affectées par la mise en séquence.

On s'autorisera àcombiner des substitutions multiples avec l'opérateur ||. Par exemple :
x1 := E1|| x2, x3 := E2,E3º x1, x2, x3 := E1,E2,E3
Substitution préconditionnée
Si j est une formule et S une substitution, on note
pre j  then S
la substitution imposant que j soit satisfaite pour appliquer S :
[pre j  then S]F º j Ù[S]F

Substitution indéterminée
Si x1, ..., xn sont des variables, j un formule et S une substitution, on note
any x1, ..., xn where j  then S
la substitution qui consiste àchoisir n'importe quels x1, ..., xn qui satisfont j pour appliquer S :
[any x1, ..., xn where j  then S]F º " z1... zn.(j 'Þ[S']F)
avec z1, ..., zn nouvelles variables, j ' = [x1, ..., xn := z1, ...,zn]j et S' = [x1, ..., xn := z1, ...,zn]S. Le renommage est rendu nécessaire àcause de l'introduction du quantificateur universel.



Machines abstraites

Intuitivement, une machine abstraite peut être comparée àun un objet comprenant un état interne (les variables) et des moyens d'actions sur cet état (les operations). Comme une machine est un élément de spécification, elle contiendra également des commentaires logiques exprimant ses propriétés (l'invariant).

Une machine est constituée de plusieurs rubriques jouant chacune un rôle dans la description des spécifications. Chacune de ces rubriques est identifiée par un mot clef. Voici quelles sont les rubriques essentielles d'une machine.
machine
cette rubrique ne contient qu'un seul élément : le nom de la machine.
sets
cette rubrique contient la déclaration des ensembles dont se servira la machine. On peut déclarer un ensemble soit comme un simple identificateur (auquel cas son contenu reste abstrait) soit par extension (comme un ensemble énuméré) Tous ces ensembles sont finis (explicitement ou implicitement)
variables
cette rubrique contient la déclaration des variables qu'utilise la machine. L'ensemble de ces variables constitue l'état interne de la machine.
invariant
cette rubrique contient une formule. C'est un élément trés important de la spécification. L'invariant contient la propriétéglobale que doit satisfaire la machine. Nous reviendrons ultérieurement sur ce point.
initialization
cette rubrique contient une susbtitution (généralisée) qui définit la valeur initiale des variables.
operations
cette rubrique contient la définition d'une liste d'objets appelés opérations. Une opération peut modifier l'état de la machine, prendre des arguments ou (inclusif) renvoyer une valeur. Les opérations sont définies en termes de substitutions généralisées.
On peut rajouter encore deux rubriques permetant d'introduire des constantes :
constants
cette rubrique contient les déclarations des noms des constantes.
constraints
cette rubrique contient les axiomes caratérisant les constantes.

L'exemple

Nous allons utiliser tout au long de notre étude de la méthode B un exemple tiréd'une petite étude de cas que J.-Y. Chauvet a publiée dans 1st Conference on the B method, Proceedings, ed. Henri Habrias, Nantes 1996 : le CARREFOUR.

Spécification informelle
La circulation d'un carrefour est réglée par deux feux tricolores dont les couleurs sont vert, orange ou rouge. Sur chacun des feux une seule des couleurs est active àla fois. Le système de feux du carrefour peut être en service ou hors service. Lorsque le système est hors service, les deux feux sont oranges. Lorsque le système est en service, la couleur de chacun des feux change suivant le cycle : orange puis rouge puis vert puis orange, etc ...

Un véhicule ne peut s'engager sur une voie que si le feu n'est pas rouge. Lorsque le système est en service, les feux doivent être réglés de façon àce que deux véhicules venant de voies différentes ne se trouvent pas en même temps sur le carrefour.

On désire obtenir un système assurant la mise en route du carrefour et la gestion du changement de couleur des feux.
De cette spécification informelle, il faut extraire les composantes essentielles et les traduire en objets formels.

Pré-spécification formelle
Il est utile pour pouvoir valider la pertinance de l'analyse de la spécification formelle, de garder trace de l'origine des éléments formels proposés.
  1. << les couleurs sont vert, orange ou rouge >> :
    Couleurs = {vert, orange, rouge}
  2. << deux feux tricolores >> :
    feuAÎ Couleurs, feuBÎ Couleurs
  3. << cycle : orange puis rouge puis vert >> :
    SuccÎ Couleurs® Couleurs
    orange|® rouge
    rouge|® vert
    vert|® orange
  4. << en service ou hors service >> :
    Etats = {hs, es}
    etatÎ Etats
  5. << Lorsque le système est hors service, les deux feux sont oranges >> :
    (etat = hs) Þ (feuA=orange Ù feuB=orange)
  6. << Lorsque le système est en service, les feux doivent être réglés de façon àce que deux véhicules venant de voies différentes ne se trouvent pas en même temps sur le carrefour >> :
    (etat = es) Þ ((feuA=rouge Ú feuB=rouge) Ù feuA¹feuB)
    Notez que notre formule dit plus que ce que réclamait la spécification informelle. Nous avons ajoutéàla propriétéréclamée de sécurité, une propriétéde disponibilité garantissant que l'un des feux permet le passage des véhicules.
Spécification formelle
Il faut maintenant réunir les éléments formels retenus dans le cadre des machines abstraites.
machine
 CARREFOUR

sets
 Etats = {hs, es}
 Couleurs = {vert, orange, rouge}

constants
 Succ

constraints
 SuccÎ Couleurs® Couleurs Ù
 Succ(orange)=rouge Ù
 Succ(rouge)=vert Ù
 Succ(vert)=orange

variables
 etat, feuA, feuB

invariant
 etatÎ Etats Ù
 feuAÎ Couleurs Ù
 feuBÎ Couleurs Ù

 (etat = hs) Þ (feuA=orange Ù feuB=orange) Ù
 (etat = es) Þ ((feuA=rouge Ú feuB=rouge) Ù feuA¹feuB)

initialization
 etat, feuA, feuB := hs, orange, orange

operations
 MiseEnService º
  pre  etat=hs then
   any  f1, f2 where
    f1Î Couleurs Ù f2Î Couleurs Ù
    (f1=rouge Ú f2=rouge) Ù f1¹f2
   then
    etat, feuA, feuB := es, f1, f2

 Changement º
  pre  etat=es then
   any  f1, f2 where
    f1Î Couleurs Ù f2Î Couleurs Ù
    (f1=rouge Ú f2=rouge) Ù f1¹f2 Ù
    (f1= Succ(feuA))Ú (f2= Succ(feuB))
   then
    feuA, feuB := f1, f2
Remarquez que dans l'opération de changement de couleur, on a rajoutéune propriétéde vivacité : la formule f1= Succ(feuA))Ú (f2= Succ(feuB) énonce que la couleur d'un des deux feux au moins change effectivement.



Correction des machines abstraites

La communautéB a l'habitude de décrire une machine abstraite comme un systéme possédant un état initial et offrant àson utilisateur une série de boutons (les opérations) permettant d'agir sur cet état. La correcion d'une machine est établit vis-à-vis de l'invariant :
  1. L'initialisation doit établir l'invariant.
  2. Chaque opération doit conserver l'invariant.
Soit le schéma de machine suivant :
machine
 M
sets
 X
variables
 x
invariant
 I
initialization
 S0
operations
 O1 º
  pre j  then S1
Puisque l'initialisation et les opérations sont définies comme des substitution, la correction de la machine M est exprimée par les formules obtenues n appliqunt ces substitutions àla formule de l'inivariant :
  1. [S0]I
  2. IÙj Þ [S1]I

Correction de CARREFOUR

Décomposons la formule de l'invariant de CARREFOUR en trois conjonctions : I1Ù I2Ù I3 avec
Initialisation
Posons
S0º etat, feuA, feuB := hs, orange, orange
L'obligation de preuve de correction de l'initialisation est la formule obtenue en développant l'application
[S0](I1Ù I2Ù I3)
Comme dans la substitution multiple S0, aucune des variables apparaissant àgauche du signe :=n'apparaît àsa droite, on peut traiter cette substitution multiple comme une substitution simple et affirmer que notre obligation de preuve est logiquement équivalente àla formule obtenue en développant
[S0]I1Ù [S0]I2Ù [S0]I3
Nous utiliserons par la suite systématiquement cette équivalence pour simplifier le développement des applications de substitutions multiples.

Il faut donc montrer la validitédes trois formules [S0]I1, [S0]I2 et [S0]I3.
  1. [S0]I1º esÎ EtatsÙ orangeÎ CouleursÙ orangeÎ Couleurs
    qui est trivialement vraie par définition des ensembles Etats et Couleurs.
  2. [S0]I2º (hs = hs) Þ (orange=orange Ù orange=orange)
    qui est on ne peut plus trivialement vraie puisque le vrai entraîne la vrai.
  3. [S0]I2º (es = hs) Þ ((orange=rouge Ú orange=rouge) Ù orange¹orange)
    qui est tout aussi trivialement vraie puisque le faux (es = hs) entraîne n'importe quoi.
MiseEnRoute
Soit S1 la substitution de mise en route, posons
S1º pre j 1 then S1'
avec
j 1º etat=hs
S1' º any f1,f2 where y11Ùy12 then S1''
et
y11º f1Î Couleurs Ù f2Î Couleurs
y12º (f1=rouge Ú f2=rouge) Ù f1¹f2
S1'' º etat, feuA, feuB := es, f1, f2
Il faut montrer que IÙj 1Þ [S1']I, c'est-à-dire, IÙj 1Þ " f1, f2.(y11Ùy12Þ [S1'']I). Pour ce, on suppose I et j 1 et on se donne f1 et f2 telles que y11 (ie f1Î Couleurs Ù f2Î Couleurs) et y12 (ie (f1=rouge Ú f2=rouge) Ù f1¹f2) ; reste àmontrer [S1'']I, c'est-à-dire [S1'']I1, [S1'']I2 et [S1'']I3.
  1. [S1'']I1º esÎ EtatsÙ f1Î CouleursÙ f2Î Couleurs.
    On a esÎ Etats par définition de l'ensemble Etats ; et f1Î Couleurs et f2Î Couleurs par hypothèse (y11).

  2. [S1'']I2º (es = hs) Þ (f1=orange Ù f2=orange)
    Ce qui est trivialement vrai puisque es ¹hs.
  3. [S1'']I3º (es = es) Þ ((f1=rouge Ú f2=rouge) Ù f1¹f2).
    Ce qui est rendu vrai par l'hypothèse y12.
Changement
Soit S2 la substitution définissant le changement de couleur des feux, posons
S2º pre j 2 then S2'
avec
j 2º etat=hs
S2' º any f1,f2 where y21Ùy22Ùy23 then S2''
et
y21º f1Î Couleurs Ù f2Î Couleurs
y22º (f1=rouge Ú f2=rouge) Ù f1¹f2
y23º (f1= Succ(feuA))Ú (f2= Succ(feuB))
S2'' º feuA, feuB := f1, f2
Il faut montrer que IÙj 2Þ [S2']I, c'est-à-dire, IÙj 2Þ " f1, f2.(y21Ùy22Ùy23Þ [S2'']I). Pour ce, on suppose I et j 2 et on se donne f1 et f2 telles que y21 (ie f1Î Couleurs Ù f2Î Couleurs), y22 (ie (f1=rouge Ú f2=rouge) Ù f1¹f2) et y23 (ie (f1= Succ(feuA))Ú (f2= Succ(feuB))) ; reste àmontrer [S2'']I, c'est-à-dire [S2'']I1, [S2'']I2 et [S2'']I3.
  1. [S2'']I1º etatÎ EtatsÙ f1Î CouleursÙ f2Î Couleurs.
    On a etatÎ Etats par l'hypothèse I et f1Î Couleurs et f2Î Couleurs par y21.
  2. [S2'']I2º (etat = hs) Þ (f1=orange Ù f2=orange).
    Par l'hypothèse j 2 (la précondition), on a que etat=es. Ce qui rend trivialement vraie l'implication recherchée.
  3. [S2'']I3º (etat = es) Þ ((f1=rouge Ú f2=rouge) Ù f1¹f2).
    Ce qui nous est donnépar I et y22
Commentaire sur cette première machine abstraite
Les deux opérations de la machine CARREFOUR peuvent se paraphraser par : << n'importe quelles valeurs qui satisfasse au moins l'invariant >>. Dés lors, la correction est facilement acquise.

Néanmoins, dans les preuves de correction, il faut noter comment la précondition intervient, rendant caduque l'examen de certains cas de l'invariant (ici, etat=hs, par exemple).



Raffinement

On peut raffiner une machine sur deux points : les données ou(inclusif) les opérations (c'est àdire, les substitutions). Le raffinement des substitutions porte àson tour sur deux points : affaiblissement des préconditions ou levée de l'indéterminisme. Il va de soit que dans la plus part des cas, le raffinement de données imlique un raffinement d'opération. Une machine et son raffinement définissent les mêmes opérérations, seuls peuvent changer les variables, les ensembles et les substitutions définissant les opérations.

Lorsque l'on passe d'une machine àson raffinement, on change d'espace de variables. Pour pouvoir contrôler la légitimitédu raffinement, il faut établir le lien entre l'état interne (ie les variables) de la machine originale et celui de son raffinement. On exprime ce lien comme une sous-formule de l'invariant du raffinement que l'on appelle invariant de liaison. Cette sous-formule utilise aussi bien les variables de la machine originale que celles du raffinement.

Un raffinement n'est un raffinement correct que s'il conserve la validitédes propriétés de la machine d'origine. La méthode B permet de s'assurer de la légitimitéd'un raffinement en engendrant des obligations de preuves énonçant cette propriétéde conservation.



Obligations de preuve

Supposons donc une machine M dont l'invariant est I, l'initialisation est S0 et n'ayant qu'une seule opération définie par la substitution conditionnelle pre j  then S1. Supposons également que cette machine est correcte. C'est àdire que l'on a effectivement démontréque les substitution S0 et pre j  then S1 conservent l'invariant I qui exprime les propriétés statiques du système que l'on a en tête.

Soit alors, une machine R dont l'invariant (de liaison) est J, l'initialisation est T0 et définissant la même opération que M, mais par la substitution pre y then T1. Pour établir que R est un raffinement de M, il faut s'assurer que celle-làvérifie encore les propriétés que vérifie celle-ci. En particulier, R doit satisfaire, en quelque sorte, I. Mais puisque M et R sont deux machines distinctes, elles agissent sur des variables différentes. La machine R ne peut donc directement établir I. Pour, depuis la machine R pouvoir parler des variables de M, on utilise le bien nomméinvariant de liaison J dont le rôle est justement de mettre en relation les variables de M et de R.

Formellement, les obligations de preuve engendrées par le raffinement de M par R sont
  1. [T0]¬[S0]¬ J et
  2. IÙ JÙ j ÞyÙ [T1]¬[S1]¬ J
Pour comprendre intuitivement ce que signifient ces formules nous allons supposer que M travaille sur une variable x appartenant àun ensemble X, et R, sur une variable y appartenant àun ensemble Y. L'invariant de liaison J exprime une relation (au sens mathématique du terme) entre X et Y. Une susbtitution peut également être vue comme une relation : la relation qui àchaque valeur initiale d'une variable associe la valeur obtenue par application de la substitution. Soit alors une substitution S de M et T une substitution de R. Si nous appelons x' l'ensemble des valeurs associées àxÎ X par S et y' l'ensemble des valeurs associées ày par T, si nous appelons x'' l'ensemble des valeurs que J associe ày' dans X alors la formule [T]¬[SJ nous dit que :
il n'est pas possible que x'' ne soit pas une partie de x'
La figure ci-dessous illustre cet énoncé. Puisque l'on a supposéque M est correcte, ses substitutions établissent I. L'ensemble de valeurs x' satisfait donc I. En démontrant l'obligation de preuve de raffinement [T]¬[SJ on obtient donc bien que x'' satisfait également I.

Dans l'obligation de preuve concernant les opérations apparaissent explicitement leur préconditions ; celle de l'opération de M en antécédant et celle de l'opération de R en conséquent. Il faudra donc prouver, en particulier, que IÙ JÙ j Þy. Ce qui nous donnera que y (la précondition du raffinement) est satisfaite chaque fois que j (la précondition originelle) est satisfaite. On pourra alors utiliser de façon licite, àla fois les substitutions T1 et S1.

Nous allons àprésent illustrer successivement comment utiliser nos deux formes de raffinements en revenant àl'exemple de gestion des feux d'un carrefour.



Raffinement d'opération

Nous allons préciser de faç définitive l'opération de mise en service et de façon encore transitoire l'opération de changement de couleur.



Substitutions généralisées en plus

Pour notre seconde opération, nous allons utiliser deux nouvelles substitutions généralisées.

Substitution gardée
Comme la substitution conditionnelle, la substitution gradée est une substitution soumise àla satisfaction d'une certaine condition, ou, plus précisément, un certain test. La différence est que dans le cas de la substitution conditionnelle, c'est àl'utilisateur extérieur de vérifier la validitéde la condition avant d'utiliser la substitution, alors que dans le cas de la substitution gardée, c'est la substitution elle-même qui doit vérifier la validitédu test. Cette différence est manifeste lorsque l'on regarde comment une substitution gardée s'applique àune formule.

Si j est une formule et S une substitution, la substitution gardée s'écrit
when j  then S
On définit l'application de la substitution gardée par
[when j  then S]Fº j Þ[S]F
Alors que l'application d'une substitution conditionnelle engendrait une conjonction, nous avons ici une implication. Dans le cas de la substitution gardée, la formule de garde y décrit un état interne pouvant ne pas apparaître. Dans ce cas, on ne << déclanchera >>pas la substitution S. On n'a donc pas d'obligation de toujours satisfaire j . Il suffit de s'assurer que chaque fois que j est vraie, S établit bien F. C'est bien ce qu'exprime le connecteur propositionnel Þ.

Substitution àchoix borné
La substitution àchoix bornéest une autre sorte de substitution indéterminée. Elle permet de pouvoir utiliser au choix un nombre fini de substitutions. Elle s'écrit
choice S1 or ... or Sn
Chacune des substitutions proposées par un choix bornédoit pouvoir être utilisée de façon indifférente. On définit donc l'application d'une substituiton àchoix bornépar une conjonction :
[choice S1 or ... or Sn]F º [S1]FÙ...Ù[Sn]F


Le raffinement CARREFOUR1

Pour raffiner l'opération de mise en service, nous allons choisir arbitrairement de mettre un feu au vert et l'autre au rouge.
L'idée du raffinement de l'opération de changement de couleur est de spécifier la fonction Succ comme substitution décrivant les divers cas de figures déterminés par les équations qui définissent la fonction Succ. C'est une première étape vers l'implémentation de la fonction mathématique Succ.

Une machine en raffinant une autre n'est pas nommée par la rubrique machine, mais par la rubrique refinement. De plus, elle doit obligatoirement contenir une rubrique additionnelle refines indiquant quelle machine elle raffine.

refinement
 CARREFOUR1

refines
 CARREFOUR

sets
 Etats = {hs, es}
 Couleurs = {vert, orange, rouge}

variables
 etat1, feuA1, feuB1

invariant
 etat1=etat Ù
 feuA1=feuA Ù
 feuB1=feuB

initialization
 etat1, feuA1, feuB1 := hs, orange, orange

operations
 MiseEnService º
  pre  etat1=hs then
   etat1, feuA1, feuB1 := es, rouge, vert

 Changement º
  pre  etat1=es then
   choice
    when feuA1=vert then feuA1 := orange
   or
    when feuA1=orange then feuA1,feuB1 := rouge,vert
   or
    when feuA1=rougeÙ feuB1=vert then feuB1 := orange
   or
    when feuA1=rougeÙ feuB1=orange then feuA1,feuB1 := vert,rouge

Obligations de preuves

Nous donnons, sans tout le détail de leur calcul, les obligations de preuves engendrées.

Initialisation
On obtient la formule triviale
¬¬(hs=hs Ù orange=orange Ù orange=orange)

Mise en service
Appelons J l'invariant de CARREFOUR1. On obtient la formule
IÙ J Ù etat=hs Þ
  etat1=hs Ù
  ¬(" f1, f2.(f1Î Couleurs Ù f2Î Couleurs Ù (f1=rouge Ú f2=rouge) Ù f1¹f2Þ
      ¬(es=es Ù f1=rouge Ù f2=vert)))
Ce qui donne, en simplifiant les négations
IÙ J Ù etat=hs Þ
  etat1=hs Ù
  $ f1, f2.(f1Î Couleurs Ù f2Î Couleurs Ù (f1=rouge Ú f2=rouge) Ù f1¹f2Ù
      es=es Ù f1=rouge Ù f2=vert)
On obtient etat1=hs de etat1=etat (cf J) et l'hopothèse etat=hs.
On obtient le second membre de la conjonction en prenant rouge et vert pour valeurs respectives de f1 et f2.

Il convient de noter ici comment la quatification universlle de ls substitution déterminée est devenue une quantification existentielle par le jeu de la négation qu'introduit l'obligation de preuve de raffinement. Pour montrer la validitéd'une formule existentielle, il nous faut exhiber au moins une valeur satisfaisante (ou, àtout le moins montrer qu'on ne peut pas supposer qu'il n'en existe pas, si l'on raisonne par l'absurde). Ainsi l'obligation de preuve du raffinement rajoute de l'information par rapport àla correction de la machine originelle.

Changement de couleur
Soit S2 la substitution définissant le changement de couleur des feux de la machine initiale CARREFOUR, posons
S2º pre j 2 then any f1,f2 where y2 then S2'
Calculons, dans un premier temps ¬[S2]¬ J. En simplifiant les négations, on obtient
$ f1, f2.(y2Ù [S2']J)
Soit maintenant T2 la substitution définissant l'opération de changement de couleur dans le raffinement CARREFOUR1. Posons
T2º pre j 2 then choice T1or T2or T3or T4'
Avec, pour chaque iÎ[1..4],
Ti' º when gi then T''i
L'application [T2]$ f1, f2.(g2Ù [S2']J) nous donne la conjonction
(g1Þ $ f1, f2.(y2Ù [T''1][S2']J)) Ù
(g2Þ $ f1, f2.(y2Ù [T''2][S2']J)) Ù
(g3Þ $ f1, f2.(y2Ù [T''3][S2']J)) Ù
(g4Þ $ f1, f2.(y2Ù [T''4][S2']J))
Au final, l'obligation de preuve concernant l'opération de changement de couleur s'écrit
IÙ J Ù etat=es Þ
  (etat1=esÙ
  (g1Þ $ f1, f2.(y2Ù [T''1][S2']J)) Ù
  (g2Þ $ f1, f2.(y2Ù [T''2][S2']J)) Ù
  (g3Þ $ f1, f2.(y2Ù [T''3][S2']J)) Ù
  (g4Þ $ f1, f2.(y2Ù [T''4][S2']J))
On obtient etat1=es comme précédemment.
Traitons le premier des quatre autres membres de la conjonction et laissons les derniers en exercice. Rappelons que
g1º feuA1= vert
que
y2º f1Î Couleurs Ù f2Î Couleurs Ù
  (f1=rouge Ú f2=rouge) Ù f1¹f2 Ù
  (f1= Succ(feuA))Ú (f2= Succ(feuB))
et que
[T''1][S2']J º etat1=es Ù orange=f1Ù feuB1=f2
Il faut donc montrer que sous les hypothèses I, J, etat=es et feuA1= vert, on peut trouver deux valeurs pour f1 et f2 telles que l'on ait la conjonction
f1Î Couleurs Ù f2Î Couleurs Ù
(f1=rouge Ú f2=rouge) Ù f1¹f2 Ù
(f1= Succ(feuA))Ú (f2= Succ(feuB)) Ù
etat1=es Ù orange=f1Ù feuB1=f2
Le choix de f1 est simple, puisqu'il faut satisfaire que orange=f1. Pour ce qui est de f2, on peut faire le choix minimaliste en prenant f2=feuB. Notre obligation de preuve se résume alors àla vérification des neufs formules suivantes.
  1. orangeÎ Couleurs
    Trivial par définition de Couleurs.
  2. feuBÎ Couleurs
    On l'a par hypothèse I.
  3. orange=rouge Ú feuB=rouge
    On ne pourra pas avoir orange=rouge, montrons donc feuB=rouge : On a l'hypothèse feuA1= vert ainsi que (par J) feuA1=feuA. On a donc que feuA = vert. Or I nous dit que feuA=rouge Ú feuB=rouge, comme feuA=vert¹rouge, il faut bien que feuB=rouge.
  4. orange¹feuB
    On l'obtient en montrant comme ci-dessus qu'en fait feuB=rouge.
  5. orange=Succ(feuA) Ú feuB=Succ(feuB)
    Trivial en utilisant l'hypothèse feuA = vert.
  6. etat1=es
    On l'a déjàmontré...
  7. orange=orange
    Sans commentaire...
  8. feuB1=feuB
    On l'a par l'hypothèse J.

Raffinement de données

Une spécification manipule en général des données abstraites : des ensembles. Une étape importante du raffinement est donc d'obtenir une repréntation concrètes des données.

Nous illustrons cette catégorie particulière de raffinement sur notre exemple en introduisant l'usage de valeurs booléennes en place des états et couleurs. Ceci implique restructuration de la représentation des feux qui àson tour implique une reformulation des opérations. Cependant, pour minimiser l'impact de la modification de représentation des données, nous conserverons la structure logique des substitutions définissant les opérations.

La relation entre les variables de la machine originale et de son raffinement est exprimée dans l'invariant du raffinement.



Le raffinement CARREFOUR2

Puisque l'état du système est àvaleur dans un ensemble àdeux élément, on peut utiliser des booléens. De plus, on peut représenter chaque feux par un triplet de booléens dont, suivant l'intuition, chaque composante correspond àune lampe, éteinte ou allumée, d'une couleur donnée. Chaque lampe pouvant prendre deux valeurs, on utilise ici encore les booléens.

L'ensemble des booléens est supposé(pré)défini par : BOOL = {true, false}.

refinement
 CARREFOUR2

refines
 CARREFOUR1

variables
 etat2, Av, Ao, Ar, Bv, Bo, Br

invariant
 etat2Î BOOL Ù
 Av, Ao, Ar Î BOOL× BOOL× BOOL Ù
 Bv, Bo, Br Î BOOL× BOOL× BOOL Ù

 (etat2=true Û etat1=es) Ù
 (feuA1=vertÛ Av=true Ù Ao=false Ù Ar=false) Ù
 (feuA1=orangeÛ Av=false Ù Ao=true Ù Ar=false) Ù
 (feuA1=rougeÛ Av=false Ù Ao=false Ù Ar=true) Ù
 (feuB1=vertÛ Bv=true Ù Bo=false Ù Br=false) Ù
 (feuB1=orangeÛ Bv=false Ù Bo=true Ù Br=false) Ù
 (feuB1=rougeÛ Bv=false Ù Bo=false Ù Br=true)

initialization
 etat2 := false  || 
 Av, Ao, Ar := false, true, false  || 
 Bv, Bo, Br := false, true, false

operations
 MiseEnService º
  pre  etat2=false then
   etat2 := true  || 
   Ao, Ar := false, true  || 
   Bv, Bo := true, false

 Changement º
  pre  etat2=true then
   choice
    when Av=true then Av, Ao := false, true
   or
    when Ao=true then  Ao, Ar := false, true  ||  Bv, Br := true, false
   or
    when Ar=trueÙ Bv=true then Bo, Bv := true, false
   or
    when Ar=trueÙ Bo=true then Ar, Av := false, true  ||  Bo, Br := false, true

Obligations de preuves

Les obligations de preuves vont ici essentiellement contrôler la cohérence de l'usage de la nouvelle représentation des données dans les opérations vis-à-vis de la façon dont l'invariant définit cette représentation.

Initialisation
On obtient, en simplifiant la double négation,
falseÎ BOOL  Ù
false, true, false Î BOOL× BOOL× BOOL  Ù
false, true, false Î BOOL× BOOL× BOOL  Ù
 
(false=true Û hs=es)  Ù
(orange=vertÛ false=true  Ù  true=false  Ù  false=false)  Ù
(orange=orangeÛ false=false  Ù  true=true  Ù  false=false)  Ù
(orange=rougeÛ false=false  Ù  true=false  Ù  false=true)  Ù
(orange=vertÛ false=true  Ù  true=false  Ù  false=false)  Ù
(orange=orangeÛ false=false  Ù  true=true  Ù  false=false)  Ù
(orange=rougeÛ false=false  Ù  true=false  Ù  false=true)
On vérifie facilement la validitéde la formule obtenue sachant que deux propositions sont équivalentes lorsqu'elles ont même valeur de vérité ; soit vrai, soit faux.

Mise en service
Nous avons présentéles obligations de preuve de correction du raffinement dans le cadre simplifiéd'une machine originale et d'un raffinement de celle-ci. Ici, Nous sommes en présence de trois machines dans une relation de raffinement mutuelle : CARREFOUR Ê CARREFOUR1 Ê CARREFOUR2 (pour reprendre la notation de 4). Les obligation de preuves établissant que CARREFOUR1 Ê CARREFOUR2 doivent prendre en compte l'invariant de CARREFOUR puisqu'en fin de compte, c'est toujours cette machine originale que nous raffinons. D'ailleurs, une formule ne faisant intervenir que les invariant de CARREFOUR2 et CARREFOUR1 aurait peu de sens puisque l'invariant (de liaison) de CARREFOUR1 fait référence aux variables de CARREFOUR.

Si K est l'invariant de CARREFOUR2, si on écrit son opération de mise en service sous la forme
pre etat2=false then U1
si l'on écrit l'opération de mise en service de CARREFOUR1 sous la forme
pre etat1=hs then T1
alors la formule àcalculer pour obtenir l'obligation de preuve est
IÙ JÙ KÙ etat=hsÙ etat1=hs Þ etat2=falseÙ [U1]¬[T1K
Ce qui donne
IÙ JÙ KÙ etat1=hs Þ
  etat2=false Ù
  trueÎ BOOL  Ù
  Av, false, true Î BOOL× BOOL× BOOL  Ù
  true, false, Br Î BOOL× BOOL× BOOL  Ù
  (true=true Û es=es)  Ù
  (rouge=vertÛ Av=true Ù false=false Ù true=false)  Ù
  (rouge=orangeÛ Av=false Ù false=true Ù true=false)  Ù
  (rouge=rougeÛ Av=false Ù false=false Ù true=true)  Ù
  (vert=vertÛ true=true Ù false=false Ù Br=false)  Ù
  (vert=orangeÛ true=false Ù false=true Ù Br=false)  Ù
  (vert=rougeÛ true=false Ù false=false Ù Br=true)
Seules deux formules méritent ici un peu d'attention
rouge=rougeÛ Av=false Ù false=false Ù true=true
et
vert=vertÛ true=true Ù false=false Ù Br=false
Pour en montrer la validité, il faut obtenir que Av=false et que Br=false àpartir des hypothèses I, J, K, etat=hs et etat1=hs. Le raisonnement est le suivant : de J et de etat1=hs, on tire etat=hs ; de I et etat=hs, on tire que feuA et feuB valent orange ; en utilisant les invariants de liaison J et K, on obtient les deux égalités recherchées.

Changement de couleur
Bien qu'un peu fastidieuse, il est intéressant d'étudier l'obligation de preuve du raffinement de l'opération de changement de couleur pour voir comment se distribuent les alternatives des substituions àchoix borné.

Nous reprenons la décomposition de la substitution définissant l'opération de changement de couleur de CARREFOUR1 et introduisons une décomposition analogue pour celle de CARREFOUR2 :
U2º pre j 3 then choice U1or U2or U3or U4'
Avec, pour chaque iÎ[1..4],
Ui' º when di then U''i
Le calcul de [U2]¬[T2K donne, en simplifiant les négations, la conjonction suivante :
(d1Þ ((g1Ù[U''1][T''1]K)Ú (g2Ù[U''1][T''2]K)Ú (g3Ù[U''1][T''3]K)Ú (g4Ù[U''1][T''4]K))) Ù
(d2Þ ((g1Ù[U''2][T''1]K)Ú (g2Ù[U''2][T''2]K)Ú (g3Ù[U''2][T''3]K)Ú (g4Ù[U''2][T''4]K))) Ù
(d3Þ ((g1Ù[U''3][T''1]K)Ú (g2Ù[U''3][T''2]K)Ú (g3Ù[U''3][T''3]K)Ú (g4Ù[U''3][T''4]K))) Ù
(d4Þ ((g1Ù[U''4][T''1]K)Ú (g2Ù[U''4][T''2]K)Ú (g3Ù[U''4][T''3]K)Ú (g4Ù[U''4][T''4]K))) Ù
Notez comment, dans chaque membre de cette conjonction, la négation a introduit une disjonction en place de la conjonction que donnait l'application de T2.

Que le lecteur se rassure, nous n'allons pas traiter exhaustivement cette obligation de preuve> Nous nous contenterons de décrire comment, sous les hypothèses I, J, K, etat=es et etat1=es, on obtient le premier membre de cette conjonction. Les autres se traitent de façon similaire.

Une analyse rapide de la forme obtenue pour [U2]¬[T2K nous porte àpenser que pour chaque di, un seul des membres de la disjonction correspondante doit être pertinent: celui de la forme giÙ[U''i][T''i]K. C'est ce que nous allons établir pour i=1 ; les autres cas se traiteraient de façon similaire..

Supposons donc d1 (ie Av=true) et montrons que g1 (ie feuA1=vert) ainsi que [U''1][T''1]K.
On obtient feuA1=vert de Av=true et K qui dit que le seul cas oùAv a la valeur true, c'est quand feuA1 a la valeur vert.
L'application [U''1][T''1]K se développe en
etat2Î BOOL  Ù
false, true, Ar Î BOOL× BOOL× BOOL  Ù
Bv, Bo, Br Î BOOL× BOOL× BOOL  Ù
(etat2=true Û etat1=es)  Ù
(orange=vertÛ false=true Ù true=false Ù Ar=false)  Ù
(orange=orangeÛ false=false Ù true=true Ù Ar=false)  Ù
(orange=rougeÛ false=false Ù true=false Ù Ar=true)  Ù
(feuB1=vertÛ Bv=true Ù Bo=false Ù Br=false)  Ù
(feuB1=orangeÛ Bv=false Ù Bo=true Ù Br=false)  Ù
(feuB1=rougeÛ Bv=false Ù Bo=false Ù Br=true)
La plus par des élément de cette conjonction sont données par hypothèse ou directement en utilisant que faux est équivalent àfaux. Le seul cas non trivial est celui de l'équivalence
orange=orangeÛ false=false Ù true=true Ù Ar=false
ou il faut obtenir Ar=false. Ce que l'on obtient en utilisant le fait (démontréci-dessus) que Ar=false implique que feuA1=vert. On utilise alors ce résultat et K pour obtenir Ar=false.



L'implantation

L'ultime étape de raffinement reste dans le formalisme des machines abstraites, mais elle est soumise àun certain nombre de restrictions. Nous ne suivrons pas ici complètement la lettre de B, mais plutôt son esprit en nous contentant de les principales de ces restrictions :

Substitutions exécutables

Affectation
L'affectation est tout simplement la substitution simple x := E en restreignant l'expression E àn'utiliser que des opérateurs connus d'un langage de programmation.

Séquence
La séquence des langages de programmations correspond àla composition des substitutions :
[S1; S2]Y º [S2][S1]Y

Conditionnelles
La conditionnelle des langages de programmation est définie comme combinaison de deux substitutions gardées et d'une substitution àchoix borné :
if B then S1 else S2º choice (when B then S1or (when ¬ B then S1)
La condition B doit être exprimée en n'utilisant que les connecteurs propositionnels correspondant aux opérateurs booléens usuels.

L'instruction vide
On a la toujours utile skipque l'on définit comme la substitution identitée qui remplace chaque variable par elle-même.



L'ultime raffinement

En tant que machine particulière, le raffinement correspondant àune implantation est introduit par la clause implementation en place de refinement.

implementation
 CARREFOUR_SYSTEM

refines
 CARREFOUR2

variables
 a1, a2, a3, b1, b2, b3

invariant
 a1Î BOOLÙ a2Î BOOLÙ a3Î BOOL  Ù
 b1Î BOOLÙ b2Î BOOLÙ b3Î BOOL  Ù

 a1=AvÙ a2=AoÙ a3=Ar  Ù
 b1=BvÙ b2=BoÙ b3=Br

initialization
 a1 := false; a2 := true; a3 := false;
 b1 := false; b2 := true; b3 := false

operations
 MiseEnService º
  a2 := false; a3 := true;
  b1 := true; b2 := false

 Changement º
  if  a1=true  then
   a1 := false; a2 := true
  else if  a2=true  then
   a2 := false; a3 := true;
   b1 := true; b3 := false
  else if  b1=true  then
   b1 := false; b2 := true
  else
   a1 := true; a3 := false;
   b2 := false; b3 := true
Notez que les préconditions ont disparues des définitions des opérations. En effet, une précondition n'est pas destinée àêtre vérifiée par le code du programme àchaque exécution, mais plutôt, a priori par l'utilisateur des opérations fournies par une machine. Une précondition est un pur élément de spécification.



Obligations de preuves

L'invariant de l'implantation est trivial. Nous ne traiterons donc pas l'initialisation ni l'opération de mise en route.

L'obligation de preuve de l'opération de changement de couleurs va nous permettre de vérifier la cohérence de l'analyse de cas spécifiée dans l'implantation vis-à-vis de celle définie précédemment en terme de choix entre substitutions gardées. En particulier, nous allons pouvoir nous assurer de l'exhaustivitédes choix proposés.

Appelons V2 la substitution définissant l'opération de changement de couleur de l'implantation et réécrivonssous la sous la forme :
if  B1 then V'1
else if  B2 then  V'2
else if  B3 then  V'3
else  V'4
Soit L l'invariant de l'implantation, en reprenant U2, la substitution définissant le changement de couleur dans CARREFOUR2, en développant ¬[U2L, on obtient :
(d1Ù [U''1]L)Ú (d2Ù [U''2]L)Ú (d3Ù [U''3]L)Ú (d4Ù [U''4]L)
que nous écrirons plus simplement
Vi=14((diÙ [U''i]L)

Comme nous l'avions remarquépour les substitutions multiples, on peut vérifier que pour chacune des substitutions séquentielles V'j avec jÎ[1..4] l'application [V'j]Vi=14((diÙ [U''i]L) donne une formule logiquement équivalente àVi=14((diÙ [V'j][U''i]L). Posons, pour simplifier les écritures
Fjº Vi=14((diÙ [V'j][U''i]L)
Tenant compte de cette équivalence, le développement [V2]¬[[U2L est lui-même équivalent àla formule :
(B1Þ F1) ÙB1Þ ((B2Þ F2) ÙB2Þ ((B3Þ F3) ÙB3Þ F4)))))
L'implication se distribuant sur la conjonction, et la proposition AÞ(BÞ C) étant équivalente à(AÙ B)Þ C, notre obligation de preuve est elle-même équivalente àla conjonction
(B1Þ F1)  Ù
B1Ù B2Þ F2)  Ù
B1Ù ¬ B2Ù B3Þ F3)  Ù
B1Ù ¬ B2Ù ¬ B3Þ F4)

Les différents cas de cette obligation de preuve se traitent selon la technique appliquée lors de la preuve de correction du raffinement CARREFOUR2 : on vérifie le membre << pertinent >>de la disjonction ; celui ou les indices j et i coïncident.
La nouveautéici est que les tests contiennent plus d'implicite que les gardes. Traitons le dernier cas, qui est celui oùl'implicite culmine.

Rappelons nous que nous travaillons sous l'hypothèse que les invariants I, J et K sont satisfaits et que le système est en service. Nous allons, dans un premier déduire de ¬ B1Ù ¬ B2Ù ¬ B3 la couleur des deux feux.
Intuitivement : puisque chaque feu doit avoir une couleur, si ni a1 ni a2 sont àvrai alors nécessairement a3=true. C'est-à-dire que le premier feu (feuA) est au rouge. Le second (feuB) est donc soit vert soit orange. Comme b1 (la lampe verte) n'est pas àvrai, c'est que le second feu est àl'orange.
De ce résultat, il est facile de déduire que d4 et [V'4][U''4]L sont valides. Ce qui nous donne F4.



Quelques mots pour finir

Le travail réalisésur ce petit exemple peut sembler bien démesuréen regard du résultat atteint : trois petites fonctions (l'initialisation et les deux opérations) dont l'intuition nous était venue dés l'énoncédu problème et que nous aurions donc pu tout aussi bien écrire directement.

Àcette objection, il convient de faire deux réponses :
This document was translated from LATEX by HEVEA.