2024-X-Ens-Info-Mpi-C-Corrigé

Égalités et différences

Le sujet comporte 17 pages, numérotées de 1 à 17 et une annexe. Vue d’ensemble du sujet. Ce sujet traite du problème de satisfiabilité de formules construites par la conjonction de contraintes d’égalité et de différence (dis-égalité) entre expressions.

Ce problème se présente, par exemple, dans la compilation de programmes, quand on cherche à réduire des expressions complexes (nécessitant beaucoup de calculs) vers des expressions plus simples, en utilisant les propriétés des opérateurs apparaissant dans ces expressions.

Par exemple, si x est une variable entière, (x * 2)/2 == x est une contrainte d’égalité entre deux expressions écrites en C. De même, si y et z sont des variables booléennes, l’égalité (y || (y && z)) == y permet de simplifier l’expression C écrite à gauche de l’égalité.

Il est important de savoir si un ensemble donné de contraintes d’égalité ou de différence est satisfiable. Dans la suite, {P_{L}} est le problème suivant : « Étant donnée une formule F , conjonction de contraintes de la classe L, décider si F est satisfiable ».

Ce sujet propose d’étudier la complexité du problème {P_{L}} et les algorithmes pour le résoudre dans le cas de quatre classes de contraintes : La partie I considère la classe {L_{1}} où les contraintes ont la forme {x \leftrightarrow y} ou {x \leftrightarrow \neg z} avec x, y et z des variables propositionnelles (c’est-à-dire, ayant des valeurs booléennes).

La partie II concerne la classe {L_{2}} incluant les contraintes de la classe {L_{1}} et celles de la forme {x \leftrightarrow (y \wedge z)} (entre autres) avec x, y et z des variables propositionnelles. Les algorithmes des deux premières parties seront codés en OCaml.

La partie III s’intéresse à la classe {L_{3}} des contraintes de la forme x = y ou {x \ne y} avec x et y des variables entières. Enfin, la partie IV concerne les contraintes d’égalité et de différence entre les expressions construites à partir de variables entières et de fonctions à deux arguments.

Les algorithmes des deux dernières parties seront codés en C. Les parties III et IV peuvent être traitées indépendamment de parties I et II. Il est recommandé de traiter les parties I et II de manière séquentielle. De même pour les parties III et IV.

Notations Les contraintes seront construites en utilisant un ensemble X de n variables notées {X_{i}} avec {1 \leq i \leq n}. Une valeur de vérité est un élément de {B\overset{\mathrm{def}}= \{0, 1\}} où 1 représente le vrai et 0 le faux. Une valeur entière k est un élément de Z et sa valeur absolue est notée |k|.

Étant donnés {k, m \in Z} avec {k \leq m}, l’intervalle [k; m] est l’ensemble des {i \in Z} tels que {k \leq i \leq m}. Logique propositionnelle.

On utilisera les formules de la logique propositionnelle construites à partir de l’ensemble de variables propositionnelles X, les constantes propositionnelles {\bot} (faux) et {\top} (vrai), les connecteurs logiques classiques {\neg} (négation), {\wedge} (conjonction), {\vee} (disjonction).

On définit {F \to G} (implication) par {(\neg F \vee G)}, et {F \leftrightarrow G} (équivalence) par {(F \to G) \wedge (G \to F )}. Pour les connecteurs {\wedge} et {\vee}, on utilise leur forme k-aire avec {k \geq 0}. Par exemple, {X_{1}\wedge X_{2}\wedge \ldots \wedge X_{k}} est une formule utilisant une conjonction k-aire.

Par convention, une conjonction 0-aire est {\top}, et une disjonction 0-aire est {\bot} ; une conjonction (resp. disjonction) unaire est identique à son opérande. Un littéral {\ell} est soit une variable {X_{i}} soit sa négation {\neg X_{i}}.

Une formule est en forme normale conjonctive (CNF) si c’est une conjonction de disjonctions de littéraux. Une formule est en forme k-CNF avec k > 0, si elle est en forme CNF et le nombre de littéraux différents dans chaque disjonction est au plus k. Par exemple, la formule {(X_{2} \vee X_{3} \vee \neg X_{4}) \wedge (\neg X_{3} \vee X_{4}) \wedge X_{1}} est en forme 3-CNF.

On rappelle qu’une formule propositionnelle F peut être satisfaite ou non par une valuation {v : X \to B}, qui assigne une valeur de vérité à chaque variable. On note v |= F la relation de satisfaction.

