Previous Contents Next

4   Opérations sur les polyèdres

Pour les opérations sur les polyèdres SPPoC repose presque entièrement sur la bibliothèque polyédrique connue sous le nom de PolyLib [23]. À l'origine, la PolyLib a été développée à l'IRISA de Rennes par Doran Wilde dans le cadre du projet Alpha . Une seconde version de la PolyLib a vu le jour en collaboration avec Philippe Clauss et Vincent Loechner de l'ICPS à Strasbourg [5]. Cette version permet de manipuler des polyèdres paramétrés, en particulier de trouver leurs sommets et de compter le nombre de points entiers qu'ils contiennent.

L'interface entre SPPoC et la PolyLib utilise le fait que des routines écrites en C peuvent être utilisées en Objective Caml par simple édition de liens. Le plus difficile est d'écrire les fonctions de conversion des structures Caml en structures C et vice-versa.

4.1   Construction des polyèdres

La PolyLib manipule en fait des unions de polyèdres, chaque polyèdre étant défini par un couple formé de ses contraintes et d'une représentation duale à base de sommets, de rayons et de droites. Ces informations sont redondantes, la forme duale pouvant être obtenue à partir des contraintes et réciproquement, mais elles permettent d'effectuer les calculs plus rapidement. Pour la même raison SPPoC manipule des listes de couples.

Il est possible de créer un polyèdre directement à partir des deux informations mais c'est déconseillé car aucun contrôle de cohérence n'est effectué. On peut cependant vouloir utiliser cette possibilité pour des raisons de performance. Il est beaucoup plus pratique de créer un polyèdre à partir d'un simple système de contraintes :
# let p = Polyhedron.of_system <:s<3*i+j>=10, 1<=i<=n, 1<=j<=m>>;;
val p : SPPoC.Polyhedron.t =
  {({j <= m, j >= 1, i <= n, i >= 1, 3*i+j >= 10},
    {ray(m), ray(n), vertex(i+7*j+7*m+n),
     ray(j+m), ray(i+n), vertex(3*i+j+m+3*n)})}
La présence de rayons dans la forme duale (calculée à partir des contraintes) nous apprend qu'il ne s'agit pas d'un polyèdre borné. En effet, le translaté d'un polyèdre suivant la direction d'un de ses rayons est inclus dans le polyèdre original.

Il est aussi possible de créer un polyèdre en spécifiant sa forme duale. Prenons l'exemple d'une bande diagonale dans un espace à deux dimensions dont les bords passent par les points (0,1) et (1,0). Sa représentation duale est constituée de ces deux sommets et de la ligne de vecteur directeur (1,1) :
# let p = Polyhedron.of_rays <:r<vertex(i), vertex(j), line(i+j)>>;;
val p : SPPoC.Polyhedron.t =
  {({i-j <= 1, i-j >= -1}, {line(i+j), vertex(j), vertex(-j)})}
La représentation de p fait bien apparaître les deux inéquations définissant la bande.

De plus SPPoC permet la création de polyèdres ne contenant aucun point ou tous les points d'un espace défini par le nom des variables associées à ses dimensions. Enfin, des fonctions sont disponibles pour créer directement des unions de polyèdres et pour récupérer la liste des contraintes ou la liste des représentations duales de telles unions. Les unions de polyèdres sont représentées par une liste de polyèdres. La calculatrice SPPoC présente toujours des unions préalablement simplifiées par la PolyLib : les polyèdres des listes sont disjoints.

4.2   Opérations ensemblistes

SPPoC reprend les opérations sur les polyèdres offertes par la PolyLib : intersection, union, soustraction, image ou pré-image par une fonction affine, calcul de l'enveloppe convexe, simplification dans le contexte d'une autre union de polyèdres, test d'inclusion et enfin test de vacuité.

