Planification avec PSC (Partie 1)
=================================

Exercice 1 : Modélisation sur papier
------------------------------------

Il existe souvent différentes façons de modéliser un problème de planification donné sous la forme d'un Problème de Satisfaction de Contraintes. Le modèle que nous vous proposons ici correspond à celui qui est décrit dans le chapitre 10.6 du cours. Si vous avez développé une solution différente, il est cependant possible qu'elle soit tout aussi valide.

Si vous pensez avoir réussi à élaborer un modèle PSC différent et qui vous semble tout aussi valide, nous vous invitons à en discuter avec un(e) assistant(e) pendant la séance d'exercices.

Définition du problème de planification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Rappelons qu'un problème de planification est défini par les éléments suivants : 

*  Une liste de propositions qui décrivent l'état du monde. 
*  Une liste d'opérateurs qui décrivent les actions qui peuvent être exécutées pour changer l'état du monde. Chaque opérateur possède des préconditions et des postconditions. 
*  Des conditions initiales, qui décrivent complètement l'état initial du monde en termes de propositions. 
*  Des conditions finales, qui décrivent complètement ou partiellement l'état du monde désiré en termes de propositions. 
*  Des mutex, qui stipulent des contraintes d'exclusion mutuelle entre les propositions et entre les opérateurs. Les mutex de propositions sont des paires de propositions qui ne peuvent être vraies en même temps. Par exemple, un missionnaire ne peut pas simultanément se trouver sur la rive gauche et sur la rive droite. Les mutex d'opérateurs définissent les paires d'opérateurs qui ne peuvent être exécutés en même temps. Par exemple, un même bateau ne peut pas être piloté à la fois par le missionnaire :math:`M_{1}` pour transporter le canibale :math:`C_{1}` et par :math:`M_{2}` pour transporter :math:`C_{2}`.

Dans notre problème de planification, nous considérons trois types d'acteurs :

* Les bateaux, qui correspondent au type :math:`B`. Dans notre exemple, il n'y a qu'un seul bateau, dénoté par :math:`B`. 
*  Les missionnaires, qui correspondent au type :math:`M`. Il y a deux missionnaires, :math:`M_{1}` et :math:`M_{2}`.
*  Les cannibales, qui correspondent au type :math:`C`. Il y a deux cannibales, :math:`C_{1}` et :math:`C_{2}`.

Le choix des propositions du problème de planification doit permettre de décrire complètement la position de chaque acteur. 

Notre problème présente aussi deux types d'actions possibles :

*  Traversée du fleuve depuis la rive gauche jusqu'à la rive droite ;
*  Traversée du fleuve depuis la rive droite jusqu'à la rive gauche.

