• Aucun résultat trouvé

Chapitre I : Présentation de la problématique

5. Méthodes de résolution

Les problèmes d’optimisation combinatoire consistent à trouver, dans un ensemble discret et fini de solutions, une solution optimale (il peut exister plusieurs solutions optimales, généralement une seule est recherchée). Trouver une solution dans un ensemble fini et discret peut sembler facile avec une stratégie d’énumération de toutes les solutions et de les comparer entre elles pour obtenir la meilleure. Mais le nombre de solutions d’un problème combinatoire est souvent exponentiel, rendant impossible une telle stratégie. La méthode de résolution choisie doit donc tenir compte de la complexité du problème.

Cette section présente la complexité et les différentes classes des problèmes combinatoires, puis les principales méthodes de résolution de ces problèmes : d’abord les méthodes exactes et ensuite les méthodes approchées.

5.1. Complexité et classe de problèmes

Les problèmes d’optimisation combinatoire sont classifiés selon leur complexité. La complexité d’un problème est définie par rapport aux algorithmes utilisés pour le résoudre, c’est l’algorithme de plus faible complexité qui détermine la classe du problème (Turing, 1937; Cook, 1971; Garey and Johnson, 1979; Toussaint, 2010).

La définition de la complexité d’un algorithme est liée aux machines de Turing (Turing, 1937), il en existe deux différentes : les déterministes et les non-déterministes. Les machines déterministes exécutent les étapes de l’algorithme les unes après les autres, chaque nouvelle étape est déterminée par la précédente. Pour les machines de Turing non-déterministes, à chaque nouvelle étape de l’algorithme, elles disposent d’un panel d’étapes possibles à exécuter (et non plus une seule étape), sans déterminer explicitement quelle étape doit être exécutée.

Un algorithme est dit polynomial (ou polynomial en temps), si le nombre d’opérations qu’il effectue suit une fonction polynomiale. Sa complexité est notée, d’après les notations de Landau, ( ), avec ∈ ℕ, et la taille des données d’entrée. La complexité est dite constante lorsque = 0 et linéaire lorsque = 1. Il existe d’autres ordres de grandeurs pour la complexité des algorithmes, notamment (log ( )) qui est une complexité logarithmique, et ( ), avec ∈ ℕ, > 1, qui est une complexité exponentielle.

Parmi les différentes classes de complexité, uniquement les classes P, NP et

NP-complet sont présentées dans ce manuscrit, car elles ont un rôle important pour les problèmes

d’ordonnancement et de tournées de véhicules.

Définition : classe P

La classe P (Polynomiale) contient l’ensemble des problèmes qui peuvent être résolus avec un algorithme polynomial en temps par une machine de Turing déterministe (Garey and Johnson, 1979).

Les problèmes de la classe P sont généralement considérés comme « faciles ».

Définition : classe NP

Un problème appartient à la classe NP (Non-deterministic Polynomial) si une solution de celui-ci peut être vérifiée en un temps polynomial (Garey and Johnson, 1979).

La classe NP contient la majorité des problèmes d’optimisation combinatoire. Tout problème issu de la classe P est également contenu dans la classe NP, car si un problème peut être résolu avec un algorithme polynomial, alors sa solution peut également être vérifiée en temps polynomial. Savoir si = reste à ce jour un problème ouvert qui interpelle de nombreux chercheurs et qui est un des 7 problèmes du prix du millénaire.

La classe des problèmes NP-complet découle de la classe des problèmes NP et repose sur la définition de réduction polynomiale. Un problème ℘ peut être réduit polynomialement à un problème ℘′ si les données du problème ℘ peuvent être transformées en données du problème ℘′ par un algorithme polynomial. Grâce à la réduction polynomiale, si un algorithme polynomial peut résoudre le problème ℘, alors le problème ℘′ peut être résolu avec le même algorithme.

Définition : un problème ℘ appartient à la classe NP-complet (Garey and Johnson, 1979) si :

(i) ℘ ∈ : le problème appartient à la classe NP ;

(ii) Tout autre problème de NP peut être réduit polynomialement à ℘.

Le premier problème prouvé NP-complet est le problème SAT (problème de satisfaisabilité booléenne) par (Cook, 1971). S’il existe un algorithme polynomial pour résoudre un problème NP-complet, alors tous les problèmes de la classe NP pourraient être résolus en un temps polynomial.

Initialement, les classes P, NP et NP-complet concernent les problèmes de décision, mais par abus de langage, les problèmes d’optimisations sont inclus dans ces classes, car il est considéré qu’un problème d’optimisation peut toujours être transformé en problème de décision.