Une formule {F^{'}} est conséquence logique de F , noté {F \models F^{'}}, quand, pour toute valuation v telle que v |= F , on a aussi {v \models F^{'}}.

Deux formules sont logiquement équivalentes lorsque chacune est conséquence logique de l’autre (ou, de façon équivalente, quand elles sont satisfaites par les mêmes valuations). Une formule est une tautologie si elle est satisfaite par toutes les valuations.

Une formule est une contradiction (antilogie) ou contradictoire si elle n’est satisfaite par aucune valuation. Une formule F est satisfiable s’il existe une valuation v telle que v |= F . L’annexe rappelle certaines règles de la déduction naturelle. Complexité.

Par la complexité d’un algorithme A on entend le nombre d’opérations élémentaires (comparaison, addition, soustraction, multiplication, division, affectation, test, etc.) nécessaires à l’exécution de A dans le pire cas.

Lorsque la complexité dépend d’un ou plusieurs paramètres {k_{0}, \ldots , k_{r}}, on dit que A a une complexité en {O(f(k_{0}, \ldots , k_{r}))} s’il existe une constante C > 0 telle que, pour toutes les valeurs de {k_{0}, \ldots , k_{r}} suffisamment grandes (c’est-à-dire plus grandes qu’un certain seuil), pour toute instance du problème de paramètres {k_{0}, \ldots , k_{r}} la complexité est au plus {C \times f(k_{0}, \ldots , k_{r})}.

De même, on dit que A a une complexité en {\Theta (f(k_{0}, \ldots , k_{r}))} s’il existe deux constantes {C_{1} > 0} et {C_{2} > 0} telles que, pour toutes les valeurs de {k_{0}, \ldots , k_{r}} suffisamment grandes, la complexité de A est au moins {C_{1} \times f(k_{0}, \ldots , k_{r})} et au plus {C_{2} \times f(k_{0}, \ldots , k_{r})}.

Dans tout ce sujet, on note « log » le logarithme à base 2, et on utilisera exclusivement ce logarithme. Programmation. Dans les questions de programmation en C et OCaml, on n’utilise que les fonctions qui sont incluses dans la bibliothèque standard du langage.

Pour le code écrit en C, on suppose que les en-têtes stdbool.h, stdio.h, stdlib.h et assert.h ont été inclus.

On rappelle quelques opérations sur les listes en OCaml.

— List.iter f lst applique la fonction f: ‘a -> unit à chaque élément de la liste lst de type ‘a list.

Si la complexité de f est en O(1), alors la complexité de List.iter est en O(k), où k est la longueur de lst.

— List.map f lst renvoie la liste de type ‘b list obtenue en appliquant la fonction f: ‘a -> ‘b à chaque élément de la liste lst de type ‘a list.

Si la complexité de f est en O(1), alors la complexité de List.map est en O(k), où k est la longueur de lst.

— List.mem e lst renvoie true si et seulement si e est un élément de lst.

Si la complexité du test d’égalité est en O(1), la complexité de List.mem est en O(k), où k est la longueur de lst.

— List.exists f lst renvoie true si et seulement s’il existe au moins un élément e de lst (liste de type ‘a list) tel que f e renvoie true avec f: ‘a -> bool.

Si la complexité de f est en O(1), alors la complexité de List.exists est en O(k), où k est la longueur de lst.

— List.for_all f lst renvoie true si et seulement si tous les éléments de lst (de type ‘a list) satisfont le prédicat f: ‘a -> bool.

Si la complexité de f est en O(1), alors la complexité de List.for_all est en O(k), où k est la longueur de lst.

— List.filter f lst renvoie la liste de tous les éléments de lst (de type ‘a list) qui satisfont le prédicat f: ‘a -> bool.

Si la complexité de f est en O(1), alors la complexité de List.filter est en O(k), où k est la longueur de lst.

On rappelle quelques opérations sur les tableaux en OCaml.

— Array.length tab renvoie la longueur du tableau tab en temps O(1).

— Array.make k v crée un tableau de k éléments, tous initialisés à v, en temps O(k).

— La valeur à la case numéro i du tableau tab est tab.(i).

Les cases sont numérotées à partir de zéro et l’accès est fait en temps O(1).

— L’affectation de la case i du tableau tab avec la valeur v s’écrit tab.(i) <- v ; sa complexité est en O(1).

— Array.copy tab crée une copie du tableau tab en temps O(k) avec k la longueur du tableau en argument.

Partie I. Équivalences de littéraux

Dans cette partie, on se place dans le cadre de la logique propositionnelle. On définit {L_{1}} comme la classe des contraintes de la forme {\ell \leftrightarrow \ell ^{'}} avec {\ell} et {\ell ^{'}} des littéraux. Une formule F est dite construite sur {L_{1}}, notation {F \in F(L_{1})}, si elle est une conjonction de contraintes de {L_{1}}.

Dans cette partie, on considère que la variable {X_{1}} est toujours interprétée à 1, donc qu’elle représente la valeur de vérité vrai. La formule {F_{0}} définie ci-dessous est dans {F(L_{1})} : {\begin{array}{l}F_{0}\overset{\mathrm{def}}= (X_{3} \leftrightarrow \neg X_{2})\wedge(X_{4} \leftrightarrow X_{3})\\[8pt]\wedge(X_{3} \leftrightarrow X_{4})\wedge(X_{1} \leftrightarrow \neg X_{5})\end{array}}

Question 1. Donner une formule logiquement équivalente à {F_{0}} en forme 2-CNF.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 2. Donner une réduction en temps linéaire du problème {P_{L_1}} au problème de satisfiabilité des formules en 2-CNF.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Mise en œuvre en OCaml. Une contrainte de {L_{1}} est représentée par un couple d’entiers (i, j) avec {i, j \in [1; 2n]}. Pour tout {i \in [1; 2n]}, le littéral représenté par i, noté lit(i), est {X_{i}} si {i \in [1; n]} et {\neg X_{i-n}} si {i \in [n + 1; 2n]}.

Par la convention sur l’interprétation de {X_{1}}, l’entier i = 1 (resp. i = n + 1) représente la valeur de vérité 1 (resp. 0). Enfin, une formule dans {F(L_{1})} est représentée par la liste des couples correspondant aux contraintes dont la formule est conjonction.

Par convention, la liste vide représente {\top}. Le nombre de variables, n, est une variable globale entière. Ce codage est donné par les déclarations OCaml ci-dessous :

Les exceptions déclarées ci-dessus seront utilisées dans les fonctions de cette partie et de la partie suivante.

Question 3. Écrire les fonctions suivantes :

— var_of_lit: int -> int renvoie l’index de la variable apparaissant dans le littéral représenté par l’argument i. Par exemple, pour n = 5, la fonction renvoie 3 si l’argument est 3, et 2 si l’argument est 7.

Si {i \notin [1; 2n]} alors la fonction exécute raise (Bad_literal i).

— neg_lit: int -> int renvoie, pour un argument i, l’entier j tel que lit(j) est logiquement équivalent à {\neg lit(i)} ; si {i \notin [1; 2n]} alors la fonction exécute raise (Bad_literal i).

— eform1_is_wf: eform1 -> bool renvoie true si et seulement si l’argument est « bien formé », c’est-à-dire qu’il contient uniquement des entiers qui représentent des littéraux sur l’ensemble de variables X.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Dans la suite de ce sujet, on suppose que tous les entiers utilisés par les valeurs de type contr1 et eform1 représentent des littéraux sur l’ensemble de variables X. Par conséquent, l’exception Bad_literal ne doit pas être levée.

Graphe d’une formule. Le graphe associé à {F \in F(L_{1})}, noté {G_{F} = (V_{F} , E_{F} )}, est un graphe non orienté dont les sommets {V_{F}} sont les entiers dans l’intervalle [1; 2n].

Les arêtes {E_{F}} sont les ensembles {i, j} tels que {i, j \in V_{F} , i \ne j} et F contient soit la contrainte {lit(i) \leftrightarrow lit(j)}, soit la contrainte {lit(i^{'}) \leftrightarrow lit(j^{'})} avec {i^{'}} et {j^{'}} égaux respectivement à neg_lit(i) et neg_lit(j). Par exemple, pour n = 5, la contrainte {(X_{3} \leftrightarrow X_{4})} est représentée par les arêtes {3, 4} et {8, 9}.

On note que les contraintes de F de la forme {\ell \leftrightarrow \ell} ne sont pas représentées par {E_{F}} .

Question 4. On suppose n = 5. Pour chacune des formules suivantes, donner le graphe associé : {\begin{array}{l}F_{1}\overset{\mathrm{def}}= (X_{3} \leftrightarrow X_{4})\wedge(X_{4} \leftrightarrow X_{2})\\[8pt]\wedge(X_{3} \leftrightarrow \neg X_{2})\wedge(X_{5} \leftrightarrow X_{2})\end{array}} {\begin{array}{l}F_{2}\overset{\mathrm{def}}= (\neg X_{4} \leftrightarrow X_{2})\wedge(X_{2} \leftrightarrow \neg X_{3})\\[8pt]\wedge(X_{4} \leftrightarrow \neg X_{5})\wedge(X_{3} \leftrightarrow X_{5})\end{array}}
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

On suppose disposer d’une bibliothèque munie d’un type graph et qui code des graphes non orientés. Les sommets sont représentés par des entiers.

Les opérations suivantes sont disponibles pour la construction et l’exploration des graphes :

— g_create : int -> graph renvoie une valeur représentant un graphe sans arêtes et ayant k sommets, où k est donné en argument, supposé strictement positif. Les sommets sont numérotés de 1 à k.

La complexité de cette opération est en O(k).

— g_add_edge : graph -> int -> int -> unit ajoute au graphe donné comme premier argument l’arête entre les sommets donnés en 2ème et 3ème arguments.

Si les sommets en argument ne sont pas déclarés dans le graphe ou si l’arête existe déjà, alors cette fonction ne change pas le graphe. Sinon, le graphe est modifié en place.

La complexité de cette opération est en O(k), avec k le nombre de sommets du graphe.

— g_nb_vertex : graph -> int renvoie le nombre de sommets du graphe en argument.

La complexité de cette opération est en O(1).

— g_succ : graph -> int -> int list renvoie la liste des sommets adjacents du sommet en second argument dans le graphe donné en premier argument. Si le sommet en argument n’existe pas, la liste renvoyée est vide.

La complexité de cette opération est en O(1).

Question 5. Écrire une fonction eform1_to_graph: eform1 -> graph qui renvoie le graphe associé à la formule F représentée par l’argument.

Si F contient une contrainte de la forme {\ell \leftrightarrow \neg \ell}, alors la fonction lève l’exception Exn_unsat.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Lorsqu’un appel à eform1_to_graph lst lève l’exception Exn_unsat, la formule représentée par lst contient toujours une contrainte contradictoire. Cependant, une formule peut être une contradiction sans que l’exception soit levée, comme c’est le cas de la formule ({X_{1} \leftrightarrow} {X_{2}) \wedge (X_{2} \leftrightarrow \neg X_{1}}).

Question 6. Donner et justifier soigneusement la complexité de eform1_to_graph en fonction de n et de la longueur p de la liste en argument.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Soient {F \in F(L_{1})} une formule et g la représentation de {G_{F}} , le graphe associé à F . On note par {CC(G_{F} )} l’ensemble des ensembles de sommets des composantes connexes de {G_{F}} . Soit m la taille de {CC(G_{F} )}, c’est-à-dire, le nombre de composantes connexes de {G_{F}} .

On représente {CC(G_{F} )} par un tableau c de 2n + 1 entiers, qui associe à chaque {i \in [1; 2n]}, sommet de g, un entier dans l’intervalle [1; m]. Ainsi, c.(i) identifie de manière unique la composante connexe du sommet i.

La case de c à la position 0 ne sera pas utilisée, mais elle permet d’utiliser directement les indices des variables comme positions de c. Par convention, c.(0) est 0.

Question 7. Écrire une fonction g_cc_array: graph -> (int array * int) qui renvoie le couple (c, m), où c est le tableau des composantes connexes du graphe en argument et où m est le nombre de composantes connexes.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 8. Démontrer que si i et j sont des sommets distincts appartenant à la même composante connexe de {G_{F}} , alors {F \models lit(i) \leftrightarrow lit(j)}.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 9. Soit {C = {i_{1}, \ldots , i_{q}} \in CC(G_{F} )} l’ensemble des sommets d’une composante connexe de {G_{F}} . Soit {C^{'}} l’ensemble de sommets {{j_{1}, \ldots , j_{q}}} avec {j_{k} =} neg_lit(i_{k}) pour tout {k \in [1; q]}.

Démontrer que {C^{'}} est élément de {CC(G_{F} )}.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 10. Démontrer que F est une contradiction si et seulement s’il existe une composante connexe C de {G_{F}} et un sommet i de C tels que neg_lit(i) est un sommet de C.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 11. Écrire une fonction cc_is_sat: int array -> bool qui a comme argument c, le tableau représentant les composantes connexes de {G_{F}} et qui renvoie true si et seulement si F est satisfiable.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Représentation d’une valuation. Une valuation v de X est représentée par un tableau v de n + 1 valeurs 0 ou 1 tel que v.(i) est 1 si et seulement si {v(X_{i}) = 1}. En particulier, v.(1)=1 par convention sur {X_{1}}.

La case à la position 0 n’est pas utilisée, mais elle permet d’utiliser les indices des variables comme positions du tableau ; par convention, v.(0) est 0.

Question 12. Écrire une fonction eform1_sat: eform1 -> (int array) option qui a comme argument une liste lst représentant une formule {F \in F(L_{1}). L}’appel eform1_sat lst renvoie None si F est une contradiction.

Si F est satisfiable, l’appel renvoie Some(v) avec v un tableau qui représente une valuation qui satisfait F . La complexité en temps de l’algorithme doit être en {O(p \times n)} où p est la longueur de la liste lst en argument.

Justifier que votre algorithme a cette complexité en temps.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Partie II. Équivalences avec une conjonction de deux littéraux

On reste dans le cadre de la logique propositionnelle, mais on considère une nouvelle classe de contraintes. La classe {L_{2}} contient les contraintes de la forme {\ell \leftrightarrow (\ell ^{'} \wedge \ell ^{''})} où {\ell , \ell ^{'}, \ell ^{''}} sont des littéraux.

Pour identifier les contraintes des classes {L_{1}} et {L_{2}}, on appelle i-contrainte une contrainte de la classe {L_{i}} avec {i \in \{1, 2\}}. Une formule F est dite construite sur {L_{2}}, notation {F \in F(L_{2})}, si elle est une conjonction de 2-contraintes. On garde l’hypothèse que {X_{1}} est toujours interprétée à 1.

Un exemple de formule de {F(L_{2})} est : {\begin{array}{l}F_{3}\overset{\mathrm{def}}= (X_{1} \leftrightarrow (X_{2} \wedge \neg X_{4}))\wedge(\neg X_{2} \leftrightarrow (X_{2} \wedge X_{3}))\\[8pt]\wedge(X_{4} \leftrightarrow (\neg X_{1} \wedge X_{5}))\end{array}}

Question 13. Donner une réduction en temps polynomial du problème {P_{L_2}} au problème de satisfiabilité des formules en 3-CNF.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 14. Donner une dérivation formelle (preuve en déduction naturelle) du séquent suivant : {X_{2} \to (\neg X_{2} \wedge X_{3}), (\neg X_{2} \wedge X_{3}) \to X_{2} \vdash \neg X_{2}}. Les règles de la déduction naturelle à utiliser dans cette preuve sont rappelées dans l’annexe.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

En utilisant la convention sur l’interprétation de {X_{1}}, toute 1-contrainte {\ell \leftrightarrow \ell ^{'}} est logiquement équivalente à la 2-contrainte {\ell \leftrightarrow (\ell ^{'} \wedge X_{1})}. À l’inverse, certaines 2-contraintes sont logiquement équivalentes à des formules de {F(L_{1})}.

Par exemple, les équivalences logiques suivantes sont vraies pour tout couple de littéraux {\ell} et {\ell ^{'}} (les preuves ne sont pas demandées) :

{(1)\quad\ell\leftrightarrow(X_1\wedge\ell')} équivalent à {\ell\leftrightarrow\ell'}, par 1 élément neutre de {\wedge}.

{(2)\quad\ell\leftrightarrow(\neg X_1\wedge\ell')} équivalent à {\ell\leftrightarrow\neg X_1}, par 0 élément absorbant de {\wedge}.

{(3)\quad\ell\leftrightarrow(\ell'\wedge\ell')} équivalent à {\ell\leftrightarrow\ell'}, par idempotence de {\wedge}.

{(4)\quad\ell\leftrightarrow(\ell'\wedge\neg\ell')} équivalent à {\ell\leftrightarrow\neg X_1}, par contradiction.

{(5)\quad X_1\leftrightarrow(\ell\wedge\ell')} équivalent à {(X_1\leftrightarrow\ell)\wedge(X_1\leftrightarrow\ell')}, par définition de {\wedge}.

{(6)\quad\ell\leftrightarrow(\neg\ell\wedge\ell')} équivalent à {(\neg X_1\leftrightarrow\ell)\wedge(\neg X_1\leftrightarrow\ell')}, admis.

Dans la suite de cette partie, on dénote par E(X) toutes les équivalences définies sur l’ensemble des littéraux par les équivalences de (1) à (6) et par leur variantes obtenues en utilisant la symétrie de la conjonction.

Par exemple, « {\ell \leftrightarrow (\ell ^{'} \wedge X_{1})} équivalent à {\ell \leftrightarrow \ell ^{'}} » est également une équivalence de E(X), grâce à la symétrie de la conjonction appliquée à (1). Mise en œuvre en OCaml. Une 2-contrainte {\ell \leftrightarrow (\ell ^{'} \wedge \ell ^{''})} est représentée par un enregistrement avec trois champs entiers.

Les entiers de ces enregistrements prennent des valeurs dans [1; 2n] et représentent des littéraux avec la même convention de codage qu’en première partie. Une formule dans {F(L_{2})} est représentée par une liste d’enregistrements ; la liste vide représente {\top}.

Ce codage est donné par les types OCaml suivants :

Une valeur e de type contr2 représente la 2-contrainte {\texttt{e.ll}\leftrightarrow(\texttt{e.lr1}\wedge\texttt{e.lr2})}. Dans la suite de cette partie, on suppose que toutes les valeurs des types contri et eformi avec {i \in \{1, 2\}} sont bien formées, c’est-à-dire que les entiers qu’elles contiennent représentent des littéraux sur X.

Question 15. Écrire la fonction contr2_simplify_eq5: contr2 -> eform1 option qui applique l’équivalence (5) pour simplifier la 2-contrainte en argument.

L’appel contr2_simplify_eq5 e2 :

— renvoie None si la simplification ne peut pas être faite avec (5) ;

— lève l’exception Exn_unsat (déclarée en partie I) si la simplification peut être faite mais la 2-contrainte représentée par e2 est une contradiction ;

— enfin, renvoie Some [(1,e2.lr1);(1,e2.lr2)] si la simplification peut être effectuée sans détecter de contradiction.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Valuation partielle. Une valuation partielle est une fonction {v : X \to \{-1, 0, 1\}} telle que {v(X_{1}) = 1}. Pour tout {i \ne 1}, si {v(X_{i}) = b \in B} alors on dit que v assigne {X_{i}} à b. Si {v(X_{i}) = -1} alors on dit que {X_{i}} n’est pas assignée par v.

On étend la notion de valuation partielle aux littéraux et aux formules comme suit :

— {v(\neg X_{i}) = \neg b} si {v(X_{i}) = b} avec {b \in B}, et {v(\neg X_{i}) = -1} si {v(X_{i}) = -1}.

— Soit la 1-contrainte {\ell \leftrightarrow \ell ^{'}}. Si {v(\ell ), v(\ell ^{'}) \in B} alors {v(\ell \leftrightarrow \ell ^{'}) = 1} si {v(\ell ) = v(\ell ^{'})} et 0 sinon.

Si au moins une des valeurs {v(\ell )} ou {v(\ell ^{'})} est −1, alors {v(\ell \leftrightarrow \ell ^{'}) = -1}.

— Pour la conjonction de deux littéraux {\ell \wedge \ell ^{'}, v(\ell \wedge \ell ^{'}) = -1} si au moins une des valeurs {v(\ell )} ou {v(\ell ^{'})} est −1 ; sinon la valeur de {v(\ell \wedge \ell ^{'})} est 1 si et seulement si {v(\ell ) = 1} et {v(\ell ^{'}) = 1}.

— Soit la 2-contrainte {\ell \leftrightarrow (\ell ^{'} \wedge \ell ^{''})}. Si {v(\ell ), v(\ell ^{'} \wedge \ell ^{''}) \in B} alors {v(\ell \leftrightarrow (\ell ^{'} \wedge \ell ^{''})) = 1} si {v(\ell ) = v(\ell ^{'} \wedge \ell ^{''})} et 0 sinon.

Si au moins une des valeurs {v(\ell )} ou {v(\ell ^{'} \wedge \ell ^{''})} est −1, alors {v(\ell \leftrightarrow (\ell ^{'} \wedge \ell ^{''})) = -1}. La valeur d’une formule {F_{i} \in F(L_{i})} où {i \in \{1, 2\}} pour une valuation partielle v, notation {v(F_{i})}, est −1 s’il existe une contrainte e de {F_{i}} telle que v(e) = −1 ; sinon, {v(F_{i}) = 1} si pour toute contrainte e de {F_{i}} on a v(e) = 1, et {v(F_{i}) = 0} sinon.

Une valuation partielle v est compatible avec une valuation {v^{'}} si pour tout {X_{i} \in X} tel que {v(X_{i}) \in B} on a {v^{'}(X_{i}) = v(X_{i})}. Une valuation partielle v est représentée en OCaml par un tableau v de taille n + 1 dont les éléments appartiennent à {−1, 0, 1}.

Comme pour les valuations, la case de position 0 n’est pas utilisée et, par convention, v.(0)=0.

Question 16. Écrire la fonction eform1_of_valp: int array -> eform1 qui renvoie une formule F de {F(L_{1})} telle que F encode les assignations de la valuation partielle v en argument.

Ainsi, un appel à eform1_of_valp v renvoie la liste de couples (i, j) tels que i > 0, v.(i) {\ne -1} et si v.(i) = 1 alors j = 1 et si v.(i) = 0 alors j = n + 1. Soit F la formule représentée par le résultat de l’appel à eform1_of_valp v.

Démontrez que, pour toute valuation {v^{'}} on a {v^{'} \models F} si et seulement si v est compatible avec {v^{'}}.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Une 2-contrainte peut être simplifiée en présence d’une valuation partielle v en une formule de {F(L_{1})}. Par exemple, si {v(X_{2}) = 1} alors la 2-contrainte {X_{2} \leftrightarrow (\ell ^{'} \wedge \ell ^{''})} a la même valeur que la 1-contrainte {(X_{1} \leftrightarrow \ell ^{'}) \wedge (X_{1} \leftrightarrow \ell ^{''})} pour la valuation partielle v.

On suppose donnée une fonction contr2_simplify_valp: contr2 -> int array -> eform1 option qui applique les équivalences de E(X) pour simplifier la 2-contrainte e2 en premier argument en tenant compte de

la valuation partielle vp donnée comme second argument. Plus précisement, si vp(e2)=0, alors la fonction lève l’exception Exn_unsat. Si aucune simplification ne s’applique, alors le résultat est None.

Si e2 est simplifiable, le résultat est Some(lst) avec lst une liste représentant une formule {F_{1}^{'}} de {F(L_{1})} obtenue par simplification de e2 telle que vp(e2) = vp({F_{1}^{'}}). La complexité de contr2_simplify_valp est en O(1). Algorithme.

Les questions suivantes vous guident pour prouver la correction et la terminaison de l’algorithme de décision pour {P_{L_2}} qui est mis en œuvre dans la fonction eform2_sat de la figure 1.

On cherche à démontrer que la fonction eform2_sat est correcte, c’est-à-dire qu’un appel à eform2_sat lst0 doit renvoyer None si la formule {F_{0} \in F(L_{2})} représentée par lst0 est une contradiction.

Sinon, l’appel doit renvoyer Some(v) où v est un tableau qui représente une valuation qui satisfait {F_{0}. L}’algorithme mis en œuvre pour eform2_sat est un algorithme de type retour sur trace (backtracking) dont le principe est de réduire la formule {F_{0} \in F(L_{2})} en une formule de {F(L_{1})} afin d’appliquer eform1_sat, l’algorithme polynomial de la partie I.

Pour effectuer cette réduction, eform2_sat appelle à la ligne 5 la fonction récursive eform2_sat_aux dont les arguments sont une liste l2 représentant une formule {F_{2} \in F(L_{2})}, une liste l1 représentant une formule {F_{1} \in F(L_{1})} et une valuation partielle vp.

La correction de eform2_sat_aux est un lemme important de la preuve de correction de eform2_sat. Un appel à eform2_sat_aux l2 l1 vp est correct s’il renvoie un tableau qui représente une valuation v telle que {v \models F_{2} \wedge F_{1}} et vp est compatible avec v, si une telle valuation existe ; sinon, l’appel doit lever l’exception Exn_unsat.

La mise en œuvre de eform2_sat_aux cherche à compléter la valuation partielle vp en assignant des valeurs à certaines variables (non assignées) qui apparaissent dans les littéraux de {F_{2}} et qui permettent de simplifier {F_{2}} en une formule de {F(L_{1})}.

Pour savoir si une 2-contrainte e2 de l2 peut être simplifiée en présence de vp, on appelle contr2_simplify_valp e2 vp (ligne 15). Si e2 se simplifie, l’argument l1 accumule les formules de {F(L_{1})} obtenues par la simplification et eform2_sat_aux continue la simplification du reste des contraintes de l2 (ligne 17).

Si la contrainte e2 est une contradiction en présence de vp, l’exception Exn_unsat est levée par contr2_simplify_valp e2 vp. Si e2 ne peut pas être simplifiée (ligne 19), alors eform2_sat_aux explore les simplifications de la contrainte e2 obtenues en assignant la variable du littéral e2.lr1 à chaque valeur de B.

L’exploration d’une assignation est faite grâce à un appel récursif à eform2_sat_aux avec le paramètre représentant la valuation partielle mis à jour avec l’assignation choisie.

Quand toutes les 2-contraintes de {F_{2}} ont été simplifiées, c’est-à-dire quand la liste l2 est vide (ligne 10), eform2_sat_aux appelle eform1_sat avec l’argument (l1'@l1) où la liste l1' est obtenue par l’appel à eform1_of_valp vp. Si l’appel à eform1_sat (l1'@l1) renvoie Some(v), alors le résultat de eform2_sat_aux est v.

Sinon, eform2_sat_aux lève l’exception Exn_unsat. Ceci a comme effet de revenir dans un appel précédent (sur la pile d’appels) de eform2_sat_aux pour explorer une autre assignation (lignes 25–28) ou, si toutes les assignations ont été explorées, de terminer l’appel à eform2_sat_aux l2 l1 vp par l’exception Exn_unsat.

Question 17. Démontrer que tout appel eform2_sat lst0 termine.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 18. Démontrer que la fonction eform2_sat est correcte en supposant que la fonction eform2_sat_aux est correcte.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Dans les trois questions suivantes, on cherche à démontrer les principaux cas de la correction de eform2_sat_aux.

Figure 1 – Mise en œuvre OCaml de l’algorithme de décision pour {P_{L_2}}

Question 19. Démontrez la correction de eform2_sat_aux quand son premier argument est la liste vide, on supposant que les fonctions eform1_of_valp et eform1_sat sont correctes.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 20. Soit un appel eform2_sat_aux l2 l1 vp tel que l2 est e2::l2' et contr2_simplify_vp e2 vp renvoie Some l1'.

Démontrez la correction d’un tel appel en supposant que l’appel récursif à eform2_sat_aux de la ligne 17 est correct.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 21. Soit un appel eform2_sat_aux l2 l1 vp tel que l2 est e2::l2' et contr2_simplify_vp e2 vp renvoie None.

Démontrez la correction d’un tel appel en supposant que les appels récursifs aux lignes 24 et 28 sont corrects.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 22. Donner la complexité de eform2_sat en fonction de la longueur k de la liste en argument et du nombre de variables n.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Partie III. Égalités et différences entre entiers

Dans cette partie, le domaine d’interprétation des variables est Z. La classe {L_{3}} est constituée de contraintes de la forme {X_{i} = X_{j}} et {X_{i} \ne X_{j}}, avec {X_{i}, X_{j} \in X}. Une formule F est dite construite sur {L_{3}}, notation {F \in F(L_{3})}, si elle est une conjonction de contraintes de {L_{3}}.

Une valuation est une fonction {v : X \to Z} qui assigne une valeur dans Z à chaque variable. Une valuation v satisfait une contrainte {X_{i} = X_{j}} (resp. {X_{i} \ne X_{j}}) si et seulement si {v(X_{i}) = v(X_{j})} (resp. {v(X_{i}) \ne v(X_{j})}). Une valuation v satisfait une formule {F \in F(L_{3})} si et seulement si v satisfait chaque contrainte de F .

Les notions de conséquence logique, équivalence logique, tautologie et antilogie sont définies de manière similaire à la logique propositionnelle. Le problème {P_{L_3}} peut être réduit (en temps polynomial en n et en la taille de la formule) au problème de satisfiabilité d’une formule propositionnelle.

Dans cette partie, on étudie un algorithme polynomial en n et en la taille de la formule pour {P_{L_3}}. Mise en œuvre en langage C. On propose de représenter les formules de {F(L_{3})} par des listes simplement chaînées acycliques de type eform3_t*.

Chaque cellule c (de type eform3_t) d’une telle liste représente une contrainte {e \in L_{3}} grâce à trois données : un booléen c.is_eq qui est vrai si et seulement si e est une égalité, et deux entiers positifs c.xi et c.xj qui représentent les indices des variables utilisées par e.

La formule {\top} est représentée par la liste vide de valeur NULL. On suppose que le nombre n de variables dans X est déclaré comme une variable globale entière.

Enfin, on suppose que toute structure eform3_t est bien formée, c’est-à-dire que tous les entiers qu’elle contient appartiennent à l’intervalle [1; n].

Question 23. Donner la déclaration du type eform3_t.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 24. Écrire la fonction void eform3_print(eform3_t *lst) qui affiche sur la sortie standard chaque contrainte (par exemple, sous la forme X_1 != X_4) de la formule représentée par lst, une contrainte par ligne ; si lst est NULL, la fonction affiche true sur une ligne.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Classes d’équivalence avec « unir et trouver ». On utilise une structure unir et trouver (union-find) pour résoudre le problème {P_{L_3}}. On suppose disposer d’une bibliothèque C qui fournit une implémentation d’une structure unir et trouver sur des ensembles d’entiers.

Cette bibliothèque exporte les déclarations de types et de fonctions suivantes :

Dans ce qui suit, on supposera les propriétés suivantes de cette implémentation :

— uf_create(s) renvoie un pointeur cs vers une structure unir et trouver allouée dans le tas et qui représente une partition de l’ensemble {0, . . . ,s−1} où chaque élément de l’ensemble est seul dans sa classe d’équivalence ; s donne la valeur du champ cs->nelem.

— uf_find(cs,i) renvoie la classe de i, c’est-à-dire un entier entre 0 et cs->nelem-1 qui est le représentant de cette classe.

— uf_union(cs,i1,i2) fusionne les classes de i1 et i2 en modifiant la structure *cs.

Si cs est un pointeur valide vers une structure uf_t avec cs->nelem==s, le champ cs->parent pointe vers un tableau de s entiers ayant des valeurs dans l’intervalle [0;s−1] ; ce tableau encode la relation représentant d’une classe d’équivalence. Si cs->parent[i]==j alors i est dans la classe de j.

Le tableau cs->parent est initialisé par uf_create de sorte que cs->parent[i]==i. Les opérations ci-dessus sur la structure unir et trouver ont une complexité en {\Theta (s)} pour uf_create et en O(log s) pour uf_find et uf_union.

Pour notre problème, l’ensemble de valeurs à partitionner sera [0; n], c’est-à-dire l’ensemble d’indices de variables dans X auquel on ajoute la valeur 0. La valeur 0 ne sera pas utilisée, mais elle permet d’utiliser directement les indices des variables comme positions du tableau cs->parent.

Question 25. Écrire la fonction uf_t *eform3_to_uf(eform3_t *lst) qui renvoie une valeur de type uf_t* représentant les classes d’équivalence sur [0; n] définies par les contraintes d’égalité de la formule représentée par lst. On suppose que lst n’est pas vide.

Les contraintes de différence de lst seront ignorées dans cette fonction.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Algorithme de décision. D’après la question précédente, la structure unir et trouver permet donc de représenter les contraintes d’égalité d’une formule de {F(L_{3})}, mais pas les contraintes de différence.

Question 26. Écrire une fonction bool eform3_sat_int(eform3_t *lst) qui renvoie true si et seulement si toutes les contraintes de différence de la formule représentée par lst peuvent être satisfaites en respectant les contraintes d’égalité de lst. Si lst est NULL, la fonction renvoie true.

Justifier la correction de la fonction eform3_sat_int, c’est-à-dire, que tout appel à cette fonction renvoie true si et seulement si la formule représentée par son argument est satisfiable.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 27. On suppose que lst (de type eform3_t*) représente une formule avec un nombre de contraintes d’égalité égal à e et un nombre de contraintes de différence égal à d.

Donner la complexité de l’appel à eform3_sat_int(lst) en fonction de n, e et d.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Calculer une valuation. Dans la suite de cette partie, on se propose de calculer, si elle existe, une valuation {v : X \to Z} qui satisfait une formule de {F(L_{3})}.

Question 28. Écrire une fonction int uf_classes(uf_t *cs) qui renvoie le nombre de classes d’équivalence de la structure unir et trouver pointée par cs ; si cs est NULL alors la fonction renvoie 0.

Cette fonction doit avoir une complexité en O(n).

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 29. Écrire une fonction int *uf_valuation(uf_t *cs) qui renvoie un pointeur v vers un tableau de n + 1 entiers, alloué dans le tas. Soit k le résultat de l’appel de uf_classes(cs). On suppose {k \geq 2}. Pour tout {i \in [1; n], v[i]} est une valeur entière dans l’intervalle [1; k] qui

est associée de façon unique à (chaque élément de) la classe d’équivalence de i. La case à la position 0 de v n’est pas utilisée, mais elle permet d’utiliser les indices des variables comme positions de v ; par convention, v[0]==0.

La fonction uf_valuation doit avoir une complexité en O(n log n).

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Soit {F \in F(L_{3})} une formule représentée par une liste lst différente de NULL telle que eform3_sat_int(lst) renvoie true. Soient cs le résultat de l’appel à eform3_to_uf(lst) et v le résultat de l’appel à uf_valuation(cs).

Question 30. Démontrer que v pointe vers un tableau qui représente une valuation qui satisfait la formule F représentée par lst.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 31. Si F ne contient pas de contraintes de différence, alors quel est le nombre minimal de valeurs que peut avoir l’image d’une valuation v qui satisfait F ? Donner un exemple d’une telle valuation.

La même question si F contient une seule contrainte de différence.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 32. Lorsque cela est possible, on souhaite construire une valuation v telle que v satisfait la formule F et l’image de v est un ensemble de taille 2. Quel algorithme sur les graphes peut être utilisé pour obtenir ce résultat et sur quel graphe ?

Vous devez définir formellement le graphe en entrée de l’algorithme et donner la complexité de l’algorithme.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Partie IV. Égalités et différences d’expressions

Soit F un ensemble de m symboles de fonction notés {f_{i} (1 \leq i \leq m)}. Chaque symbole {f_{i} \in F} représente une fonction de {Z \times Z} dans Z. Comme dans la partie III, on interprète les variables {X_{i}} de X dans l’ensemble Z.

Les symboles de fonctions de F ne sont pas interprétés, c’est-à-dire qu’ils n’ont pas de définition. Les seules propriétés connues de ces fonctions sont données sous la forme de contraintes d’égalité ou de différence. Ces contraintes sont celles de la classe {L_{4}} définie ci-dessous.

Les contraintes de la classe {L_{4}} utilisent des termes. Un terme t est défini de manière inductive : t est

— soit {X_{i}}, une variable de X,

— soit {f_{i}(t_{1}, t_{2})}, l’application d’une fonction {f_{i} \in F} sur deux termes {t_{1}} et {t_{2}}, appelés aussi sous-termes directs de t.

Le symbole racine du terme {X_{i}} (resp. {f_{j}(t_{1}, t_{2})}) est {X_{i}} (resp. {f_{j}}). On note T(X, F) l’ensemble des termes construits avec les variables de X et les symboles de fonctions F. Ces termes peuvent être représentés par des arbres binaires.

La figure 2 représente les arbres binaires correspondants aux termes : {t_{1}\overset{\mathrm{def}}= f_{1}(f_{2}(X_{1}, X_{2}), X_{2})} et {t_{2}\overset{\mathrm{def}}= f_{3}(X_{1}, f_{2}(X_{1}, X_{2}))}. Chaque noeud de ces arbres est étiqueté en partie haute par un nom unique {u_{i}} appelé identifiant, et en partie basse par le symbole racine du terme.

L’arc à gauche (resp. droite) d’un noeud correspond au premier (resp. second) sous-terme direct et est dessiné avec un trait en pointillé (resp. plein).

Arbres des termes t1 et t2

Figure 2 – Arbres des termes {t_{1}} (à gauche) et {t_{2}} (à droite) L’ensemble des sous-termes d’un terme t, noté T (t), est défini de manière inductive par {\mathcal T(X_i)\overset{\mathrm{def}}=\{X_i\}} et {\mathcal T(f_i(t_1,t_2))\overset{\mathrm{def}}=\{f_i(t_1,t_2)\}\cup\mathcal T(t_1)\cup\mathcal T(t_2)}.

Question 33. Donner {T (t_{1})} et {T (t_{2})} pour les termes {t_{1}} et {t_{2}} définis ci-dessus.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

La classe {L_{4}} contient les contraintes de la forme {t_{1} = t_{2}} ou {t_{1} \ne t_{2}} avec {t_{1}} et {t_{2}} des termes.

Une formule F de {F(L_{4})} est une conjonction de contraintes {L_{4}. L}’ensemble des sous-termes d’une contrainte e de la forme {t_{1} = t_{2}} ou {t_{1} \ne t_{2}} est {T (e)\overset{\mathrm{def}}= T (t_{1}) \cup T (t_{2}). L}’ensemble des sous-termes d’une formule F de {F(L_{4})}, noté T (F ), est défini comme l’union des ensembles des sous-termes de toutes les contraintes de F .

Mise en œuvre en langage C. Afin de concevoir un algorithme de décision pour {P (L_{4})}, nous représentons les contraintes d’égalité d’une formule de {F(L_{4})} par une structure de données appelée e-graphe. Cette structure combine un graphe orienté acyclique avec une structure unir et trouver.

Les définitions de types C suivantes codent une structure de e-graphe :

Dans ce qui suit, on supposera les propriétés suivantes de la mise en œuvre de la structure egraph_t.

Si g est une variable de type egraph_t représentant les contraintes d’égalité d’une formule {F \in F(L_{4})}, g.tset code l’ensemble des sous-termes T (F ) comme suit :

— g.tset est un pointeur vers le debut d’un tableau de taille g.tset_sz ;

— g.tset[0] est initialisée à la valeur {.label=NULL, .li=0, .ri=0} (premier champ à NULL et les suivants à 0) de tnode_t, valeur qu’on appelle aussi terme vide ;

— un terme est représenté par une valeur t de type tnode_t tel que t.label est une chaîne de caractères qui décrit le symbole racine du terme, et t.li (resp. t.ri) est la position du premier (resp. second) sous-terme direct dans le tableau g.tset.

Un invariant du tableau g.tset est le suivant : tout terme de T (F ) est représenté par une unique position du tableau g.tset. Cet invariant correspond à une factorisation des termes communs et donc à la transformation de l’ensemble des arbres correspondant aux termes en un graphe orienté acyclique.

Par exemple, considérons la formule {t_{1} = t_{2}} utilisant les termes représentés dans la figure 2. La partie droite de la figure 3 représente le tableau g.tset de l’ensemble des sous-termes de la formule (une valeur tnode_t par ligne) et le terme vide.

La partie gauche de la figure 3 représente le tableau g.tset sous la forme d’un graphe orienté acyclique : les noeuds contiennent en partie haute la position du terme dans le tableau g.tset et, pour la lisibilité de la transformation, l’ensemble des identifiants {u_{i}} des nœuds correspondants dans les arbres de la figure 2.

La partie basse d’un noeud donne le symbole racine. La composante g.pre de l’e-graphe contient la relation prédécesseur dans le graphe dirigé acyclique représenté par g.tset. Cette relation est donnée sous la forme d’une matrice linéarisée en un tableau de {\texttt{g.tset\textunderscore sz}^{2}} valeurs dans l’ensemble {0, 1}.

Si g.pre[i*g.tset_sz + j] vaut 1 alors le terme g.tset[i] a comme sous-terme direct le terme g.tset[j]. La valeur 0 dénote que g.tset[j] n’est pas sous-terme direct de g.tset[i].

Codage des sous-termes en graphe et en tableau

Figure 3 – Codage de {T (t_{1} = t_{2})} sous forme de graphe (à gauche) et de tableau (à droite) La composante g.uf de l’e-graphe g code les classes d’équivalence définies par les contraintes d’égalités entre les termes.

La définition du type uf_t est la même que celle donnée dans la partie III. Chaque terme est représenté dans l’ensemble des éléments de g.uf par la position du noeud racine du terme dans le tableau g.tset.

Par conséquent, un autre invariant de la structure d’e-graphe est le suivant : g.uf->nelem est égal à g.tset_sz.

Question 34. Écrire la fonction bool term_is_diff(egraph_t *g, int tid1, int tid2) qui teste si la contrainte {t_{1} \ne t_{2}} peut être satisfaite simultanément aux contraintes d’égalité représentées par g.

Les paramètres tid1 et tid2 sont les positions dans g.tset des noeuds racine représentant respectivement {t_{1}} et {t_{2}}.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Congruence. Soient {F \in F(L_{4})} et g un e-graphe tel que g.tset représente T (F ), l’ensemble des sous-termes de F .

On définit la relation d’équivalence {\equiv _{g}} sur T (F ) par : {t \equiv _{g} t^{'}} si et seulement si t et {t^{'}} sont représentés par des positions de g.tset qui sont dans la même classe d’équivalence de g.uf.

On définit la relation {C_{g}} sur T (F ) comme suit : pour tout {f_{i} \in F} et pour tout quadruplet de termes {(t_1,t_2,t_3,t_4)\in\mathcal T(F)^4}, si {t_{1} \equiv _{g} t_{3}} et {t_{2} \equiv _{g} t_{4}}, alors {(f_{i}(t_{1}, t_{2}), f_{i}(t_{3}, t_{4})) \in C_{g}}.

Question 35. Écrire la fonction bool term_is_congruent(egraph_t *g, int tid1, int tid2) qui renvoie true si et seulement si les termes représentés aux positions tid1 et tid2 dans g.tset sont dans la relation {C_{g}}.

Donner et justifier soigneusement la complexité de term_is_congruent en fonction de g.tset_sz, si on suppose que le test d’égalité d’étiquettes (symboles de X ou F) se fait en temps constant.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Algorithme de décision. Soient {F \in F(L_{4})} et g un e-graphe tel que g représente les termes et les contraintes d’égalité de F ainsi que deux termes {t_{1}} et {t_{2}}, pas nécessairement dans T (F ). On cherche à modifier g pour qu’il représente {F \wedge (t_{1} = t_{2})}.

On appelle cette opération merge(g, t1, t2) pour faire la différence avec uf_union. En effet, cette opération ne peut pas se limiter à appeler uf_union pour unir les classes d’équivalence des positions représentant les termes {t_{1}} et {t_{2}}.

Elle doit en plus tester si {(t^{'}_{1}, t^{'}_{2}) \in C_{g}} pour tous termes {t^{'}_{1}} et {t^{'}_{2}} qui incluent respectivement {t_{1}} et {t_{2}} comme sous-termes directs mais ne sont pas déjà dans la même

classe d’équivalence. Si {(t^{'}_{1}, t^{'}_{2}) \in C_{g}}, alors {t^{'}_{1}} et {t^{'}_{2}} doivent être mis dans la même classe d’équivalence. Cette dernière relation est propagée aux termes qui contiennent {t^{'}_{1}} et {t^{'}_{2}} et ainsi de suite, tant qu’on trouve des couples de termes dans la relation {C_{g}} mais pas dans {\equiv _{g}}.

Question 36. Écrire une fonction récursive void merge(egraph_t *g, int tid1, int tid2) qui ajoute la contrainte d’égalité entre les termes aux positions tid1 et tid2 dans g.tset, selon le processus décrit ci-dessus.

Si tid1 et tid2 sont déjà dans la même classe d’équivalence, merge termine immédiatement.

Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 37. Donner le nombre maximum d’appels récursifs qu’un appel à merge peut engendrer en fonction de g.tset_sz.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :
Question 38. On suppose que le graphe orienté acyclique représenté par g.tset contient a arêtes. Donner la complexité en temps de l’opération merge en fonction de g.tset_sz et a.
Cliquer ici pour voir (ou cacher) la réponse
Pour voir la suite de ce contenu, vous devez : Pour poursuivre votre exploration, vous pouvez :

Fin du sujet.

Annexe : Preuves en déduction naturelle

Un séquent {\Gamma \vdash F} est composé d’un ensemble de formules {\Gamma} et d’une formule F . Le sous-ensemble suivant de règles de la déduction naturelle peut être utilisé dans la preuve demandée à la question 14.

Règles de déduction naturelle rappelées en annexe