Chacun de ces deux types d'actions peut faire intervenir un certain nombre d'acteurs. Par exemple, une traversée ne peut avoir lieu qu'à l'aide d'un bateau (c'est-à-dire un acteur de type :math:`B`). Seul un missionnaire (c'est-à-dire un acteur de type :math:`M`) peut conduire le bateau. Le bateau ne peut contenir qu'un passager supplémentaire, qui peut être indifféremment de type :math:`M` (un missionnaire) ou de type :math:`C` (un cannibale). La liste des opérateurs du problème de planification doit couvrir toutes les combinaisons d'acteurs pour les deux types d'actions. 

Choix des propositions du problème de planification
___________________________________________________

Afin de décrire la position d'un acteur, nous vous proposons d'utiliser deux propositions, correspondant aux deux positions possibles de l'acteur (rive gauche ou rive droite). Pour un acteur :math:`A` donné, nous introduisons donc les deux propositions suivantes :

*  :math:`g(A)` est vraie si et seulement si l'acteur :math:`A` est sur la rive gauche ; 
*  :math:`d(A)` est vraie si et seulement si l'acteur :math:`A` est sur la rive droite.

Ces deux propositions peuvent vous sembler redondantes, puisque :math:`A` est nécessairement sur la rive droite s'il n'est pas sur la rive gauche (:math:`d(A)` est vraie si :math:`g(A)` est fausse et vice-versa). Nous avons cependant choisi cette représentation car elle se généralise aisément à des problèmes plus compliqués, dans lesquels un acteur peut occuper plus de deux positions.

Choix des opérateurs du problème de planification
_________________________________________________

Comme nous l'avons indiqué plus haut, des opérateurs seront nécessaires pour modéliser la traversée du fleuve par des groupes d'acteurs. Pour un état donné, les opérateurs seront les suivants :

* :math:`gd(B, M, A)` est l'opérateur décrivant la traversée du fleuve de gauche à droite, à bord du bateau :math:`B`, piloté par le missionnaire :math:`M`, avec pour passager :math:`A` (qui peut par ailleurs être de type :math:`M` ou :math:`C`). Les préconditions de cet opérateur sont que les propositions :math:`g(B)`, :math:`g(M)` et :math:`g(A)` doivent être vraies. Les postconditions sont que les propositions :math:`d(B)`, :math:`d(M)` et :math:`d(A)` doivent être vraies.  
* :math:`dg(B, M)` est l'opérateur décrivant la traversée de droite à gauche du missionnaire :math:`M` à bord du bateau :math:`B`. Les préconditions de cet opérateur sont que les propositions :math:`d(B)` et :math:`d(M)` doivent être vraies. Les postconditions sont que les propositions :math:`g(B)` et :math:`g(M)` doivent être vraies.

Le choix a été fait dans ce modèle de ne pas tenir compte de la possibilité pour un bateau de faire la traversée de droite à gauche avec un passager en plus du pilote. Ce choix limite l'éventail des plans valides possibles. Il a l'avantage de correspondre à un problème de planification plus petit, plus facile à résoudre, et qui générera des plans plus courts (puisqu'il n'est alors pas permis de perdre du temps à faire traverser un cannibale dans un sens, puis dans l'autre sens). Dans cet exercice, sachant que les deux cannibales sont sur la rive gauche dans l'état initial, il est très facile de constater qu'il existe un plan valide qui ne fait jamais traverser un cannibale de la rive droite à la rive gauche. Il faut cependant se rappeler, que, dans le cas d'un problème plus général, un tel choix de modélisation pourrait déboucher sur un problème de planification infaisable, quand bien même le problème initial serait soluble. 

Conditions initiales du problème de planification
_________________________________________________

Au début, tous les acteurs sont sur la rive gauche. Les conditions initiales sont donc les suivantes :

.. math:: 
    g(B) = g(M_1) = g(M_2) = g(C_1) = g(C_2) = True

Conditions finales du problème de planification
_______________________________________________

Le but est de faire passer tous les acteurs du côté droit de la rivière. Les conditions finales sont donc les suivantes :

.. math:: 
    d(B) = d(M_1) = d(M_2) = d(C_1) = d(C_2) = True

Mutex de propositions du problème de planification
__________________________________________________

Les mutex de propositions sont des paires de propositions qui ne peuvent être vraies en même temps. Dans notre problème, pour chaque acteur :math:`A`, nous avons le mutex suivant : 

.. math:: 
    [g(A), d(A)]

En effet, un acteur ne peut se trouver en même temps sur la rive gauche et sur la rive droite.

Mutex d'opérateurs du problème de planification
_______________________________________________

Les mutex d'opérateurs sont des paires d'opérateurs qui ne peuvent être exécutés en même temps. Dans notre problème, pour chaque paire d'opérateurs distincts :math:`op_1` et :math:`op_2`, si les deux opérateurs ont un acteur en commun (qu'il soit de type :math:`B`, :math:`M` ou :math:`C`), alors on a le mutex suivant :

.. math:: 
    [op_1, op_2]

Dans le cas où l'acteur en commun est de type :math:`B`, ce mutex traduit le fait qu'un même bateau ne peut pas contenir deux équipages différents en même temps. Dans le cas où l'acteur en commun est de type :math:`M` ou :math:`C`, et si l'on a plusieurs bateaux à disposition, un même missionnaire ou un même cannibale ne peut se trouver sur deux bateaux différents en même temps.

Définition d'un PSC correspondant
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Rappelons qu'un plan non linéaire est constitué d'une séquence d'états :math:`S_{0}`, :math:`S_{1}`, ..., :math:`S_{n}`, chaque état :math:`S_{i}` étant décrit par des propositions qui sont vraies ou fausses au début de l'état, un ensemble d'opérateurs qui sont exécutés pendant cet état, et des propositions qui sont vraies ou fausses à la fin de l'état (après l'exécution des opérateurs), et qui correspondent aux propositions du début de l'état suivant.

Un opérateur est caractérisé par des préconditions, c'est-à-dire des propositions qui doivent être vraies au début de l'état :math:`S_{i}` pour que l'opérateur puisse être exécuté durant :math:`S_{i}`, et des postconditions, c'est-à-dire des propositions qui devront être vraies au début de l'état :math:`S_{i+1}`.

Choix des variables PSC pour les propositions
_____________________________________________

Pour chaque état :math:`S_{i}`, avec :math:`i = 0...n+1`, et chaque acteur :math:`A`, le PSC contiendra les variables booléennes suivantes, qui correspondent aux propositions du problème de planification introduites plus haut :

* :math:`g(A, S_{i}) = True` si et seulement si l'acteur :math:`A` est sur la rive gauche à la fin de l'état :math:`S_{i-1}` et au début de l'état :math:`S_{i}` ; 
* :math:`d(A, S_{i}) = True` si et seulement si l'acteur :math:`A` est sur la rive droite à la fin de l'état :math:`S_{i-1}` et au début de l'état :math:`S_{i}`. 

Choix des variables PSC pour les opérateurs
___________________________________________

Pour chaque état :math:`S_{i}`, avec :math:`i = 0...n`, et chaque opérateur :math:`op` (par exemple, :math:`dg(B, M_{1})`), le PSC contiendra la variable booléenne :math:`op(S_{i})`, qui indiquera si oui ou non l'opérateur est exécuté dans l'état :math:`S_{i}`.

Expression des contraintes du PSC
_________________________________

Il existe six types de contraintes qui doivent être vérifiées pour qu'un plan soit valide :

* Les contraintes sur l'état initial ;
* Les contraintes sur l'état final ;
* Les préconditions et les postconditions des opérateurs ;
* Les axiomes de cadre ;
* Les mutex de propositions ;
* Les mutex d'opérateurs.

**Contraintes PSC correspondant aux contraintes sur l'état initial** Tous les acteurs sont initialement sur la rive gauche. Par conséquent, les contraintes sur l'état initial prennent une forme très simple, qui est la suivante :

.. math:: 
    g(B, S_0) = g(M_1, S_0) = g(M_2, S_0) = g(C_1, S_0) = g(C_2, S_0) = True  

**Contraintes PSC correspondant aux contraintes sur l'état final** Les contraintes sur l'état final sont comparables aux contraintes sur l'état initial, à la différence fondamentale près qu'il faut définir lequel des états :math:`S_{0}`, :math:`S_{1}`, ..., :math:`S_{n}` correspond à cet état final. En d'autres termes, il faut choisir par avance la longueur des plans solutions que l'on veut considérer, c'est-à-dire le nombre total d'états. 

Si l'on ne considère que des plans très courts, il est possible que l'on n'obtienne aucune solution, car il peut n'exister aucun plan qui parvienne à passer de l'état initial à l'état final avec si peu d'états intermédiaires. Si en revanche on autorise des plans très longs, la taille du PSC et la complexité de sa résolution explosent. Traditionnellement, on commence donc par des plans courts, et on augmente progressivement leur taille tant qu'aucune solution n'est découverte.

Dans notre cas, le problème se résout très facilement à la main. On peut donc tricher et choisir le nombre d'états en sachant qu'il existe un plan solution qui le contient. Les étapes suivantes constituent ainsi une solution possible, sachant que le but à atteindre est d'avoir tous les acteurs sur la rive droite :

* :math:`gd(B, M_{1}, C_{1})` 
* :math:`dg(B, M_{1})`
* :math:`gd(B, M_{1}, C_{2})`
* :math:`dg(B, M_{1})`
* :math:`gd(B, M_{1}, M_{2})`.

Il suffit donc de cinq étapes, soit cinq états (sans compter l'état initial :math:`S_{0}`) pour obtenir l'état final désiré. On choisira donc :math:`S_{5}` comme état final. Les contraintes sur l'état final sont par conséquent les suivantes :

.. math:: 
    d(B, S_5) = d(M_1, S_5) = d(M_2, S_5) = d(C_1, S_5) = d(C_2, S_5) = True  

**Contraintes PSC correspondant aux contraintes de préconditions et de postconditions des opérateurs** Reprenons les deux types d'opérateurs introduits précédemment et explicitons leurs préconditions et postconditions en termes de variables du PSC : 

*  L'opérateur :math:`gd(B, M, A)` a pour préconditions que :math:`g(B)`, :math:`g(M)` et :math:`g(A)` doivent toutes être vraies. Les postconditions stipulent que :math:`d(B)`, :math:`d(M)` et :math:`d(A)` doivent toutes être vraies. 
*  L'opérateur :math:`dg(B, M)` a pour préconditions que :math:`d(B)` et :math:`d(M)` doivent être vraies. Les postconditions stipulent que :math:`g(B)` et :math:`g(M)` doivent être vraies. 

On n'a pas mentionné ici les suppressions de chaque opérateur. Par exemple, une autre postcondition de :math:`dg(B, M)` est que :math:`d(B)` doit être fausse. Il n'est cependant pas nécessaire d'expliciter ces postconditions négatives, car elles seront automatiquement imposées par les mutex de propositions introduits précédemment et présentés en détail plus bas.

En termes de PSC, ces préconditions et postconditions prennent la forme des contraintes suivantes sur les variables du PSC, pour chaque état :math:`S_{i}` avec :math:`i = 0...n` et chaque opérateur :math:`op` :

* Pour chaque proposition :math:`prop` qui est une postcondition de l'opérateur :math:`op`, on a : :math:`op(S_{i}) \Rightarrow prop(S_{i+1})` ;
* Pour chaque proposition :math:`prop` qui est une précondition de l'opérateur :math:`op`, on a : :math:`op(S_{i}) \Rightarrow prop(S_{i})`.

On utilise ici la notation :math:`\Rightarrow` pour indiquer l'implication logique. Ainsi, si :math:`prop` est une postcondition de l'opérateur :math:`op`, alors :math:`op(S_{i}) \Rightarrow prop(S_{i+1})` est équivalent à si :math:`op(S_{i}) = True`, alors :math:`prop(S_{i+1}) = True`. Ceci traduit bien le fait que si l'opérateur :math:`op` est exécuté dans l'état :math:`S_{i}`, alors sa postcondition :math:`prop` doit être vraie à la fin de l'état :math:`S_{i}`, c'est-à-dire au début de l'état :math:`S_{i+1}`.

**Contraintes PSC correspondant aux axiomes de cadre** Les axiomes de cadre stipulent que si aucun opérateur ne vient, dans l'état :math:`S_{i}`, modifier une proposition :math:`prop`, alors elle reste identique à l'état :math:`S_{i+1}`. De manière équivalente : si la valeur d'une proposition change d'un état :math:`S_{i}` à l'état suivant :math:`S_{i+1}`, c'est que l'opérateur :math:`op(S_{i})` y est pour quelque chose. Par exemple, si :math:`g(C_{1}, S_{i}) = True` et :math:`g(C_{1}, S_{i+1}) = False`, alors :math:`op(S_{i})` est nécessairement l'un des deux opérateurs suivants : :math:`gd(B, M_{1}, C_{1})` ou :math:`gd(B, M_{2}, C_{1})`.

Remarquez que cette contrainte est de formulation relativement complexe, et en particulier qu'elle implique plus de deux variables : :math:`prop(S_{i})`, :math:`prop(S_{i+1})`, et les variables pour l'état :math:`S_{i}` de tous les opérateurs qui ont :math:`prop` comme précondition ou postcondition. Plus précisément, les contraintes d'axiomes de cadre prennent les deux formes suivantes, pour chaque état :math:`S_{i}`, et pour chaque proposition :math:`prop` :

* Si :math:`prop(S_{i}) = False` et :math:`prop(S_{i+1}) = True`, alors pour au moins un opérateur :math:`op` qui a :math:`prop` comme postcondition, on a :math:`op(S_{i}) = True` ;
* Si :math:`prop(S_{i}) = True` et :math:`prop(S_{i+1}) = False`, alors pour au moins un opérateur :math:`op` qui a :math:`prop` comme postcondition négative, on a :math:`op(S_{i}) = True`.

Comme indiqué précédemment, il n'est pas nécessaire de considérer le deuxième cas, qui comporte une postcondition négative. En effet, si nous prenons l'exemple de la proposition :math:`d(M_{1})`, lorsque celle-ci passe de :math:`True` à :math:`False`, il n'est pas nécessaire de vérifier que l'opérateur :math:`dg(M_{1})` est exécuté, puisqu'alors les contraintes de mutex de propositions imposeront que :math:`g(M_{1})` passe (à l'inverse) de :math:`False` à :math:`True`. Le premier cas ci-dessus suffit alors pour assurer que :math:`dg(M_{1})` soit exécuté. 

Rappelons que ces contraintes ne sont pas simplement unaires ou binaires, mais qu'elles ont une multiplicité plus grande que deux. Il faut donc aussi modifier le module PSC pour qu'il puisse manipuler de telles contraintes.

**Contraintes PSC correspondant aux mutex de propositions** Les mutex de propositions sont des contraintes qui stipulent que deux propositions sont mutuellement exclusives, c'est-à-dire qu'elles sont incompatibles et ne peuvent pas être vraies en même temps. Par exemple, on ne peut pas avoir :math:`g(M_{1})` et :math:`d(M_{1})` vraies en même temps, puisque le missionnaire :math:`M_{1}` ne peut pas se trouver à la fois sur la rive droite et sur la rive gauche.

Plus généralement donc, pour chaque état :math:`S_{i}`, et pour chaque acteur :math:`A` (de n'importe quel type), le modèle PSC contiendra les contraintes suivantes :

.. math:: 
    g(A, S_i)\text{ NAND }d(A, S_i)

Cette contrainte autorise virtuellement qu'un acteur puisse n'être ni sur la rive gauche, si sur la rive droite (:math:`g(A, S_{i}) = d(A, S_{i}) = False`). Mais, dans la pratique, cette situation ne pourra pas se produire. Ceci peut se démontrer par récurrence : Nous partons du principe que les contraintes sur l'état initial décrivent complètement la situation initiale. C'est-à-dire qu'au début de l'état :math:`0`, un acteur est nécessairement sur une rive donnée. Considérons maintenant l'état :math:`k`, et supposons qu'un acteur se trouve sur la rive gauche (:math:`g(A) = True`) au début de cet état (le raisonnement est tout aussi valide si l'on remplace gauche par droite et droite par gauche). Deux possibilités se présentent alors pour les opérateurs exécutés dans l'état :math:`k` : 1) aucun opérateur n'a :math:`g(A)` comme précondition, et alors les contraintes d'axiomes de cadre vont imposer que :math:`g(A)` reste vraie à la fin de l'état ; 2) au moins un opérateur a :math:`g(A)` comme précondition, et alors les contraintes de postconditions vont imposer que :math:`d(A)` soit vraie à la fin de l'état. Dans les deux cas, la rive sur laquelle se trouve l'acteur à la fin de l'état est clairement définie.

**Contraintes PSC correspondant aux mutex d'opérateurs** De manière analogue aux mutex de propositions, chaque mutex d'opérateur :math:`[op_1, op_2]` va donner lieu, pour chaque état :math:`S_{i}`, à la contrainte suivante :

.. math:: 
    op_1(S_i)\text{ NAND }op_2(S_i)

Résumé du modèle PSC
____________________

Au bout du compte, voici le modèle que nous obtenons :

**Variables :**

* Variables booléennes :math:`g(A, S_{i})`, pour chaque acteur :math:`A` et chaque état :math:`S_{i}` ;
* Variables booléennes :math:`d(A, S_{i})`, pour chaque acteur :math:`A` et chaque état :math:`S_{i}` ;
* Variables booléennes :math:`op(S_{i})`, pour chaque opérateur :math:`op` et chaque état :math:`S_{i}`.

**Contraintes :**

* Contraintes sur l'état initial : 

.. math:: 
    g(B, S_0) = g(M_1, S_0) = g(M_2, S_0) = g(C_1, S_0) = g(C_2, S_0) = True 

* Contraintes sur l'état final :

.. math:: 
    d(B, S_5) = d(M_1, S_5) = d(M_2, S_5) = d(C_1, S_5) = d(C_2, S_5) = True 

* Contraintes de préconditions et postconditions des opérateurs, pour chaque état :math:`S_{i}` et chaque opérateur :math:`op` : 
    
        * Pour chaque proposition :math:`prop` qui est une postcondition de l'opérateur :math:`op` : :math:`op(S_{i}) \Rightarrow prop(S_{i+1})`.
        * Pour chaque proposition :math:`prop` qui est une précondition de l'opérateur :math:`op` : :math:`op(S_{i}) \Rightarrow prop(S_{i})`.
    

*  Contraintes d'axiomes de cadre, pour chaque état :math:`S_{i}` et chaque proposition :math:`prop` : 
    
        * Si :math:`prop(S_{i}) = False` et :math:`prop(S_{i+1}) = True`, alors pour au moins un opérateur :math:`op` qui a :math:`prop` comme postcondition, on a :math:`op(S_{i}) = True`
    

*  Contraintes de mutex de propositions, pour chaque état :math:`S_{i}` et pour chaque acteur :math:`A` :
     
.. math:: 
         g(A, S_i)\text{ NAND }d(A, S_i)
     

* Contraintes de mutex d'opérateurs, pour chaque état :math:`S_{i}`, et pour chaque paire d'opérateurs distincts :math:`op_1` et :math:`op_2` qui ont un acteur en commun :
    
.. math:: 
        op_1(S_i)\text{ NAND }op_2(S_i)
     

Dans ce PSC, toutes les variables sont booléennes. Typiquement, plutôt qu'un algorithme PSC, on utiliserait un algorithme SAT pour résoudre ce genre de problèmes. Un algorithme SAT est en effet spécialisé dans la résolution de PSC exprimés sous la forme de clauses (des expressions qui peuvent être vraies ou fausses) ne comportant que des variables booléennes.