Les problèmes issus des classes NP et NP-complet focalisent l’attention des chercheurs, car ceux-ci ne peuvent généralement pas être résolus de manière exacte pour des problèmes de tailles moyennes et importantes dans des temps de calcul raisonnables. Les prochaines sections présentent des méthodes de résolution exactes pour des problèmes d’optimisation, qui ont pour but de prouver l’optimalité de la solution, mais qui sont souvent très longues ; et des méthodes de résolution approchées qui ont pour objectif de trouver rapidement une solution de qualité satisfaisante, mais pas forcément optimale.

5.2. Résolution par méthodes exactes

5.2.1. Programmation linéaire

La programmation linéaire (Dantzig, 2016) consiste à résoudre un modèle linéaire (aussi appelé « programme linéaire ») de manière exacte, communément, avec la méthode du simplex (Dantzig, 1987) ou la méthode des points intérieurs (Karmarkar, 1984). Les programmes linéaires modélisent les contraintes du problème sous forme d’équations et d’inéquations toutes linéaires (il n’y a pas de terme quadratique, ou d’opérateur logique), il est également impossible de multiplier/diviser des variables entre elles. La fonction objectif du problème doit elle aussi être linéaire. Il existe trois grandes familles de programme linéaire : les programmes linéaires « simples », les programmes linéaires en nombres entiers et les programmes linéaires mixtes.

Un programme linéaire « simple » ne contient que des variables continues, c’est-à-dire, les variables peuvent prendre n’importe quelle valeur réelle dans un intervalle donné. La méthode du simplexe, bien que non-polynomiale dans le pire cas, est très performante pour ce type de modèle, car elle est souvent polynomiale en moyenne.

Un Programme Linéaire en Nombres Entiers (PLNE) ne fait intervenir que des variables entières. Ces problèmes sont généralement plus difficiles à résoudre que les programmes linéaires simples, en raison de la difficulté à respecter la contrainte d’intégrité des variables. Les PLNE sont fréquemment résolus en relaxant la contrainte d’intégrité des variables puis en combinant des méthodes du simplex ou des points intérieurs avec des méthodes de Branch-and-Bound ou Branch-and-Cut pour obtenir une solution en nombres entiers.

Un programme linéaire mixte (Mixed Integer Linear Programming – MILP) comprend à la fois des variables continues et des variables entières. La méthode de résolution d’un MILP est semblable à la résolution d’un PLNE.

La programmation linéaire est utilisée pour de nombreux problèmes de tournées de véhicules (Fügenschuh, 2009; Rasmussen et al., 2012; Braekers et al., 2014) ; des problèmes d’ordonnancement (Stein, 1978; Mingozzi et al., 1998; Bruzzone et al., 2012; Artigues, 2017) ; et des problèmes intégrés d’ordonnancement et de transport (Castillo-Salazar et al., 2016; Garaix et al., 2018; Gondran et al., 2018).

La programmation linéaire est régulièrement mise en difficulté lorsque le nombre de variables est très important, notamment pour des instances de grandes tailles. Dans ce cas le temps de résolution devient très important. Dans certains cas, la quantité de mémoire consommée par les solveurs atteint plusieurs gigaoctets et dépasse la capacité des ordinateurs.

Afin de pallier ces deux problèmes, il existe des méthodes de décomposition, les plus courantes sont la méthode de Dantzig-Wolfe (Dantzig and Wolfe, 1960) et la génération de colonnes.

La programmation linéaire (et les méthodes de décomposition) est un outil très utilisé dans les communautés des problèmes d’ordonnancement et de tournées de véhicules, mais est souvent limitée, face à des problèmes qui comportent un grand nombre de contraintes logiques du type « Si… Alors… Sinon ». La modélisation de ces contraintes fait intervenir une variable binaire, une constante M (ou H), et deux contraintes (un pour le Si, l’autre pour le Sinon). Ce type de contrainte ralentit énormément les solveurs en programmation linéaire. Il existe un autre grand courant de modélisation et de résolution exacte qui est la programmation par contraintes.

5.2.2. Génération de colonnes

Face à des programmes linéaires de grande taille, la génération de colonnes s’appuie sur trois constats : le modèle linéaire comporte un trop grand nombre de variables pour qu’elles puissent toutes être explicitées ; la grande majorité des variables est nulle dans la solution optimale ; et la méthode du simplexe n’a pas besoin d’accéder simultanément à toutes les variables du problème. La génération de colonnes est particulièrement adaptée à la résolution de problème possédant un très grand nombre de variables, car l’ajout itératif de variables peut permettre d’espérer obtenir une solution optimale en n’ayant considéré qu’un sous-ensemble de variables. (Barnhart et al., 1998; Desaulniers et al., 2005) proposent deux états de l’art concernant la génération de colonnes.