Il est important de comprendre que SPPoC construit les polyèdres en ne tenant compte que des variables qui interviennent dans les contraintes ou la représentation duale. Supposons qu'un utilisateur souhaite calculer l'intersection entre une droite horizontale dans le plan et un polyèdre plus complexe de dimension deux. La définition de la droite peut se faire comme suit :
# let d = Polyhedron.of_system <:s< i=4 >>;;
val d : SPPoC.Polyhedron.t = {({i = 4}, {vertex(4*i)})}
Ceci définit un polyèdre de dimension 1 (il est possible de s'en convaincre en demandant la liste des variables ; elle se limite à i). Le convexe de dimension deux peut, lui, se définir par :
# let p = Polyhedron.of_system <:s<2*i+j>1, i-4*j<10, j<=20>>;;
val p : SPPoC.Polyhedron.t =
  {({j <= 20, i-4*j <= 10, 2*i+j >= 1},
    {vertex((14*i-19*j)/9), vertex((-19*i+40*j)/2), vertex(90*i+20*j)})}
Lors du calcul d'intersection de la droite d et du polyèdre p, SPPoC se rend compte que les deux polyèdres n'ont pas la même dimension et cherche à les plonger dans un espace commun. En l'occurrence un espace à deux dimensions (associées aux variables i et j). Le résultat de l'intersection est un segment de droite :
# Polyhedron.inter d p;;
- : SPPoC.Polyhedron.t =
{({i = 4, j <= 20, 2*j >= -3}, {vertex((8*i-3*j)/2), vertex(4*i+20*j)})}
Dans tous les cas, il est possible de trouver un espace commun : SPPoC récupère les ensembles de variables des unions de polyèdres, en calcule l'union V et plonge les deux unions de polyèdres dans un espace ayant autant de dimensions que V a d'éléments. L'identification des dimensions communes se fait par égalité des noms de variables les caractérisant. Il n'y a pas de renommage automatique.

Pour les images et pré-images de polyèdres par une fonction affine, un mécanisme semblable est mis en place. Pour comprendre l'utilité de l'auto-ajustement en présence de fonctions, prenons l'exemple de la projection du polyèdre ci-dessous selon la direction associée à i :
# let q = Polyhedron.of_rays
          <:r< vertex(i+5j), vertex(2i-j),
               vertex(3j), vertex(2i) >>;;
val q : SPPoC.Polyhedron.t =
  {({i <= 2, 5*i+j <= 10, 2*i-j >= -3, 2*i+j >= 3},
    {vertex(i+5*j), vertex(2*i-j), vertex(3*j), vertex(2*i)})}
La fonction à utiliser est <:fct< i <- i >>. 3 Notre fonction de projection se retrouve donc avec un espace de départ et d'arrivée à une dimension. SPPoC va ajuster automatiquement la dimension de cet espace lors du calcul de l'image :
# Polyhedron.image q <:fct< i <- i >;;
- : SPPoC.Polyhedron.t = {({i >= 0, i <= 2}, {vertex(0), vertex(2*i)})}
Si, au contraire, on souhaite plonger q dans un espace avec un plus grand nombre de dimensions (par exemple, en utilisant <:fct< i,j,k <- i,j,k >>), SPPoC va automatiquement ajuster la dimension de q avant d'appliquer la fonction. L'utilisateur n'a donc pas, en principe, à se préoccuper des dimensions de ses fonctions et de ses polyèdres. Cependant SPPoC permet de forcer le plongement d'une fonction ou d'une union de polyèdres dans un espace de dimension supérieure.

Par contre, l'utilisateur doit absolument utiliser les mêmes variables pour référencer les dimensions des espaces dans lesquels sont définis ses polyèdres. Si pour une raison ou une autre, ce n'est pas possible lors de la définition des polyèdres, SPPoC permet, par la suite, de renommer les variables liées aux dimensions.

4.3   Opérations sur les polyèdres paramétriques

Même sans opération spécifique, la PolyLib permet de manipuler des polyèdres paramétriques en ajoutant des dimensions supplémentaires pour les paramètres. Par exemple il est possible de définir un carré paramétré par la taille n de ses cotés :

# let carre = Polyhedron.of_system <:s< 0<=i<=n, 0<=j<=n >>;;
val carre : SPPoC.Polyhedron.t =
  {({j <= n, j >= 0, i <= n, i >= 0},
    {vertex(0), ray(n), ray(i+n), ray(i+j+n), ray(j+n)})}
Il est possible de faire des opérations ensemblistes sur ces polyèdres paramétriques. Par contre, la représentation duale de ces objets est déconcertante ; elle ne donne plus les sommets des polyèdres bornés. En effet, pour << paramétrer >> le polyèdre, des dimensions représentant les paramètres sont ajoutées. La représentation duale tient compte de ces dimensions et, comme les paramètres sont généralement non bornés, on se retrouve avec des lignes ou des rayons suivant les dimensions paramétriques.

Il existe une version étendue de la PolyLib qui est celle à laquelle SPPoC propose une interface et qui permet de trouver les sommets d'un polyèdre paramétrique et de calculer le nombre de points entiers contenus dans un tel polyèdre. Voici un appel à l'extension de la PolyLib qui donne les sommets d'un polyèdre paramétrique sous sa forme habituelle, c'est à dire eux-mêmes paramétrés :

# Polyhedron.vertices
    carre ( Polyhedron.of_system <:s< n>=0 >> );;
- : (SPPoC.Polyhedron.t * SPPoC.Expr.t list list) list =
[{({n >= 0}, {ray(n), vertex(0)})}, [[n; n]; [n; 0]; [0; n]; [0; 0]]]
On reconnait bien ici les sommets du carré : (0, 0), (0, n), (n, 0) et (nn), ainsi que la définition du domaine du paramètre n : n³0.

Pour compter le nombre de points entiers contenus dans un polyèdre paramétrique borné4 l'extension de la PolyLib utilise les polynômes de Ehrhart. Les polynômes de Ehrhart sont des polynômes à coefficients périodiques. En fait les coefficients sont des couples formés d'une liste de nombres et d'un paramètre. La valeur du coefficient est le nombre de la liste de rang le paramètre modulo la taille de la liste. Intuitivement, ces coefficients périodiques sont utiles pour construire des expressions dépendant de paramètres en évitant une définition au cas par cas suivant les valeurs de ces paramètres. Prenons l'exemple du polynôme nm+[1/2,2/3]n m + [0,1/3]m n + 1, sa valeur pour n=2 et m=3 est 2× 3+1/2× 3 + 1/3× 2 +1. Deux exemples de calcul du nombre de points entiers sont donnés ci-après. Les résultats sont, bien entendu, donnés en fonction des paramètres.

# Polyhedron.enum
    carre 
    (Polyhedron.of_system <:s< n>=0 >>);;
- : SPPoC.Polyhedron.expr_quast =
[{({n >= 0}, {ray(n), vertex(0)})}, 1+2*n+n**2]
# let rectangle = 
    Polyhedron.of_system <:s< 0<=i<=n, 0<=j<=n/3 >>;;
val p : SPPoC.Polyhedron.t =
  {(3*j <= n, j >= 0, i <= n, i >= 0,
    vertex(0), ray(n), ray(i+n), ray(3*i+j+3*n), ray(j+3*n))}
# Polyhedron.enum
     rectangle 
    (Polyhedron.of_system <:s< n>=0 >>);;
- : SPPoC.Polyhedron.expr_quast =
[{({n >= 0}, {ray(3*n), vertex(0)})},
 1/3*n**2+n*[4/3, 1, 2/3]n+[1, 2/3, 1/3]n]
L'opération de comptage de points entiers est coûteuse en mémoire et en temps. En appelant directement la fonction de comptage de la PolyLib , seuls des polyèdres de petite dimension peuvent être traités. Pour parvenir à traiter des polyèdres plus complexes, SPPoC offre une fonction de plus haut niveau Polyhedron.enumeration. Cette fonction utilise le fait que la plupart des polyèdres manipulés peuvent s'écrire sous la forme d'un produit cartésien. Un résultat bien connu est que le cardinal d'un produit cartésien est le produit des cardinaux des ensembles utilisés dans le produit. Le fait de lancer le calcul du nombre de points sur plusieurs polyèdres de faible dimension plutôt que sur le polyèdre original permet de limiter l'explosion combinatoire. L'exemple le plus frappant est celui d'un hyper-cube de dimension d ; un tel polyèdre est le produit cartésien de d polyèdres de dimension 1.

L'algorithme de décomposition d'un polyèdre en produit cartésien consiste à partitionner l'ensemble des contraintes définissant le polyèdre. On regroupe deux contraintes dans le même sous-ensemble si et seulement si elles ont au moins une variable en commun. À la partition de l'ensemble des contraintes correspond donc une partition de l'ensemble des variables (toutes les variables apparaissent dans les contraintes puisque le polyèdre doit être borné). Le polyèdre original est le produit cartésien des polyèdres définis par les sous-ensembles de contraintes sur les sous-ensembles de variables correspondants.


Previous Contents Next