• Aucun résultat trouvé

Théorie du Model-Checking

5.4 L’Algorithme du Model-checking d’une Formule de LTL

Dans l’énoncé l’on utilise |φ| pour dénoter la longueur d’une formule φ et |S| pour dénoter la cardinalité d’un ensemble S.

Théorème 5.14 (Wolper-Vardi-Sistla) (dans [GPV+95]) Etant donné une formule de PLTL, on peut construire un automate de Büchi Aφ = (S, T, S0, F, ) avec = 2APet |S| 2O(|φ|) tel que L(A) soit exactement l’ensemble de tous les modèles de φ..

5.4 L’Algorithme du Model-checking d’une Formule de LTL

Soient A et S deux automates qui représentent respectivement le système et la spécification. Supposons que L(A) et L(S) définissent tous les comportements possibles respectivement du système et de la spécification. Le problème qui se pose maintenant c’est de se donner les moyens théoriques pour dire que le système A puisse satisfaire la spécification S (qui s’écrit A |= S), c’est-à-dire en terme plus clair dans la théorie des langages, ceci s’interprète par la notion de contenance (containment en anglais) qui s’exprime par L(A) ⊆ L(S). Malheusement, l’implantation d’un tel calcul s’avère être très coûteuse puisque la complexité d’un tel problème est NP. Jouant sur la notion ensembliste de l’intersection de deux ensembles, le problème de contenance est ramené vers un problème d’intersection. Ce qui fait qu’au lieu de considérer L(A) ⊆ L(S) on considérera L(A) ∩

L(S)

qui est plus simple à concrétiser que celle de l’inclusion (problème NP) d’une part et d’autre part, calculer le complément d’un automate de Büchi est difficile. Toutefois il serait meilleur de calculer S¬φ plutôt que de calculer d’abord l’automate Sφpuis de calculer son complément.

Le model checking suivant permet de résoudre le problème de la vérification dans le sens d’idée développé dans ce cadre.

5.4.1 Transformation d’un graphe de Kripke en un graphe de

Büchi L’algorithme est très simple, il suffit pour cela de reproduire le graphe de Kripke tel qu’il est, en faisant attention de garder les états et les transitions ainsi que leurs liaisons, de faire glisser les labels (propositions) qui étiquettent les états (sommet du graphe) sur les transitions incidentes vers l’intérieur, et les boucles seront étiquetées par la formule de l’état lui-même. On obtient ainsi le graphe de Büchi (automate de Büchi).

53

Exemple 5.15

Figure 5.7 : Transformation d’une structure de Kripke en un automate de Büchi 5.4.1.1 Calcul de l’intersection de deux Automates

Pour vérifier qu’une spécification est valide, on se ramène à déterminer tous les ω-mots qui satisfont l’automate obtenu à partir de la composition de l’automate qui représente le système (l’implémentation) et celui de la spécification (formule de la logique temporelle)[GPV+95, SB00]. La composition n’est autre que l’intersection de deux automates.

Soit A = (S1, T1, S01, F1, ∑) et B = B¬φ = (S2, T2, S02, F2, ∑), alors L(A)∩ BL( ¬φ) est l’ensemble des mots reconnaissables par l’automate de Büchi P = A ⊗ B = (S, S0,T,F,∑) où S = S1 x S2 et S0 = S01 x S02 et T étant la synchronisation de T1 et T2 sur les actions identiques et F = F1 x F2.

5.4.2 Implémentation de la Procédure du Model-checking

Pour implémenter une telle procédure, nous aurons besoin de deux choses: un moyen pour former la composition parallèle de deux automates, et un autre moyen pour vérifier si un automate contient des exécutions d’acceptance [SB00, EH00].

Le premier moyen en principe est direct, il suffit de former la composition parallèle P = A x B de deux automates. Pour cela on calcule les éléments suivants :

P = A ⊗ B = (S1 x S2, S01 x S02,T, F1 x F2,∑) [le produit synchrone]

T est définit par : p a

a

s

s

s

s

s

s

1

,

2

) ( '

1

, '

2

)

1 '1

