• Aucun résultat trouvé

4.2 Architecture logicielle

5.2.3 Quelques précisions sur la preuve de raffinement avec la méthode B

5.2.3.1 Des arbres abstraits aux arbres concrets

Un arbre est un mapping (fonction totale) de ses nœuds vers des valeurs. La différence entre arbres abstraits et concrets est que pour premiers, les nœuds abstraits sont représentés par leur chemin depuis la racine tandis que les seconds sont représentés par un couple (h, i) où h est la hauteur du nœud tandis que i est son index dans la liste (ordonnée) des nœuds de même hauteur. Dans un cas on a une représentation mathématique classique (un chemin est une liste) et dans l’autre une représentation pratique pour un calcul plus optimisé.

L’isomorphisme montre l’équivalence des deux représentations : il consiste essentiellement en une bijection entre les nœuds des deux représentation, et on a prouvé formellement que cette bijection préserve toutes les opérations que l’on a défini sur les arbres. Les opérations définies sont pour la plupart des opérations usuelles comme “père”, “fils”, etc. Et elles sont toutes défi- nies sur les deux représentations. La préservation de ces opération par l’isomorphisme assure, par exemple, qu’au père d’un nœud on associe le père de son image.

L’aperçu ci-dessous du contenu des spécifications (simplifié), transcrit en langage “usuel” montre comment cela se traduit concrètement sans rentrer dans les détails syntaxiques et tech- niques. On y considère des arbres N -aires (N fils par nœud interne) de hauteur H. La spécifica- tion des arbres et de quelques fonctionnalités associées est spécifique pour chaque représenta- tion. Ensuite, la définition des autres fonctionnalités est identique pour les deux représentations.

Arbres abstraits

// spécifique à la représentation

A_N odes = suites sur [1, N ]de taille ≤ H dom(A_f ather) = A_N odes non vides ∀ux ∈ dom(A_f ather), A_f ather(ux) = u . . .

// indépendant la représentation A_T rees = A_N odes → V alues

A_read : A_N odes × A_T rees → V alues

∀(n, t) ∈ A_N odes × A_T rees, A_read(n, t) = t(n) . . .

Arbres concrets

// spécifique à la représentation

C_N odes = {(h, i)|h ∈ [0, H] & i ∈ [0, NH−h− 1]}

dom(C_f ather) = {(h, i) ∈ C_N odes | (h + 1, i/N ) ∈ C_N odes} ∀(h, i) ∈ dom(C_f ather), C_f ather(h, i) = (h + 1, i/N )

. . .

// indépendant la représentation C_T rees = C_N odes → V alues

C_read : C_N odes × C_T rees → V alues

∀(n, t) ∈ C_N odes × C_T rees, C_read(n, t) = t(n) . . .

L’isomorphisme entre les deux représentations est une mise en relation des éléments homonymes des deux spécifications. Et pour constituer un isomorphisme, ces relations doivent être bijective et préserver les opérations homonymes. Ainsi la relation entre objets (arbres, nœuds...) est étendue aux fonctions. Concrètement, ces relations sont définies dans les spécifications, et les propriétés d’isomorphismes y apparaissent comme des assertions à prouver. Voici un aperçu de ce à quoi ressemble la spécification de l’isomorphisme.

Isomorphismes

// caractérisation de la relation entre les arbres des deux représentations AC_N odeIso : A_N odes ↔ C_N odes

AC_T reeIso : A_T rees ↔ C_T rees

. . . spécification technique précise deAC_T reeIso . . . . . .

CA_N odeIso : relation inverse de AC_N odeIso CA_T reeIso : relation inverse de AC_T reeIso ∀t ∈ A_T ree, AC_T reeIso(t) = CA_T reeIso ◦ t (rappel : ◦ désigne la composition de fonctions) . . .

// propriétés prouvées : symétries, bijections

AC_N odeIso est une fonction totale et CA_N odeIso est une fonction totale AC_N odeIso est une bijection et CA_N odeIso est une bijection

AC_T reeIso est une fonction totale et CA_T reeIso est une fonction totale AC_T reeIso est une bijection et CA_T reeIso est une bijection

∀t ∈ C_T ree, CA_T reeIso(t) = AC_T reeIso ◦ t . . .

// propriétés prouvées : préservations

∀n ∈ A_N odes, AC_N odeIso(A_f ather(n)) = C_f ather(AC_N odeIso(n)))