L’objectif de la génération de colonnes est de résoudre un problème restreint en nombre de colonnes, c’est-à-dire, de taille réduite avec un ensemble limité de variables, puis de générer les variables (ou colonnes) utiles, de manière itérative, jusqu’à l’obtention d’une solution optimale. La génération d’une nouvelle colonne se fait lors de la résolution d’un sous-problème (ou problème esclave) et consiste à chercher la meilleure variable à ajouter dans le problème restreint, c’est-à-dire la variable de coût réduit minimal. Le coût réduit d’une variable est calculé à partir des variables duales obtenues après la résolution du sous-problème restreint. Le sous-problème initial à résoudre est appelé sous-problème maître (ou sous-problème principal).

Sans perte de généralité, soit le problème initial (ou maître) (℘) suivant :

(℘) min s. c. ≥ ∀ = 1, … , ≥ 0 ∀ ∈ Avec :

 , l’ensemble des indices des variables ;

 ∈ ℝ| |, les coefficients de la fonction objectif ;  ∈ ℝ| |, le vecteur des variables ;

 ∈ ℝ , les coefficients du second membre ;

 (∀ = 1, … , ), la variable duale associée à la contrainte .

Le problème restreint ( ), de la génération de colonnes, à l’itération est le suivant, avec ⊆ l’ensemble restreint des variables utilisées à l’itération :

( ) min s. t. ≥ ∀ = 1, … , ≥ 0 ∀ ∈

La résolution du ( ) permet d’obtenir les valeurs optimales des variables duales associées aux contraintes. Toute colonne ∈ \ est susceptible de diminuer la valeur de la fonction objectif si celle-ci a un coût réduit négatif (cas d’un problème de minimisation). Le coût réduit d’une variable est donné par :

= −

S’il n’y a aucun coût réduit de valeur négative, alors il n’existe pas de variable améliorante et la solution du problème restreint est la solution optimale du problème maître. Si une variable a un coût réduit négatif, alors celle-ci est intégrée au problème ( ) ( = ∪ ). Il existe plusieurs stratégies pour sélectionner la variable à ajouter dans le ( ), la stratégie de base est de choisir la variable avec le coût réduit négatif minimal, il existe d’autres stratégies consistant à sélectionner plusieurs variables (de coût réduit négatif) à chaque itération.

La génération de colonnes fonctionne lorsque le problème restreint ( ) est réalisable, car celui-ci possède donc une solution duale qui permet la recherche de variables améliorantes. Dans certains cas, le ( ) peut ne pas être réalisable, notamment dans les cas où : trouver une solution réalisable (sans considération pour sa qualité) de (℘) est très difficile, il est alors compliqué de trouver un ensemble de variables permettant d’initialiser le ( ) ; la génération de colonnes est associée à un Branch-and-Price dans le cadre d’un problème (℘) impliquant des nombres entiers, le branchement peut rendre le ( ) irréalisable.

La génération de colonnes est régulièrement utilisée pour les problèmes de tournées de véhicules et d’ordonnancement. Il est intéressant de noter qu’il existe de nombreuses méthodes pour accélérer la résolution de la génération de colonnes, (Desaulniers et al., 2001), (Desaulniers et al., 2005), (Feillet, 2010) listent notamment les méthodes suivantes :

 effectuer la résolution du sous-problème avec la programmation dynamique ;  ne pas résoudre les sous-problèmes à l’optimalité lors des premières itérations ;

 ajouter plusieurs colonnes à partir de la résolution d’un sous-problème (et pas uniquement une seule colonne).

5.2.3. Programmation par contraintes

La Programmation Par Contraintes (PPC) est une méthode de résolution exacte des problèmes combinatoires qui consiste à modéliser les problèmes par un ensemble de contraintes, qui ne sont pas forcément linéaires. Les contraintes non linéaires peuvent être, entre autres, des relations logiques de type « Si… alors » ou de type avec et deux variables. La programmation par contraintes combine des techniques issues de la recherche opérationnelle dont la théorie des graphes et la programmation linéaire ; avec des modèles de représentation et de traitement des contraintes issues de l’intelligence artificielle, notamment la satisfaction des contraintes, la propagation des contraintes et des langages déclaratifs de modélisation. Les problèmes de planification et d’ordonnancement des systèmes de productions, ainsi que les problèmes de tournées de véhicules font partie des applications courantes de la PPC (Wallace, 1996; Cambazard and Bourreau, 2004; Rossi et al., 2006; Baptiste et al., 2012; Hojabri et al., 2018; Bourreau et al., 2019, 2020).

