<< Page principale
L'Optimisation Combinatoire est une branche de l'optimisation en mathématiques appliquées et en informatique, également liée à la recherche opérationnelle, l'algorithmique et la théorie de la complexité. On parle également d'optimisation discrète car on travaille sur des domaines discrets (non continus).
On cherche la meilleure ou les meilleures solutions d'un problème par rapport à une fonction entière ou réelle appelée fonction de coût, de score ou fonction objectif. On cherche alors à maximiser ou minimiser cette fonction.
Certains problèmes d'optimisation combinatoire peuvent être résolus de manière exacte en temps polynomial par exemple par un algorithme glouton, un algorithme de programmation dynamique ou en montrant que le problème peut être formulé comme un programme linéaire en variables réelles.
Cependant, la majorité de ces problèmes sont NP-complets et pour les résoudre il est nécessaire de faire appel à d'autres techniques, dites heuristiques ou métaheuristiques, afin d'obtenir une solution approchée de manière à garantir l'obtention d'une solution en un temps raisonnable.
On s'intéresse à des problèmes d'optimisation pour lesquels on cherche une solution qui soit la meilleure. Nous allons notamment nous focaliser sur un modélisation sous forme de CSP (Constraint Satisfaction Problems).
On définit un Problème de Satisfaction de Contraintes par :
On peut augmenter ce cadre théorique avec une fonction objectif (appelée également fonction de coût ou de score) qui permet d'attribuer une valeur de qualité à une instanciation des variables : $$f : D_1 × ... × D_n → ℝ$$.
On peut alors tenter de rechercher la ou les meilleurs solutions par rapport à la fonction objectif. Il s'agit alors d'un problème d'optimisation sous contraintes.
Résoudre un CSP consiste alors :
Note : on remarquera que pour certains problèmes il peut y avoir moins de domaines que de variables (cf problème ci-après), les variables prenant généralement leurs valeurs dans un seul domaine.
Un cryptarithme ou jeu cryptarithmétique est un casse-tête numérique et logique qui consiste en une équation mathématique où les lettres doivent être remplacées par des chiffres, deux lettres ne pouvant pas avoir la même valeur. L'un des plus connus, car probablement le plus ancien, est le problème $SEND + MORE = MONEY$.
Pour résoudre ce genre de problème il est possible de le formuler comme un CSP :
il n'est pas nécessaire de définir une fonction objectif, mais elle peut être définie comme étant le nombre de contraintes non satisfaites, on doit donc la minimiser de manière à atteindre 0.
Les contraintes $c_1$ à $c_5$ peuvent être résumées par une seule contrainte :
$$ \table 10000 × M + 1000 × O + 100 × N + 10 × E + Y = ; 1000 × S + 100 × E + 10 × N + D + ; 1000 × M + 100 × O + 10 × R + E ; $$Dans ce cas, le problème est plus difficile à résoudre car le fait d'avoir plusieurs contraintes permet dès qu'une contrainte est violée de remettre en question les affectations d'un petit nombre de variables.
La résolution utilise une méthode exacte pour ce genre de problème :
Ce problème peut être résolu efficacement avec un solveur comme Minizinc :
include "globals.mzn";
var 0..9: S;
var 0..9: E;
var 0..9: N;
var 0..9: D;
var 1..9: M;
var 0..9: O;
var 0..9: R;
var 0..9: Y;
var 0..1 : R1;
var 0..1 : R2;
var 0..1 : R3;
var 0..1 : R4;
array[1..8] of var int : letters = [S,E,N,D,M,O,R,Y];
constraint D+E=Y+10*R1;
constraint R1+N+R=E+10*R2;
constraint R2+E+O=N+10*R3;
constraint R3+S+M=O+10*R4;
constraint R4=M;
constraint all_different(letters);
solve satisfy;
output[" \(R4) \(R3) \(R2) \(R1)\n"];
output[" \(S) \(E) \(N) \(D)\n"];
output[" \(M) \(O) \(R) \(E)\n"];
output[" \(M) \(O) \(N) \(E) \(Y)\n"];
output[" SENDMORY = \(all)\n"];
On trouvera de nombreux autres exemples de cryptarithmes sur cette page comme :
Exercice 3.1
Installer Minizinc et résoudre le problème $HUIT + HUIT = SEIZE$.
Le problème des N-reines consiste à placer sur un échiquier de $N × N$ cases, $N$ reines de manière à ce qu'aucune reine ne soit en prise avec une autre.
Ce problème a été posé en 1848 par un joueur d'échecs allemand, Max Bezzel, pour $N = 8$.
Solution pour N=7 |
Diagonales droites (orange) et gauches (vert) |
Exercice 3.2
Exprimez ce problème sous forme de CSP en déterminant les variables, les domaines et en exprimant les contraintes sur les colonnes et les diagonales. Comment définir la fonction objectif ?
Voici l'expression du problème en Minizinc.
Voici quelques définitions préalables :
Une configuration est une affectation des variables du problème.
Une configuration partielle est une affectation d'une partie des variables du problème.
Une solution est une configuration qui respecte les contraintes du problème.
Toute configuration n'est pas forcément une solution, mais toute solution est une configuration.
L'espace de recherche du problème est l'ensemble des configurations distinctes du problème.
Une solution optimale $X^{⋆}$, également appelée optimum global, est une solution qui vérifie :
Une solution dite optimum local est une solution meilleure que celles qui l'entourent mais moins bonne que l'optimum global.
Plusieurs problèmes emblématiques peuvent être modélisés sous forme de CSP :
Pour résoudre un CSP, on dispose de deux approches :
L'intérêt des méthodes exactes est qu'elles vont passer en revue l'ensemble des configurations réalisables du problème et si le problème admet une ou plusieurs solutions optimales, elles seront trouvées.
Plus le problème est difficile, plus on va prendre de temps pour le résoudre. Certains problèmes sont trop volumineux (nombre de variables, taille des domaines, nombre de contraintes) et demandent trop de calculs pour pouvoir être résolus en un temps raisonnable.
La méthode la plus simple pour résoudre un problème consiste à réaliser l'énumération de ses configurations, cependant il faut obtenir une configuration qui soit une solution et déterminer si elle est meilleure que celles qu'on a pu trouver précédemment.
La technique arborescente est intéressante car elle permet d'élaguer des branches de l'arbre de recherche et permet ainsi de ne pas passer en revue toutes les configurations existantes.
Certaines techniques dites heuristiques (du grec heuriskô qui signifie "je trouve") ont pour but de mener plus rapidement vers une solution, comme par exemple :
L'ordre dans lequel on affecte les variables (cf. problème SAT) impacte le temps de résolution de manière importante. Avoir un ordre judicieux permet souvent de gagner beaucoup de temps en évitant les calculs inutiles (cf. problème SAT).
On peut par exemple choisir les variables les plus contraintes (qui participent au plus grand nombre de contraintes, ou appartenant à des contraintes alldiff).
De la même manière, l'ordre dans lequel on choisit les valeurs à affecter aux variables influe sur le temps de résolution.
On peut alors mettre en oeuvre des techniques de consistance (on parle généralement d'arc-consistance) qui dès qu'on affecte une variable vont chercher les contraintes en relation avec la variable et supprimer des valeurs des domaines des autres variables ou restreindre ces domaines :
L'application de ces règles qui assurent la consistance du problème permettent d'éviter de faire des calculs dont on sait qu'ils ne méneront pas à une solution car les contraintes ne seront pas satisfaites.
Reprenons le problème des N-Reines et voyons comment le résoudre par des méthodes exactes :
Solution pour N=7
Voici le nombre solutions en fonction de $N$ :
N | Solutions |
2 | 0 |
3 | 0 |
4 | 2 |
5 | 10 |
6 | 4 |
7 | 40 |
8 | 92 |
9 | 352 |
10 | 724 |
11 | 2680 |
12 | 14200 |
Une configuration du problème peut être représentée sous forme d'un tableau ou d'une liste qui contient la colonne sur laquelle est placée une reine. L'indice du tableau ou de la liste représentant la ligne sur laquelle elle se trouve.
Pour la solution précédente (N=7), on aurait donc $(2,5,1,4,7,3,6)$, l'indice de la première ligne ou colonne commençant à 1 dans ce cas.
On peut résoudre ce problème en réalisant une énumération exhaustive des configurations, il suffit de générer toutes les permutations possibles des valeurs $(1,2, ..., N)$.
Cependant, le nombre de permutations est de l'ordre de $O(n!)$.
N | Permutations | Solutions | Python | C++ |
4 | 24 | 2 | 0.00 | 0.01 |
5 | 120 | 10 | 0.01 | 0.01 |
6 | 720 | 4 | 0.01 | 0.01 |
7 | 5040 | 40 | 0.01 | 0.01 |
8 | 40320 | 92 | 0.08 | 0.01 |
9 | 362880 | 352 | 0.73 | 0.15 |
10 | 3628800 | 724 | 7.42 | 1.77 |
11 | 39916800 | 2680 | 86.97 | 22.41 |
12 | 479001600 | 14200 | 1095.12 | 292.28 |
13 | 6227020800 | 73712 | ? | 3984.28 |
Voici le code python correspondant :
On crée une procédure récursive avec backtrack qui positionne la reine $r$ en colonne $c$ de manière à ce qu'elle ne soit pas en conflit avec une autre reine placée précédemment :
Cependant, on peut envisager différentes solutions pour construire la solution :
Voici le pseudo code utilisé pour la méthode 1.
Voici le pseudo code utilisé pour la méthode 2.
On utilise trois tableaux d'entiers initialisés à 0 :
Diagonales droites (orange) et gauches (vert)
Voici le pseudo code utilisé pour la méthode 3.
Les résultats présentés dans la table suivante montrent l'efficacité des trois méthodes.
N | Méthode 1 | Méthode 2 | Méthode 3 |
8 | 0.00 | 0.00 | 0.00 |
9 | 0.00 | 0.00 | 0.00 |
10 | 0.00 | 0.00 | 0.00 |
11 | 0.00 | 0.00 | 0.00 |
12 | 0.04 | 0.00 | 0.00 |
13 | 0.24 | 0.03 | 0.02 |
14 | 1.46 | 0.22 | 0.15 |
15 | 9.37 | 1.41 | 1.03 |
16 | 64.19 | 9.31 | 7.20 |
17 | 462.73 | 64.69 | 54.12 |
La méthode trois qui utilise trois tableaux pour indiquer si une reine est dans une colonne, une diagonale droite ou une diagonale gauche, est la plus efficace.
Voici le code minizinc du problème des N-Reines :
include "globals.mzn";
int: N = 8;
array[1..N] of var 1..N: queens;
constraint alldifferent(queens);
constraint alldifferent([ queens[i] + i | i in 1..N]); % distinct diagonals
constraint alldifferent([ queens[i] - i | i in 1..N]); % upwards+downwards
% search
solve satisfy;
output [ if fix(queens[j]) == i then "Q" else "." endif ++
if j == N then "\n" else "" endif | i,j in 1..N]
Certaines instances de problèmes peuvent demander des siècles voire des millions ou milliards d'années pour être résolues par des approches exactes.
On décide alors de tenter de trouver une solution sous-optimale (que l'on espère proche de l'optimum global) en passant moins de temps à chercher mais en cherchant intelligemment.
On ne s'intéresse qu'à une partie des configurations réalisables du problème, on mettra donc généralement moins de temps à résoudre le problème qu'avec une méthode exacte.
On risque de ne pas trouver la ou les solutions optimales mais une ou des solutions approchées (sous optimales). Au final on ne sait pas si la solution trouvée est la solution optimale.
Ces méthodes sont souvent qualifiées de métaheuristiques. Une métaheuristique est un algorithme d’optimisation visant à résoudre des problèmes d'optimisation considérés comme difficiles.
On peut classer les métaheuristiques en grands groupes ou suivant leurs caractéristiques sachant qu'on peut également intégrer tel ou tel aspect d'une métaheuristique dans une autre métaheuristique :
Les méthodes fondées sur la recherche locale se basent sur la notion de voisinage d'une configuration.
Partant d'une configuration $X$, on définit par exemple les voisins ou le voisinage $N(X)$ de $X$ comme étant toutes les configurations $X'$ pour lesquelles on a modifié la valeur d'une des variables $x_i$.
On s'arrange pour que le voisinage soit inclus strictement dans l'ensemble des configurations et/ou qu'il soit plus petit que l'ensemble des configurations possibles.
La taille du voisinage va donc influer sur la recherche : plus le voisinage est grand, plus on pourra explorer d'espace de recherche, mais plus la recherche prendra du temps.
Le principe de la recherche locale également appelée descente ou hill-climbing consiste, partant d'une configuration donnée, à rechercher les voisins améliorants et à en choisir un parmi ceux existants. On s'arrête si on ne trouve pas de voisin améliorant.
La recherche locale n'est généralement pas assez puissante et plusieurs problèmes se posent alors :
Voici une représentation explicative à une dimension mais il faut bien comprendre que l'on travaille en $n$ dimensions et que les courbes ne sont pas lisses.
Autre exemple :
Plusieurs variantes de la recherche locale existent :
Les problèmes liés aux méthodes inexactes et métaheuristiques qui en découlent sont liés à l'intensification et la diversification qui sont deux notions complémentaires et contradictoires :
La recherche locale itérée consiste à lancer plusieurs fois une recherche locale en partant d'une une nouvelle solution à chaque étape. On gardera la meilleure solution obtenue.
Le principe de l'algorithme glouton consiste à faire à chaque étape un choix optimum local dans l'espoir d'obtenir un résultat optimum global.
En d'autres termes, on cherche à chaque étape à faire le choix de la minimisation (ou de la maximisation) lors du choix d'une valeur à affecter à une variable.
Cette technique n'est probante que sur des problèmes simples, néanmoins elle peut être utilisée afin de construire une configuration initiale avant de lancer une descente ou une autre metaheuristique.
La recherche tabou se fonde sur un mécanisme de mémorisation des configurations déjà visitées afin d'éviter de boucler. Ce mécanisme de mémorisation est appelée liste tabou. En fait il s'agit d'une liste de taille fixe et le fait d'ajouter un nouvel élément en fin de liste supprime l'élément le plus ancien en début de liste.
Le recuit simulé (Simulated Annealing) est une métaheuristique basée sur le travail de Metropolis (). Elle se fonde sur une analogie avec le recuit en métallurgie qui consiste par exemple à refroidir un alliage de manière progressive afin de lui conférer des propriétés autres que celles que l'on aurait obtenu avec un refroidissement brutal.
Le recuit simulé est une descente avec un probabilité d'accepter des solutions moins bonnes avec un facteur de probabilité (règle de Metropolis) qui décroit à mesure que le système se refroidit.
La difficulté de la méthode repose sur les paramètres qui doivent être déterminés de manière empirique :
Lien
Les algorithmes génétiques comme leur nom l'indique se base sur la génétique et la reproduction cellulaire. Ils font partie des algorithmes à population et des algorithmes évolutionnaires c'est à dire qu'ils s'inspirent de la théorie de l'évolution.
On part d'un ensemble de configurations initiales qui constituent la population $P$. Cette population évolue au cours des générations. A chaque génération :
On pratique en quelque sorte un eugénisme.
Le problème que l'on peut rencontrer, dû à l'eugénisme, est le fait qu'après un certain temps les individus peuvent être similaires ce qui empêche la recherche de trouver des solutions plus intéressantes.
Plusieurs variantes de cet algorithme existent : on peut par exemple générer plusieurs enfants, choisir non pas deux mais plusieurs parents et les combiner.
Un algorithme mémétique est simplement un algorithme génétique pour lequel on applique une descente sur l'enfant généré à partir des deux parents après mutation.
On va donc chercher à obtenir un enfant bien meilleur que ses parents.
Il existe de nombreux autres algorithmes dont beaucoup sont inspirés de la nature comme :
Certaines techniques peuvent être développées pour des problèmes particuliers :
On se base sur le comportement des fourmis qui recherchent de la nourriture. La nourriture étant ici une bonne solution.
On dispose d'une population de fourmis qui construisent des solutions et déposent sur les voies qu'elles empruntent des phéromones. Plus il y a de phéromones sur une voie, plus il y a de chance qu'il s'agissent d'un chemin qui mène à une bonne solution.
Cette technique est bien adaptée au problème du voyageur de commerce (cf. Wikipedia.
Les algorithmes à Essaims Particulaires reposent sur le principe d'auto-organisation d'un groupe d'organismes vivants (notamment les oiseaux) d'agir ensemble et d'exhiber un comportement complexe, à partir de comportements simples des individus, en respectant les règles suivantes :
On pourra lire l'article suivant PSO (de Data Analytics Post) pour de plus amples explications.