Chapitre 11 : Vérification d’un algorithme • Problème général: Supposons qu’un algorithme A effectue un certain traitement sur des données, il faut prouver mathématiquement que • étant donné les hypothèses sur ces données • A calcule effectivement le bon résultat • Pour faire une preuve mathématique il faut: – Pouvoir formaliser • les hypothèses, • le résultat escompté • Et de façon plus générale l’état d’un programme n’importe où lors de l’exécution – Avoir une démarche mathématique pour faire la preuve. 1 Le problème de la preuve de programmes • Pour cela, – on s’aide de la logique des prédicats (ou logique du premier ordre). – un programme sera vu comme un « transformateur » de formules logiques. • Exemple: le « formule » suivante : {x=3} x=x+2 {x=5} Attention ici x=3 est une formule logique vraie si x vaut 3; ce n’est pas une assignation C++ !! Etat avant l’exécution Etat après l’exécution se lit: si x vaut 3 avant l’instruction x=x+2, 2 alors après l’instruction x vaut 5 Le problème de la preuve de programmes • Attention: toute formule logique peut a priori être vrai ou fausse; ainsi on peut voir que – {x=3} x=x+2 {x=5} est vraie – {x=0} x=x+2 {x=5} est fausse • Le problème de la preuve de programme sera de vérifier si la « formule »: {hypothèses de départ} Algorithme {résultat} est bien vraie 3 Rappel de logique: le calcul des propositions • Syntaxe: – Soit un ensemble P de propositions p, q, r, … – L’ensemble des formules f possibles en logique des propositions est : } f ::= p (pour n’importe quelle proposition p) f ::= ! f négation f ::= f op f f ::= ( f ) implications ::= signifie peut être réécrit en avec op ::= v | ¶ | f | a | j ou inclusif et 4 | signifie ou • vers le droite • vers la gauche • double Exemples de formules du calcul des propositions – p ¶ (q f r) –pj!r 5 Rappel de logique: le calcul des propositions • Sémantique: – Une formule peut être vraie (True) ou fausse (False) (on dit que son domaine sémantique est l’ensemble {True ,False} – Ayant par exemple la formule f ≠ p ¶ (q f r), en donnant une valeur True ou False à chaque proposition (p, q et r), l’interprétation de la formule c’est-à-dire son évaluation est soit Vraie soit Fausse. – C’est en regardant la sémantique de chaque opérateur (via sa table de vérité) que l’on peut interpréter une formule. • Exemple: si v(p)=True, v(q)=True, v(r)=False donne v(f) = False. { • En effet True ¶ ( True f False) vaut False 6 False Rappel de logique: le calcul des propositions • Définitions: une formule logique est – consistante si elle est vraie pour au moins une interprétation – Valide ou est une tautologie si elle est vraie pour toues les interprétations. – Deux formules f1 et f2 sont (logiquement) équivalentes (dénoté f1 ¤ f2 si pour toute interprétation v : v(f1) = v(f2) c’est-à-dire si f1 j f2 est toujours vrai – Si f1 f f2 est toujours vrai on dit que f1 implique logiquement f2 (dénoté f1 fi f2) – Priorités des opérateurs logiques : ( < = « plus prioritaire que ») ! < v , ¶ < f , a , j < ¤, fi 7 ¤ et fi sont des méta-opérateurs Exemple de formules logiquement équivalentes 1. 2. 3. 4. 5. !! f ¤ f f∧f¤f¤f∨f f1 ∧ f2 ¤ f 2 ∧ f1 f1 ∨ f2 ¤ f 2 ∨ f1 f1 j f2 ¤ f2 j f1 6. 7. f1 ∧ (f2 ∧ f3) ¤ (f1 ∧ f2) ∧ f3 f1 ∨ (f2 ∨ f3) ¤ (f1 ∨ f2) ∨ f3 8. T ∧ f ¤ f 9. T ∨ f ¤ T Lois de De Morgan { 8 10. F ∧ f ¤ F 11. F ∨ f ¤ f 12. f1 f f2 ¤ !f1 ∨ f2 13. f1 f T ¤ T 14. f1 f F ¤ !f1 15. T f f2 ¤ f2 16. F f f2 ¤ T 17. !(f1 ∨ f2 ) ¤ (!f1 ) ∧ (! f2) 18. !(f1 ∧ f2 ) ¤ (!f1 ) ∨ (! f2) Rappel de logique: la logique des prédicats • Appelée également logique du premier ordre • On rajoute – des variables (simples ou indicées) – des opérateurs arithmétiques et relationnels – des quantificateurs A (pour tout) et E (il existe) • Syntaxe: – Ax f(x) formule lue : pour tout x on a f(x) – Ex f(x) formule lue : il existe x tel que f(x) Variable x liée par le quantificateur Par opposition aux variables libres 9 (voir exemples) Exemples de formules de logique des prédicats 1. 2. 3. 4. 5. x < y + 1 // x et y sont des variables libres : formule consistante x=7jy=3 // idem Ax (x.y=0 f x=0) // x est lié par le Ax et y est libre: f consistante Ax (x.y = 0 f x = 0 ∨ y = 0) // x lié par le Ax, y libre : f valide Ax (0 ¯ x < 0 f x = 7) // x est une variable liée f valide !! La formule 5. est trivialement valide ! Les variables libres seront les variables de notre algorithme: on décrira ainsi l’état 10 d’un algorithme Implication et équivalence logique • • Ces notions sont étendues Exemples: 1. Ax f(x) ¤ ! Ex !f(x) 2. E x f(x) ¤ ! A x !f(x) 3. Si le domaine des valeurs possibles pour x n’est pas vide: Ax f(x) fi Ex f(x) 4. E x (f1(x) ∨ f2(x)) ¤ (E x f1(x)) ∨ (E x f2(x)) 5. A x (f1(x) ∧ f2(x)) ¤ (A x f1(x)) ∧ (A x f2(x)) 11 Preuve partielle et totale • • • Il est généralement impossible de tester complètement un algorithme (pour toutes les données possibles) Si on veut être sûr que l’algorithme est correct il faut se rabattre sur des méthodes analytiques (mathématiques) pour le vérifier. Démarche de la preuve de correction d’un algorithme 1. 2. 3. Spécifier: exprimer le propriétés mathématiques / logiques sur les variables du programme, avant et après son exécution prouver la correction partielle : si l’algorithme se termine, il vérifie la spécification prouver la correction totale : l’algorithme se termine 12 Exemple de preuve de programme: le pgcd • Spécification – Avant l’exécution: Pré-condition x et y sont des entiers positifs. – Après l’exécution on veut comme résultat : Post-condition x contient le pgcd des 2 valeurs initialement introduites – Problème: • x et y changent de valeur dans le programme et on veut qu’à la fin x = pgcd(x initial, y initial) – Solution: • on suppose qu’il existe 2 instructions au début de l’exécution: x0 = x ; y0 = y ; • et on veut qu’à la fin fin x = pgcd(x0 , y0 ) avec x0 et y0 contenant toujours les valeurs initiales 13 Exemple de preuve de programme: le pgcd • Spécification {x˘ ˘0 ∧ y˘ ˘0 ∧ x=x0 ∧ y=y0} while (y > 0) { int Save = y; y = x % y; x = Save; } {x = pgcd(x0,y0)} Précondition Code à vérifier Postcondition 14 Exemple de preuve de programme: le pgcd Il faut faire la • Preuve partielle : si l’algorithme se termine, il vérifie la spécification • Preuve totale : (en plus de la preuve partielle) l’algorithme se termine toujours = preuve de terminaison Le problème c’est les boucles (while, for, do)! 15 Exemple de preuve de programme: le pgcd • Preuve partielle : Supposons que le code soit une boucle while instruction dont on connait la précondition déterminant l’état des variables avant la boucle et la postcondition c’est-à-dire le résultat désiré dans les variables après cette boucle (comme pour le pgcd) • • • Pour faire la preuve partielle : il faut attacher à chaque boucle un invariant c’est-à-dire une affirmation (sous forme de formule logique) appelée aussi assertion vraie à chaque fois que l’exécution du programme atteint le test de la condition. En particulier, si l’exécution atteint cette boucle, l’invariant doit initialement être vrai. De plus, l’invariant et la négation de la condition de 16 la boucle doit impliquer la postcondition Preuve partielle pour le pgcd • • • • Formellement notons: Pré : la précondition de la boucle Post : la postcondition de la boucle I : un invariant de la boucle • C : la condition de la boucle • Il faut : – – // initialement l’invariant est vrai Pré fi I I ∧! C fi Post // à la sortie de la boucle on a la résultat désiré – Et pour que I soit un invariant, il faut : {I ∧ C} instruction {I} qui exprime que ayant {I ∧ C} comme précondition à instruction, alors {I} est une bonne postcondition de instruction. 17 Preuve partielle pour le pgcd • • Formellement pour pgcd on peut prendre: Pré ≡ x˘˘0 ∧ y˘˘0 ∧ x=x0 ∧ y=y0 • • Post ≡ x = pgcd(x0,y0) I ≡ : x˘˘0 ∧ y˘˘0 ∧ pgcd(x0,y0) = pgcd(x,y) • C≡y > 0 • Il faut : – – Pré fi I I ∧! C fi Post – {I ∧ C} instruction {I} ! ! ! En vertu de la règle: • pgcd(x,0) = x En vertu des 2 règles: • x ˘ 0 ∧ y>0 fi x%y ˘ 0 • x18˘ 0 ∧ y>0 fi pgcd(x,y) = pgcd(y, x%y) Preuve totale pour le pgcd • • Il faut prouver que l’algorithme se termine toujours = preuve de terminaison Démarche: associer une fonction entière f (appelée fonction de terminaison) sur les (certaines) valeurs des variables du programmes: f : {valeurs des variables} f N telle que: La valeur de f a – I∧Cfif˘0 – {f = f0 ∧ I ∧ C} instruction {f < f0} décru après l’instruction f0 = la valeur de f avant l’exécution de l’instruction 19 Preuve totale pour le pgcd • • La conséquence d’une telle définition pour la fonction de terminaison f est qu’on est sûr que partant de tout entier positif, après un nombre fini d’itérations, on doit sortir de la boucle Fonction de terminaison pour pgcd: y – – y décroît strictement à chaque itération y est positive tant que l’invariant est vérifié Donc l’algorithme pgcd est totalement correct ☺ 20 Exemple 1: Recherche du minimum Précondition Invariant Fonction de terminaison Postcondition {0<n} int IndProv = 0; int i=1; ¯IndProv<i ∧ {0<i¯ ¯n ∧ 0¯ Aj(0¯ ¯j<i f V[IndProv] ¯V[j]} {n-i} while (i<n) { if (V[i] < V[IndProv]) IndProv = i; ++i } {0¯ ¯ IndProv <n ∧ Aj(0¯ ¯j<n f V[IndProv]¯ ¯V[j]} 21 Exemple 2: Image miroir Valeur initiale de V Précondition Invariant {0<n ∧ Aj(0¯ ¯j<n f V[j]=V0[j]} int i=0; {0¯ ¯j<i f V[j]=V0[n-1-j] ∧ V[n-1-j]=V0[j]) ¯i¯ ¯n/2 ∧ Aj(0¯ ∧ Aj(i¯ ¯j¯ ¯n-1-i f V[j]=V0[j]} {n/2-i} Fonction de terminaison Postcondition while (i<n/2) { int Save = V[i]; V[i] = V[n-1-i]; V[n-1-i]=Save; ++i; } {0<n ∧ Aj(0¯ ¯j<n f V[j]=V0[n-1-j]} 22 Exemple 3 : Tri par sélection Précondition Invariant 2 Fonction de terminaison 2 Invariant 1 Fonction de terminaison 1 {0<n ∧ Aj(0¯ ¯j<n f V[j]=V0[j]} {0¯ ¯k<i-1 f V[k]¯ ¯V[k+1]) ¯i<n ∧ Ak(0¯ ∧ Am,k(0¯ ¯V[k] ¯m<i¯ ¯k<n f V[m]¯ ∧ E une bijection f A j(0¯ ¯j<n f (V[j]=V0[f(j)])} {n-1-i} for (int i = 0; i<n-1; ++i) { elem Save; int min = i; {i<j¯ ¯n ∧ i¯ ¯min<j ∧ Ak(0¯ ¯k<j f V[min] ¯V[k]} {n-j} for (int j = i+1; j<n; ++j) if (V[j]<V[min]) min = j; E une bijection f nécessite la logique de second ordre et n’est pas complètement formalisé ici! Save = V[min]; V[min] = V[i]; V[i] = Save; Postcondition Valeur initiale de V } {0<n ∧ Ak(0¯ ¯k<n-1 f V[k]¯ ¯V[k+1] ¯j<n f (V[j]=V0[f(j)])} ∧ E une bijection f A j(0¯ 23 Exemple 3 : Tri par insertion Précondition Invariant 2 Fonction de terminaison 2 Invariant 1 Fonction de terminaison 1 Valeur initiale de V ∧ Aj(0¯ ¯j<n f V[j]=V0[j]} {0<i¯ ¯k<i-1 f V[k]¯ ¯V[k+1]) ¯n ∧ Ak(0¯ ∧ E une bijection f A j(0¯ ¯j<n f (V[j]=V0[f(j)])} {0<n {n-i} for (int i = 1; i<n; ++i) { int j; elem Save = V[i]; {-1<j¯ ¯i-1 ∧ Ak(j+2¯ ¯k¯ ¯i f V[k]>Save} {j+1} for (j = i-1; j>=0 && V[j] > Save; --j) V[j+1] = V[j]; V[j+1] = Save; } {0<n ∧ Ak(0¯ ¯k<n-1 f V[k]=V[k+1] ∧ E une bijection f A j(0¯ ¯j<n f (V[j]=V0[f(j)])} E une bijection f nécessite Postcondition 24 la logique de second ordre et n’est pas complètement formalisé ici!