Un point clé de la programmation par contraintes est la séparation de la modélisation du problème et de sa résolution. Le problème est modélisé sous forme de « problème de satisfaction de contraintes » (Constraint Satisfaction Problem – CSP). La résolution du CSP s’appuie sur trois principes : le filtrage, la propagation et la recherche de solution par exploration. Le filtrage et la propagation permettent de réduire de l’espace de recherche, et l’exploration complète de cet espace prouve l’existence – ou non – d’une solution réalisable, ou prouve l’optimalité de la solution dans le cas d’un problème d’optimisation.

De manière plus formelle, en PPC, un modèle est défini à partir d’un triplet ( , , )

(Régin, 2004), où = { , , … , } est l’ensemble des variables du problème ;

= { , , … , } est l’ensemble des domaines associés à chaque variable ∈ ( est associé à la variable ) ; et = { , , … , } est l’ensemble des contraintes. Une contrainte traduit une caractéristique du problème qui doit être satisfaite par un sous-ensemble de variables. Un solveur de programmation par contraintes calcule une solution en instanciant chacune des variables du modèle à une valeur satisfaisant simultanément toutes les contraintes.

La propagation de contraintes utilise un algorithme de filtrage pour chaque contrainte : celui-ci, en fonction de variables prises en compte par la contrainte et de leurs domaines respectifs, supprime les valeurs impossibles c’est-à-dire qui ne peuvent appartenir à aucune solution. Par exemple, soient deux variables avec leurs domaines respectifs ∈ [1 ; 2 ; 3] et ∈ [1 ; 2 ; 3] avec la contrainte ∶ + = 5, alors les affectations = 1 ou = 1 sont impossibles (aucune solution n’est réalisable avec = 1 ou = 1), les domaines peuvent être réduit à ∈ [2 ; 3] et ∈ [2 ; 3].

La modification du domaine d’une variable , par un algorithme de filtrage, peut avoir des répercussions sur les domaines des autres variables, il est donc utile de réétudier l’ensemble des variables ayant au moins une contrainte en commun avec la variable . Ce mécanisme est appelé propagation. L’ensemble de l’espace de recherche est parcouru en affectant successivement toutes les valeurs possibles à toutes les variables. Cette exploration est efficace, car les algorithmes de filtrage et de propagation sont sollicités après chaque affectation.

L’algorithme de parcours de l’espace des solutions le plus courant est l’algorithme parcours en profondeur d’un arbre et est basé sur des techniques de backtracking comme le

rappelle (Malapert, 2010). Il débute avec l’ensemble des variables non affectées, et itérativement, une variable est sélectionnée et une valeur lui est affectée. L’algorithme décrit alors un arbre de recherche où chaque branche correspond à l’affectation d’une valeur à une variable, et chaque nœud à une affectation partielle (par abus de langage, une solution partielle). L’algorithme vérifie à chaque nœud que l’affectation partielle est valide et respecte toutes les contraintes. Si c'est le cas (le nœud représente une solution), alors l’algorithme filtre le domaine de la variable puis il propage les contraintes. Ensuite, l’algorithme sélectionne une nouvelle variable et une nouvelle valeur pour obtenir un nouveau nœud. Sinon, l’algorithme « backtrack » en remontant dans l’arbre, c’est-à-dire que la dernière affectation réalisée est remise en cause, et l’algorithme choisit une nouvelle valeur à la variable.

L’algorithme de parcours de l’arbre de recherche peut arriver sur deux types de feuilles : soit toutes les variables sont instanciées et toutes les contraintes sont respectées, alors la feuille de l’arbre est sur une solution ; soit une (ou plusieurs) variable(s) a (ont) un domaine vide (après filtrage et propagation), et aucune solution n’est possible avec les valeurs déjà affectées aux autres variables.

La Figure 5 propose un parcours en profondeur du problème composé de trois variables avec leurs domaines respectifs : = [1 ; 2], = [1 ; 2 ; 3] et = [1 ; 2 ; 3], et d’une

unique contrainte ∶ ≠ ≠ .

