Sémantique axiomatique Assignations Composition d’instructions L’instruction "if-else" L’instruction "while" Rétrécissement et élargissement Arrêt L’instruction "if"
Vérification d’un programme La vérification des programmes est faite en deux étapes: L’association d’une formule à chaque étape du programme. La démonstration que le résultat finale est la conséquence logique des conditions initiales, après être passé par les étapes du programme.
Qu’est-ce que la sémantique axiomatique? La sémantique axiomatique des assignations, instructions composés, instructions conditionnelles et instructions itératives fut développée par le professeur C. A. R. Hoare. Les éléments de base sont les formules pour l’assignation et la condition. L’effet des autres instructions est décrit par la règle d’inférence qui combine les formules d’assignations et de conditions (tout comme les instructions sont elle même des combinaisons d’assignations et de conditions).
L’assignation Soit une formule contenant la variable v. v e est alors définie comme la formule obtenue à partir de quand toutes les occurrences de la variable v sont remplacées par l’expression e.
Remplacement, un exemple Avant le remplacement: h >= 0 & h <= n & n > 0 h 0 0 >= 0 & 0 <= n & n > 0 Après le replacement
m == min(ai pour 1 <= i <= k–1) & Autre exemple m == min(ai pour 1 <= i <= k–1) & k–1 <= N k k+1 m == min(ai pour 1 <= i <= (k+1) – 1) & (k+1)–1 <= N m == min(ai pour 1 <= i <= k) & k <= N
L’axiome pour l’assignation {v e } v = e {} Exemple: { 0 >= 0 & 0 <= n & n > 0 } x = 0; { x >= 0 & x <= n & n > 0 }
Deux exercices { ??? } z = z + 1; { z <= N } { a > b } a = a – b; { ??? }
Composition d’instructions Supposons que {´ } S ´ {´´ } et {´´ } S ´´ {´´´ } Alors, on conclu que {´ } S ´ S ´´ {´´´ } En autre mots: {´ } S ´ {´´ } S ´´ {´´´ }
Un exemple x = 0; f = 1; while (x != n) { x = x + 1; f = f * x; } Nous voulons prouver: { f == x! } x = x + 1; f = f * x; { f == x! }
´´´ est f == x! Le factoriel Appliquons la règle d’inférence pour la composition. ´ est f == x! ´´´ est f == x! S ´ est x = x + 1; S ´´ est f = f * x;
´ ´´ Le factoriel (2) S ´ Nous cherchons le ´´ pour lequel: { f == x! } x = x + 1; {´´ } f = f * x; { f == x! } Observons que: f == x! f == ((x + 1) – 1)! Et donc: f == (x – 1)! x x + 1 f == x! { f == x! } x = x + 1; {f == (x – 1)! } ´ ´´ S ´
´´ ´´´ Le factoriel (3) Maintenant, observons que: f == (x – 1)! f * x == (x – 1)! * x == x! Donc, nous avons: f == x! f f * x f == (x – 1)! Et ainsi: {f == (x – 1)! } f = f * x; {f == x! } QED ´´ S ´´ ´´´
L’instruction "if-else" Supposons que { & } S´ {} et { & } S´´ {} Alors, on conclu que {} if ( ) S ´ else S ´´ {} Peu importe le cas choisi par l’instruction ‘if-else’, le résultat est la même formule ‘’. Donc l’instruction ‘if-else’ en entier résulte en la formule ‘’.
"if-else", un exemple L’instruction if ( a < 0 ) b = -a; else b = a; Rend la formule b == abs(a) vrai. Plus spécifiquement: {true} if ( a < 0 ) b = -a; else b = a; { b == abs(a) } est true est b == abs(a) est a < 0 S´ est b = -a; S´´ est b = a;
"if-else", un exemple (2) Considerons chaque cas. Premièrement, si est vrai: true & a < 0 a < 0 – a == abs(a) Alors, d’après l’axiome de l’assignation: {– a == abs(a)} b = -a; {b == abs(a)} De la même façon, si est vrai: true & a < 0 a 0 a == abs(a) Alors: {a == abs(a)} b = a; {b == abs(a)}
"if-else", un exemple (3) Donc S´ et S´´ résultent en: b == abs(a) Nous avons donc prouvé: {true} if ( a < 0 ) b = -a; else b = a; { b == abs(a) } En autre mots, cette expression conditionnelle trouve abs(a). Elle fait avec aucune précondition: "true" signifie qu’il n’y a aucune restriction sur les valeurs initiales de a et b.
L’instruction "while" Supposons que { & } S {} Un invariant de boucle est une condition qui est satisfaite immédiatement avant le début d’une boucle, demeure vrai lors de son exécution, et est toujours satisfaite à la sortie de la boucle. Supposons que { & } S {} [C’est à dire, S préserve (l’invariant de boucle)] Alors, on conclu que { } while ( ) S { & } En autant que la boucle se termine (qu’elle n’est pas infini).
Le factoriel (encore) x = 0; f = 1; while ( x != n ) { x = x + 1; f = f * x; } On sait que n ≥ 0. Après les instructions ce qui implique que f == x! (1 == 0!) Nous avons déjà démontré que: { f == x! } x = x + 1; f = f * x; { f == x! }
Le factoriel (encore) (2) Maintenant, est f == x! est x != n est x == n En utilisant la règle d’inférence pour les boucles "while": { f == x! } while ( x != n ) { x = x + 1; f = f * x; } { f == x! & x == n}
Le factoriel (encore) (3) Alors après la boucle, on a: f == x! & x == n f == n! Ainsi: { true } x = 0; f = 1; { f == x! } et: { f == x! } while ( x != n ) { x = x + 1; f = f * x; } { f == n!}
Le factoriel (encore) (4) Donc, nous avons montré que la boucle détermine f == n!, avec aucune précondition sur les valeurs initiales de f et n, tant que n ≥ 0. Ainsi, avec l’axiome de la composition: { true } x = 0; f = 1; while ( x != n ) { x = x + 1; f = f * x; } { f == n!} Donc, le programme calcule le factoriel de n.
Le factoriel (encore) (5) Le résonnement est le suivant pour la boucle: une variable est ajustée rendant l’invariant temporairement faux, mais un autre ajustement est fait qui rétablie l’invariant. Donc l’invariant est vérifié à la fin de la boucle. { f == x! } x = x + 1; {f == (x – 1)! } L’invariant est maintenant temporairement faux {f == (x – 1)! } f = f * x; {f == x! } L’invariant est maintenant rétablie Ce raisonnement n’est pas valide pour les boucles infinie: la condition & n’est pas atteinte, et la situation est indéterminée suivant la boucle.
Rétrécissement et élargissement Supposons que ´ et {} S { } Alors, on conclu que {´ } S { } Supposons que {} S { } et ´ Alors, on conclu que { } S { ´ } Ces règles peuvent permettre de rétrécir une précondition, ou d’élargir une postcondition.
Rétrécissement et élargissement, un exemple Nous avons vu que n! est calculé pour tout n>=0, avec {true} comme précondition (le calcul réussit toujours); Donc, selon l’axiome du rétrécissement, le calcul de n! réussira également pour n == 5.
Autre exemple { N >= 1 } { N >= 1 & 1 == 1 & a1 == a1 } i = 1; s = a1; { N >= 1 & i == 1 & s == a1 } { N >= 1 & s == a1 + … + ai } INVARIANT while ( i != N ) { { N >= 1 & s == a1 + … + ai & i != N } i = i + 1; { N >= 1 & s == a1 + … + ai–1 & i – 1 != N } s = s + ai; { N >= 1 & s == a1 + … + ai } } { N >= 1 & s == a1 + … + ai & i == N } { N >= 1 & s == a1 + … + aN }
Autre exemple (2) Nous avons montré que ce programme calcul la somme de a1, ..., aN. La précondition N >= 1 est seulement nécessaire pour démontrer la terminaison.
Arrêt Les preuves précédentes sont seulement partiellement correcte. Il reste a démontrer que les boucles se terminent. Une preuve correcte doit également montrer que toutes les boucles auront toujours un nombre fini d’itérations. On peut montrer qu’une boucle se terminera en montrant que chaque itération nous rapproche de la condition d’arrêt.
Encore le factoriel Initialement, x == 0. Chaque itération incrémente x de 1, donc, on passe par les nombres: 0, 1, 2, ... n >= 0 sera nécessairement un de ces nombres Ce raisonnement ne fonctionnerait pas pour n < 0, et la condition d’arrêt ne serait pas atteinte pour ces valeurs.
Une fonction décroissante Une boucle termine quand une fonction des variables du programme atteint 0 lors de l’exécution de la boucle. Pour le factoriel, la fonction pourrait être n – x. La valeur de la fonction est initialement n et diminue de 1 à chaque itération. Pour la somme, on peut choisir N – i.
Multiplication par des additions successives { B >= 0 & B == B & 0 == 0} POUR L’ARRÊT b = B; p = 0; { b == B & p == 0 } { p == A * (B – b) } INVARIANT while ( b != 0 ) { p = p + A; { p == A * (B – (b – 1)) } b = b - 1; { p == A * (B – b) } } { p == A * (B – b) & b == 0} { p == A * B } La boucle termine, puisque la valeur de b atteint 0.
Exemple Montrons que: p = a; a = b; b = p; échange les valeurs de a et b : { a == A & b == B } { b == A & a == B } Les étapes de la preuve: { a == A & b == B } p = a; { p == A & b == B } a = b; { p == A & a == B } b = p; { b == A & a == B }
Exemple Quel est l’effet de la suite d’instruction suivante sur les variables entières x, y: x = x + y; y = x - y; x = x - y;
Exemple (suite) {x == X & y == Y } {x + y == X + Y & y == Y } x = x + y; {x == X + Y & y == Y } {x == X + Y & x - y == X } y = x - y; {x == X + Y & y == X } { x - y == Y & y == X } x = x - y; { x == Y & y == X }
Le plus grand commun diviseur { X > 0 & Y > 0 } a = X; b = Y; { } que devrait-être l’invariant? while ( a != b ) { & a != b } { if ( a > b ) { & a != b & a > b } a = a - b; else { & a != b & (a > b) } b = b - a; } { & (a != b) } { PGCD( X, Y ) == a }
PGCD (2) On aura besoin de ces propriétés des pgcd: PGCD( n + m, m ) == PGCD( n, m ) PGCD( n, m + n ) == PGCD( n, m ) La première étape (de façon très formelle): { X > 0 & Y > 0 } { X > 0 & Y > 0 & X == X & Y == Y } a = X; b = Y; { a > 0 & b > 0 & a == X & b == Y }
PGCD (3) Quand la boucle termine, on a: a == b & PGCD( a, b ) == a On voudra probablement donc cette condition dans l’invariant: a == b & PGCD( X, Y ) == PGCD( a, b ) Au début de la boucle: { a > 0 & b > 0 & a == X & b == Y } {a > 0 & b > 0 & PGCD( X, Y ) == PGCD( a, b ) } Donc l’invariant sera: a > 0 & b > 0 & PGCD( X, Y ) == PGCD( a, b )
PGCD (4) On veut prouver que {a > 0 & b > 0 & PGCD(X, Y) == PGCD(a, b) & a != b} while ...... {a > 0 & b > 0 & PGCD(X, Y) == PGCD(a, b)} La condition finale sera donc: a > 0 & b > 0 & PGCD(X, Y) == PGCD(a, b) & a == b Ce qui implique: PGCD( X, Y ) == a
PGCD (5) La boucle consiste en une instruction conditionnelle. La preuve sera faite si on démontre: {a > 0 & b > 0 & PGCD(X, Y) == PGCD(a, b) & a != b} if ( a > b ) a = a - b; else b = b - a; {a > 0 & b > 0 & PGCD(X, Y) == PGCD(a, b)}
PGCD (6) Considérons le cas a > b: {a > 0 & b > 0 & PGCD(X, Y) == PGCD(a, b) & a != b & a > b } {a – b > 0 & b > 0 & PGCD(X, Y) == PGCD(a – b, b)} a = a - b; {a > 0 & b > 0 & PGCD(X, Y) == PGCD(a, b)}
PGCD (7) Maintenant, le cas a > b. {a > 0 & b > 0 & PGCD(X, Y) == PGCD(a, b) & a != b & (a > b) } {a > 0 & b – a > 0 & PGCD(X, Y) == PGCD(a, b – a)} b = b - a; {a > 0 & b > 0 & PGCD(X, Y) == PGCD(a, b)}
PGCD (8) Les deux cas du if-else emmènent la même condition finale. Il reste seulement à montrer que la boucle termine. On montre que max( a, b ) diminue à chaque itération de la boucle. Soit a == A, b == B au début d’une itération. Supposons d’abord que a > b: max( a, b ) == A, donc a – b < A, b < A, donc max( a – b, b ) < A.
PGCD (9) Et si a < b: max( a, b ) == B, b – a < B, a < B, donc max( a, b – a ) < B. Puisque a > 0 et b > 0, max( a, b ) > 0. Donc la diminution de a et b ne peut pas se poursuivre indéfiniment. QED
L’instruction "if" & Supposons que { & } S { } et & Alors, on conclu que { } if ( ) S { }
Un exemple de "if" Démontrons que: { N > 0 } k = 1; m = a1; while ( k != N ) { k = k + 1; if ( ak < m ) m = ak; } { m == min( 1 <= i & i <= N: ai ) }
Minimum (1) Le fait que la boucle se termine est évidant: N – k vers zéro. L’invariant de la boucle: à la kième itération, après avoir inspecté a1, ..., ak, on sait que m == min( 1 <= i & i <= k : ai ). Initialement, on a: { N > 0 } k = 1; m = a1; { k == 1 & m == a1 } { k == 1 & m == min( 1 <= i & i <= k : ai ) }
Minimum (2) Nous devons donc démontrer: { m == min( 1 <= i & i <= k : ai ) & k != N } k = k + 1; if ( ak < m ) m = ak; { m == min( 1 <= i & i <= k : ai ) }
Minimum (3) { m == min( 1 <= i & i <= k : ai ) & k != N } k = k + 1; { m == min( 1 <= i & i <= k – 1: ai ) & k – 1 != N } Notons que k – 1 != N assure l’existence de ak.
Minimum (4) Il reste à démontrer: { m == min( 1 <= i & i <= k – 1: ai ) & k – 1 != N } if ( ak < m ) m = ak; { m == min( 1 <= i & i <= k: ai ) } On utilisera le fait que: min( 1 <= i & i <= k: ai ) == min2( min( 1 <= i & i <= k – 1: ai ), ak )
Minimum (5) Considérons les deux cas de l’expression conditionnelle. Premièrement, (ak < m). {m == min(1 <= i & i <= k – 1: ai ) & k – 1 != N & (ak < m)} {m == min2(min( 1 <= i & i <= k – 1: ai ), ak )} {m == min(1 <= i & i <= k: ai )}
Minimum (6) Deuxièmement, ak < m. {m == min(1 <= i & i <= k – 1: ai ) & k – 1 != N & ak < m} {ak == min2( min( 1 <= i & i <= k – 1: ai ), ak )} {ak == min(1 <= i & i <= k: ai )} m = ak; {m == min(1 <= i & i <= k: ai )} Donc la boucle préserve la condition m == min( 1 <= i & i <= k: ai )
Minimum (7) La boucle en entier fonctionne donc ainsi: { m == min( 1 <= i & i <= k: ai ) } while ( k != N ) } k = k + 1; if ( ak < m ) ak = m; } { m == min( 1 <= i & i <= k: ai ) & k == N } { m == min( 1 <= i & i <= N: ai ) } Nous avons montré que le programme trouve le minimum parmi N nombres, si N > 0. QED
Encore une boucle "while" Exemples Encore une boucle "while" { B > 0 } POUR LA TERMINAISON b = 1; p = A; while ( b != B ) { b = b + 1; p = p * A; } { ??? }
Encore une boucle "while“ (2) Exemples Encore une boucle "while“ (2) { B > 0 & 1 == 1 & A == A} POUR LA TERMINAISON b = 1; p = A; { b == 1 & p == A } { p == A ** b } INVARIANT while ( b != B ) { b = b + 1; { p == A ** (b - 1) } p = p * A; { p == A ** b } } { p == A ** b & b == B} { p == A ** B } La boucle se termine: la valeur B - b devient 0.
Un autre example avec "if" Exemples Un autre example avec "if" { N > 0 } POUR LA TERMINAISON k = 1; while ( k != N ) { if ( Ak > Ak+1 ) { p = Ak; Ak = Ak+1; Ak+1 = p; } k = k + 1; } { ??? }
Un autre example avec "if“ (2) Exemples Un autre example avec "if“ (2) { N > 0 } FOR TERMINATION k = 1; { Ak == max( 1 <= i & i <= k: Ai ) } INVARIANT while ( k != N ) { { Ak == max( 1 <= i & i <= k: Ai ) & k != N } if ( Ak > Ak+1 ) { p = Ak; Ak = Ak+1; Ak+1 = p; } { Ak+1 == max( 1 <= i & i <= k+1: Ai ) } k = k + 1; { Ak == max( 1 <= i & i <= k: Ai ) } } {Ak == max( 1 <= i & i <= k: Ai ) & k = N } {AN == max( 1 <= i & i <= N: Ai ) }
Un autre example avec "if“ (3) Examples Un autre example avec "if“ (3) {Ak == max( 1 <= i & i <= k: Ai ) & k != N } Cas 1: Ak > Ak+1 { Ak == max( 1 <= i & i <= k: Ai ) & k != N & Ak > Ak+1} p = Ak; { p > Ak+1 } Ak = Ak+1; { p > Ak } Ak+1 = p; { Ak+1 > Ak } { Ak+1 == max( 1 <= i & i <= k+1: Ai ) } Cas 2: Ak <= Ak+1 { Ak == max( 1 <= i & i <= k: Ai ) & k != N & Ak <= Ak+1 }