( → ≡ →

& a

s

s

2

'2

Le deuxième moyen étant de vérifier que L(P) ≠∅ si et seulement s’il existe des exécutions

L , 1 2 1 1 1 0 2 1 s s saa et 2,L 2 2 1 2 0 2 1 s s

saa telle que pour chaque i ∈ {1,2}, s0iS0iet infiniment plusieurs des

L

,

,

,

1 2 0 i i i

s s

s

sont dans Fi.

Ce point peut facilement être prouvé. Supposons, que L(P) ≠∅. Alors il existe une exécution acceptante dans P qui a pour forme :

( , ) ( , ) ( ,

2

)L

2 2 1 2 2 1 1 1 1 2 0 1 0

s s s s s

s

p ap a

, avec 2 0 1 0 2 0 1 0, ) (s sS xS et tel qu’il existe infiniment plusieurs des

(s

1j

,s

2j

) dans F

1

xF

2

.

L’existence des deux exécutions avec la propriété désirée est maintenant immédiate à cause de la définition de la transition p

a

→ . De la même manière nous pouvons vérifier dans l’autre sens que p a

a

s

s

s

s

s

s

1

,

2

) ( '

1

, '

2

)

1 '1

( → ⇐ →

& a

s

s

2

'2. Comme

( , ) ( ,

2

)

1 1 1 1 2 1 + + +

p j j aj j j

s s s

s

pour tout j ≥ 0, et 2 0 1 0 2 0 1 0, )

(s sS xS . Il reste maintenant de montrer que

2 1 2

1

, )