À chaque nœud de l’arbre, la solution partielle est constituée des variables précédées d’un astérisque. Dans le nœud 1, les domaines de chaque variable sont les données du problème. Les arcs sortants du nœud 1 sont des branchements sur la variable (sans perte de généralité, l’ordre des branchements est arbitraire), dans le premier cas = 1, les domaines des autres variables sont inférés pour donner le nœud 2 avec une nouvelle solution partielle. Par exemple, le domaine de la variable est réduit : la valeur 1 est supprimée de ce domaine. Ensuite l’algorithme de parcours en profondeur branche à partir du nœud 2 sur le sommet ( = 2) et arrive à une feuille de l’arbre car toutes les variables sont instanciées (grâce à la propagation des contraintes sur le domaine de la variable ) et respectent la contrainte . L’algorithme de parcours backtrack sur le nœud 2 où il fait un nouveau branchement ( = 3) pour obtenir sur le nœud 4 qui est une feuille. Ensuite, il backtrack de nouveau sur le nœud 2. Puisque toutes les possibilités du nœud 2 ont été explorées, l’algorithme backtrack au nœud 1 et effectue un nouveau branchement pour la variable avec = 2.

1

2

3 4

5

A=[1 ; 2] B=[1 ; 2 ; 3] C=[1 ; 2 ; 3] *A=[1] B=[2 ; 3] C=[2 ; 3] *A=[1] *B=[2] C=[3] *A=[1] *B=[3] C=[2] *A=[2] B=[1 ; 3] C=[1 ; 3] A=1 B=2 B=3 A=2

Figure 5 Parcours en profondeur d’un arbre de recherche

Les algorithmes de filtrage étant un des fondements de la PPC, il est important que ceux-ci soient efficaces et puissants. La communauté scientifique de la programmation par contraintes s’est penchée sur ces algorithmes de filtrage et la littérature compte maintenant un grand ensemble de contraintes permettant un filtrage ayant les deux caractéristiques précédemment citées. Ces contraintes sont nommées contraintes globales et utilisent généralement de nombreuses approches issues de la RO. Parmi les nombreuses contraintes globales, les plus courantes sont : la contrainte « AllDifferent » qui impose à un ensemble de variables d’avoir toute une valeur différente (Laurière, 1978; Régin, 1994) ; la contrainte « Cumulative » qui permet de résoudre des problèmes d’ordonnancement avec affectation de ressources (Aggoun and Beldiceanu, 1993) ; la contrainte « Cycle » (ou « Circuit ») qui permet de résoudre des problèmes de type voyageur de commerce (Beldiceanu and

Contejean, 1994). Le site web ("Global Constraint Catalog," 2014)

(http://sofdem.github.io/gccat/gccat/sec5.html) liste plus de 420 contraintes globales.

5.2.4. Programmation dynamique

La programmation dynamique fait partie des méthodes de résolution exactes. Une des applications des plus connues est l’algorithme des plus courts chemins de (Bellman, 1954). La programmation dynamique repose sur le principe d’optimalité énoncé par Bellman : « toute politique optimale est composée de sous-politiques optimales ». La programmation dynamique est basée sur quatre idées : résoudre des sous-problèmes, stocker leurs solutions, conserver les solutions pertinentes et les utiliser pour construire la solution du problème initial.

Le sous-problème de départ est le plus petit possible et sa solution permet de résoudre un sous-problème de taille (ou niveau) supérieure. Les solutions de sous-problèmes peuvent être combinées pour résoudre le problème de taille supérieure. La solution d’un sous-problème peut être utilisée par plusieurs sous sous-problèmes de niveaux supérieurs.

La Figure 6 illustre la résolution d’un problème, représenté par un rond orange, par programmation dynamique. Les sous-problèmes sont en vert, et les flèches indiquent les liens entre les résolutions des sous-problèmes.

Problème initial

Sous-problèmes

Figure 6 Résolution d’un problème par programmation dynamique

Un problème d’optimisation peut être résolu par programmation dynamique si la solution du problème est composée de solutions partielles des sous-problèmes. Un des algorithmes de programmation dynamique parmi les plus connus est l’algorithme de Bellman-Ford (Ford and Lester, 1956; Bellman, 1958; Moore, 1959) pour le calcul d’un chemin de longueur optimale dans un graphe. Cet algorithme est notamment étendu par (Feillet et al., 2004) pour la résolution du Problème du Plus Court Chemin Élémentaire avec Contraintes de Ressources (Elementary Shortest Path Problem with Resource Constraints – ESPPRC).

5.3. Résolution par méthodes approchées : les métaheuristiques

Les heuristiques et les métaheuristiques sont des méthodes approchées qui ont pour objectif d’obtenir des solutions valides, de préférence de bonne qualité, en un temps raisonnable. Les métaheuristiques sont des schémas génériques de résolution indépendants du problème traité ; à la différence des heuristiques qui sont généralement dédiées à la résolution