∀n ∈ A_N odes, ∀t ∈ A_T rees, (A_read(n, t) = C_read(AC_N odeIso(n), AC_T reeIso(t))) ∀n ∈ C_N odes, CA_N odeIso(C_f ather(n)) = A_f ather(CA_N odeIso(n)))

∀n ∈ C_N odes, ∀t ∈ C_T rees, (C_read(n, t) = A_read(CA_N odeIso(n), CA_T reeIso(t))) . . .

L’isomorphisme a été prouvé avec l’atelier B (preuve du premier ordre). La partie “dif- ficile” est celle qui traite des définitions spécifiques aux représentations (caractère bijectif et correspondance des fonctionnalités de base). Pour toutes les autres fonctions, les preuves ont été faciles car complètement méthodiques du fait que ces fonctionnalités sont définies de la même manière dans les cas concret et abstrait. Comme l’atelier B ne propose pas de facilité particulière pour les isomorphismes, ces preuves méthodiques ont été un peu fastidieuses car manuelles, mais on peut les voir comme automatiques même si la mise en œuvre a été humaine.

La preuve de cet isomorphisme permet de prouver très simplement le premier raffinement. En effet, remplacer les arbres abstraits dans l’algorithme abstrait par les arbres concrets consiste simplement à remplacer les opérations sur les arbres abstraits par les opérations homonymes sur les arbres concrets (A_f ather devient C_f ather, A_sons devient C_sons,etc). Comme ces opérations se comportent de la même manière (“préservées” par l’isomorphisme), les al- gorithmes qui les utilisent aussi. La preuve de l’équivalence des deux algorithmes a été quasi- automatique comme l’avait été celle des opérations définies identiquement dans les deux repré- sentations.

Ci-dessous un aperçu pour l’opération de lecture dans la mémoire (pour l’écriture, c’est très similaire). Techniquement, l’état comprend la mémoire, une variable pour recevoir l’argument de l’opération avant l’appel (le nœud dont on veut la valeur) et une variable pour y trouver le résultat après l’appel (la valeur, en cas de succès). Une fonction “secure” vérifie que la mémoire n’est pas corrompue pour le nœud à lire (condition pour exiger un résultat précis de la part de l’opération de lecture). Lors de son appel, l’opération retourne dans “test” une

indication sur la validité du résultat (non corruption).

Algorithme sur les arbre abstraits

// Variables d’état : (initialisées par des valeurs quelconques) A_M emory : A_T rees

A_N ode_argument : A_N odes A_Result_value : V alues

// Opération de lecture Read (return test) =

IF A_secure(A_N ode_argument, A_M emory)

THEN test := SU CCESS & A_Result_value := A_read(A_N ode_argument, A_M emory) ELSE test := ERROR & A_Result_value := non spécifié, i.e n’importe quelle valeur

END ;

Algorithme sur les arbre concrets, RAFFINE “Algorithme sur les arbre abstraits” // Variables d’état : (initialisées par des valeurs quelconques)

C_M emory : C_T rees

C_N ode_argument : C_N odes C_Result_value : V alues // Opération de lecture Read (return test) =

IF C_secure(C_N ode_argument, C_M emory)

THEN test := SU CCESS & C_Result_value := C_read(C_N ode_argument, C_M emory) ELSE test := ERROR & C_Result_value := non spécifié, i.e n’importe quelle valeur

END

// Invariant de liaison (exprime la relation entre l’état abstrait et l’état concret) A_M emory = CA_T reeIso(C_M emory)

A_N ode_argument = CA_N odeIso(C_N ode_argument) A_Result_value = C_Result_value

La preuve de raffinement assure que, “modulo” la relation entre les deux états,

— Tout état résultant de l’initialisation de la machine concrète correspond à un état possible après l’initialisation de la machine abstraite.

— Toute exécution de l’opération concrète donne un résultat correspondant à un résultat possible de la machine abstraite, à partir de deux situations de départ correspondantes (par l’invariant de liaison).

Le raffinement assure donc que le comportement concret correspond à un comportement abstrait possible donc acceptable. C’est une réduction du non-déterminisme qui traduit un choix d’implémentation parmi ceux autorisés par la spécification, et certains comportement abstraits possibles peuvent donc être éliminés au niveau concret. Dans le cas du passage des arbres abstraits aux concrets, seule la représentation de l’état change et l’algorithmique est conservée sans modification si bien que aucun comportement n’est en fait éliminé (même si ça n’est pas plus prouvé que requis par la logique du raffinement). Lors de la deuxième étape de raffinement, il en va différemment.