(s

j

s

j

dans F xF

. Comme F1 = S1, alors

(s

1j

,s

2j

)∈F

1

xF

2 si et seulement si

s

2j

F

2

,

et ceci arrive le plus souvent par supposition.

54

La deuxième partie de notre model-checking permet de déterminer si un automate A possède des exécutions d’acceptations, ou, d’une façon équivalente, si L(A) ≠∅. Pour cela, il suffit d’imaginer que l’automate sera représenté par un graphe orienté et ou les sommets sont les états et l’existence d’une transition suppose l’existence d’un sommet initial et un sommet terminal reliés par une transition (un arc) qui est étiqueté par une action a∈∑. Alors L(A) ≠∅ est vraie si et seulement il existe un chemin s0, s1, … , sk dans A tel que sk soit un état d’acceptation et qu’il existe un cycle de sk vers sk. S’il existe un tel chemin et un tel cycle il est claire que L(A) ≠∅, et que nous pouvons obtenir un cycle d’acceptation en suivant s0 à sk et nous répétons ceci en suivant le cycle. Réciproquement, supposons L(A) ≠∅ , et il existe une exécution s0X1s1X2s2,L Par la définition de l’exécution d’acceptation et par le fait que F est fini, il existe un cycle de sk vers lui-même qui contient un état d’acceptation.

En utilisant ce fait, il serait plus judicieux par exemple de rechercher les composantes fortement connexes. Pour cela, l’un des meilleurs moyens qui permet de les déterminer étant celui qui utilise une procédure basée sur une procédure du type le depth-first search (une recherche en profondeur d’abord (dfs)) [HPY98] pour vérifier que sk satisfait les conditions citées plus haut. C’est-à-dire, en premier lieu, c’est de vérifier que sk est un état d’acceptation, en principe ceci est trivial. Par contre pour vérifier que sk appartient à un cycle, nous seront obligé d’utiliser une deuxième procédure dfs qui s’effectue dans le pire des cas en une complexité temps de O(nm), avec n le nombre de sommets (états) et m le nombre de transitions. Mais en entrelaçant deux dfs’s nous pouvons réduire cette complexité à O(m). Les deux procédures dfs récursives qui utilisent une pile sont présentées comme suit :

Figure 5.8 : L’Algorithme de Recherche des Etats Valides

5.4.3 Transformation d’une formule LTL en Automate de Büchi

La procédure de traduction permet de transformer une formule LTL ou PLTL en un automate de Büchi [GO01]. Une étape préliminaire est alors engagée pour n’autoriser qu’un certain type d’opérateurs et procéder ainsi à une forme de simplification de la spécification. Pour cela il est nécessaire d’utiliser les équivalences (LTL) suivantes et de les convertir dans leur forme normale :

• F g = 1 U g • G g = ¬( 1 U ¬g) • ¬(f g) = ¬f ∧¬g • ¬(f g) = ¬f ∨¬g • ¬¬f = f

55 • ¬X f = X¬f • ¬(f U g) = ¬f R ¬g • ¬(f R g) = ¬f U ¬g • ¬[]f = <>¬f. • ¬<>f = [] ¬f. ƒ Æ ψ = (¬ ϕ) ∨ ψ • <>ϕ = (T U ϕ). • []ϕ = (F ∨ ϕ).

• Transformation de ( []<>P ) Æ ( []<>Q ) par élimination de l’implication ¬( []<>P ) ∨ ( []<>Q )

• Elimination de [] et <>

• La négation est poussée vers l’intérieur: ¬( F ∨ ( T U P ) ) ∨ ( F ∨ ( T U Q ) ) devient (T U (F U ¬ P ) ) ∨ ( F ∨ ( T U Q ) ).

Il est à remarquer que les nouvelles formules construites par réécriture ne contiennent plus que les opérateurs U, R, X, ∧, ∨, ¬ et les formules atomiques.

Durant toutes les étapes de construction du graphe (automate), une partie des nœuds sera marquée

done, qui est un terme anglais pour dire que tout sommet ainsi marqué ne sera ni modifié ni supprimé, à

la seule possibilité que les arcs partant de ce sommet peuvent être modifiés.

Pour commencer cette construction, nous créons tout d’abord deux nœuds, init et v0 reliés par un arc. Le champs du nœuds init sera vide et v0 aura Old(v0) = Next(v0) = Ø, New(v0) =

f

. Le nœud init est alors marqué par done.

Le reste de la construction du graphe s’exécute en faisant appel à la procédure récursive expand(v0) suivante :

Figure 5.9 : L’Algorithme EXPAND

La procédure process_formula() modifie les nœuds u et fait des appels récursifs à la fonction expand() qui prend en charge d’autres formules. En outre, elle procède par une analyse par cas sur la structure de g. Les différentes situations qu’elle peut traiter sont :

56

• g = p. Si ¬p ∈ Old(u), alors p et ¬p sont supposés être vraies, ce qui est impossible comme nous le savons, alors il ne sera pas possible d’atteindre ce nœud ce qui nous oblige de supprimer u ainsi que les arcs incidents intérieurement à u. Dans l’autre cas on appelle alors la procédure expand(u).

• g = ¬p. Ce cas est similaire au cas passé. Si p ∈ Old(u), alors on supprime u et tous les arcs incidents intérieurement. Sinon on fait un appel à expand(u).

• g = g1 ∧ g2. g1 et g2 doivent être vrais, on les rajoute alors à New(u) et on appelle expand(u). • g = g1 ∨ g2. Si l’exécution parallèle considère u, alors g1 est vrais en u, ou bien g2 est vrais. On éclate alors le nœud u pour représenter les deux cas. On crée alors deux nœuds u1 et u2 avec le même champ que u, et on rajoute u1 à New(u1) et g2 à New(u2). On supprime alors le nœud u et pour chaque arc incident à l’intérieur, on le duplique pour avoir une copie pour u1 et une autre pour u2. Finalement on appelle expand(u1) et aussi expand(u2).

• g = X g’. Ceci montre que X g’ doit être vraie en tout état suivant, alors on rajoute g’ à Next(u). • g = g1 U g2. Nous savons que g1 U g2 = g2 ∨ (g1 ∧ X(g1 U g2)). Dans ce cas il nous sera possible de dupliquer en mettant g2 dans New(u1) et (g1 ∧ X (g1 U g2)) in New(u2) et en faisant des appels récursifs.

• g = g1 R g2. Nous avons g1 R g2 = g2 ∧ (g1 ∨ X(g1 U g2)) = (g2 ∧ g1) ∨ (g2 ∧ X(g1 R g2)). Puis pour les autres parties nous procédons de la même par la duplication de chaque sous formule.

5.4.3.1 Comment s’y prendre pour appliquer cet algorithme

Pour se faire, on utilisera une table à trois cases, qu’on nommera respectivement par, New, Old et

Next.Puis on procédera comme suit :

• Pour les formules composées, on essayera à chaque fois de séparer la formule en deux parties. La première partie sera étiquetée dans l’état courant et la deuxième dans l’état suivant Next .

• Prendre une formule de l’état New et la rajouter à l’état Old.

• Selon la formule, ou bien on fractionne le nœud courant en deux nouveaux nœuds ou bien une nouvelle copie du nœud sera mise en place.

Exemple 5.16

ϕ U ψ est fractionnée comme suit :

1. Add ϕ to New, add ϕ U ψ to Next. 2. Add ψ to New.

Comme ϕ U ψ = ψ ∨ ( ϕ ∧ X(ϕ U ψ )). ϕ ∨ψ , split (fractionner) : 1. Add ϕ, ψ to New.

2.Add ψ to New, ϕ ∨ ψ to Next. Comme ϕ ∨ ψ = ψ ∧ ( ϕ ∨ X(ϕ ∨ ψ )).

ϕ ∧ ψ, evolve (se transforme en): 1. Add ϕ, ψ to New.

2. Xϕ, evolve: 3. Add ϕ to Next.