Formulez un problème d'optimisation
Chaque lundi, le gérant d’une petite brasserie rennaise se pose la même question : combien d’hectolitres de blonde et d’ambrée lancer cette semaine ? L’ambrée rapporte moins mais la blonde consomme un houblon rare, et les cuves de fermentation ne sont pas extensibles. Il pourrait tâtonner. Il peut aussi écrire le problème proprement, et laisser les mathématiques trouver la meilleure réponse.
Dans ce chapitre, vous allez apprendre à traduire une situation concrète en problème d’optimisation, puis à résoudre à la main le cas le plus simple : un programme linéaire à deux variables. Cette brasserie nous accompagnera pendant toute la première partie du cours.
Les trois ingrédients d’un problème d’optimisation
Section intitulée « Les trois ingrédients d’un problème d’optimisation »Tout problème d’optimisation se compose de trois éléments.
- Les variables de décision : ce que vous pouvez choisir. Pour la brasserie, et sont les volumes de blonde et d’ambrée, en hectolitres.
- La fonction objectif (ou critère, ou coût) : ce que vous voulez rendre le plus petit ou le plus grand possible. Ici, le bénéfice de la semaine.
- L’ensemble réalisable (on dit aussi admissible) : les choix autorisés par les contraintes. Un volume négatif n’a pas de sens, et on ne peut pas remplir plus de cuves qu’on n’en possède.
On écrit alors le problème sous la forme
ce qui se lit « trouver un point de qui rend le plus petit possible ». Un point qui appartient à est dit réalisable ; un point réalisable qui atteint le minimum est une solution optimale, et la valeur correspondante est la valeur optimale.
Et si l’on veut maximiser, comme notre brasseur avec son bénéfice ? Il suffit de remarquer que maximiser revient à minimiser :
Les deux problèmes ont les mêmes solutions, seule la valeur change de signe. C’est pourquoi la théorie est presque toujours écrite pour des minimisations : vous n’aurez jamais qu’un seul cas à retenir.
Minimum global et minimum local
Section intitulée « Minimum global et minimum local »Deux notions de minimum coexistent, et il faut bien les distinguer.
- est un minimum global si pour tout de .
- est un minimum local si pour tout de assez proche de (dans une petite boule autour de ).
Pensez à un paysage de montagne : le fond de chaque vallée est un minimum local, mais seul le fond de la vallée la plus basse est le minimum global. Un randonneur qui descend toujours la pente finit dans une vallée, pas forcément la plus basse. Vous retrouverez ce randonneur dans la deuxième partie, car c’est exactement ce que fait la descente de gradient.
Un panorama des problèmes
Section intitulée « Un panorama des problèmes »Les méthodes de résolution dépendent beaucoup de la forme de et de . Voici les grandes familles que vous rencontrerez dans ce cours :
| Famille | Objectif | Contraintes | Méthode typique | Chapitres |
|---|---|---|---|---|
| Programmation linéaire | affine | affines (égalités, inégalités) | simplexe | 1 à 4 |
| Sans contrainte | quelconque, dérivable | aucune | gradient, Newton | 5 à 7 |
| Sous contraintes d’égalité | quelconque | multiplicateurs de Lagrange | 8 | |
| Sous contraintes d’inégalité | quelconque | conditions KKT | 9 |
Une fonction affine est de la forme : pas de carré, pas de produit de variables. Quand l’objectif et toutes les contraintes sont affines, on parle de programme linéaire (linear program, souvent abrégé PL, ou LP en anglais). C’est le cas de la brasserie.
Modéliser la brasserie
Section intitulée « Modéliser la brasserie »Voici les données du brasseur, pour une semaine :
| Ressource | Blonde (par hl) | Ambrée (par hl) | Disponible |
|---|---|---|---|
| Cuves de fermentation (hl) | 1 | 1 | 4 |
| Malt (sacs) | 1 | 3 | 9 |
| Houblon aromatique (lots) | 1 | 0 | 3 |
| Bénéfice (centaines d’euros) | 3 | 2 |
L’ambrée est plus maltée (3 sacs par hectolitre), la blonde est la seule à utiliser le houblon aromatique. La modélisation se fait en trois questions.
- Que choisit-on ? hectolitres de blonde et hectolitres d’ambrée.
- Que veut-on ? Maximiser le bénéfice (en centaines d’euros).
- Qu’est-ce qui limite ? Chaque ressource donne une contrainte : la consommation ne dépasse pas le disponible.
On obtient le programme linéaire :
« s.c. » signifie « sous les contraintes ». Les deux dernières, les contraintes de positivité, sont faciles à oublier et pourtant indispensables : sans elles, le modèle pourrait proposer de brasser hectolitres d’ambrée pour libérer du malt.
La méthode graphique
Section intitulée « La méthode graphique »Avec deux variables, on peut tout dessiner dans le plan . Chaque contrainte découpe le plan en deux par la droite et ne garde qu’un côté (pour savoir lequel, testez l’origine : vérifie-t-il l’inégalité ?). L’ensemble réalisable est l’intersection de tous ces demi-plans : un polygone convexe, c’est-à-dire sans creux.
Reste à placer l’objectif. Pour une valeur donnée , les points qui rapportent exactement forment la droite : une ligne de niveau de l’objectif. Toutes ces droites sont parallèles, et elles s’éloignent de l’origine quand augmente. La méthode consiste donc à faire glisser la droite dans le sens où grandit, tant qu’elle touche encore le polygone. Le dernier point touché est l’optimum.
Sur la figure, la droite traverse le polygone : on peut mieux faire. En la poussant vers le haut à droite, elle quitte le polygone au sommet , pour .
Pourquoi l’optimum tombe-t-il sur un sommet ? Parce que l’objectif est affine : sur un segment, il varie de façon linéaire, donc il est toujours au moins aussi bon à l’une des deux extrémités qu’au milieu. En répétant l’argument, on arrive au résultat fondamental de la programmation linéaire.
Les cas particuliers
Section intitulée « Les cas particuliers »Tout ne se passe pas toujours aussi bien. Trois situations méritent d’être reconnues au premier coup d’œil.
- Une infinité de solutions : si la droite de l’objectif est parallèle à un côté du polygone, elle quitte le polygone par tout un côté. Tous les points de ce côté sont optimaux (et en particulier ses deux sommets, ce qui est cohérent avec le résultat précédent).
- Un problème non borné : si le polygone est ouvert dans la direction où l’objectif augmente, on peut faire grandir indéfiniment. Il n’y a pas d’optimum. En pratique, c’est presque toujours le signe d’une contrainte oubliée.
- Un problème irréalisable : si les contraintes se contredisent (par exemple et ), l’ensemble réalisable est vide.
En Python, on délègue la résolution à scipy.optimize.linprog, qui minimise par convention. Pour maximiser le bénéfice, on minimise donc son opposé :
from scipy.optimize import linprog
c = [-3, -2] # on minimise -z = -3 x1 - 2 x2A_ub = [[1, 1], # cuves [1, 3], # malt [1, 0]] # houblonb_ub = [4, 9, 3]
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=[(0, None), (0, None)])print(res.status, res.x, -res.fun) # 0 = succès ; x = [3, 1] ; bénéfice 11L’attribut status vaut 2 si le problème est irréalisable et 3 s’il est non borné : pensez à le tester avant de lire la solution.
En résumé
Section intitulée « En résumé »- Un problème d’optimisation se compose de variables de décision, d’une fonction objectif et d’un ensemble réalisable défini par les contraintes.
- On se ramène toujours à une minimisation grâce à .
- Un minimum global est le meilleur de tout l’ensemble réalisable ; un minimum local n’est le meilleur que dans un voisinage.
- Un programme linéaire a un objectif et des contraintes affines ; n’oubliez pas les contraintes de positivité.
- En deux variables, la méthode graphique trace le polygone réalisable et fait glisser la ligne de niveau de l’objectif.
- L’optimum d’un programme linéaire, s’il existe, est atteint en un sommet ; le problème peut aussi avoir une infinité de solutions, être non borné ou irréalisable.
Exercices
Section intitulée « Exercices »Exercice · Résoudre graphiquement
facileRésolvez graphiquement le programme linéaire suivant, puis vérifiez en évaluant l’objectif en chaque sommet :
Voir la solution
La droite coupe les axes en et ; la droite en et . Leur intersection s’obtient en résolvant le système : en soustrayant deux fois la seconde équation à la première, on trouve , donc puis .
Les sommets du polygone sont , , et , où vaut respectivement 0, 4, 5 et 3,5. L’optimum est , avec . Graphiquement, la droite (de pente ) quitte le polygone en ce sommet, car sa pente est comprise entre celles des deux contraintes ( et ).
En code
import numpy as npfrom scipy.optimize import linprog
# Vérification aux sommets trouvés graphiquementsommets = np.array([[0, 0], [4, 0], [3, 2], [0, 3.5]])print(sommets @ [1, 1]) # [0. 4. 5. 3.5]
# Résolution directe (linprog minimise : on passe -z)res = linprog([-1, -1], A_ub=[[2, 1], [1, 2]], b_ub=[8, 7], bounds=[(0, None), (0, None)])print(res.x, -res.fun) # [3. 2.] 5.0# Contraintes A x <= b, positivité comprise (-x1 <= 0, -x2 <= 0)A <- rbind(c(2, 1), c(1, 2), c(-1, 0), c(0, -1))b <- c(8, 7, 0, 0)sommets <- NULLfor (i in combn(nrow(A), 2, simplify = FALSE)) { if (abs(det(A[i, ])) < 1e-9) next # droites parallèles x <- solve(A[i, ], b[i]) # intersection de deux droites if (all(A %*% x <= b + 1e-9)) sommets <- rbind(sommets, x) # réalisable ?}z <- sommets %*% c(1, 1)cbind(sommets, z) # (3, 2) : 5 ; (4, 0) : 4 ; (0, 3.5) : 3.5 ; (0, 0) : 0sommets[which.max(z), ] # 3 2Exercice · Modéliser une crêperie
moyenUne crêperie prépare chaque matin des galettes de sarrasin et des crêpes de froment, par douzaines. Une douzaine de galettes rapporte 4 €, une douzaine de crêpes 3 €. La plaque (la billig) permet de cuire au plus 10 douzaines au total. La farine de sarrasin disponible suffit pour 6 douzaines de galettes, et le lait pour 8 douzaines de crêpes. Écrivez le programme linéaire, puis résolvez-le.
Voir la solution
Notons le nombre de douzaines de galettes et celui de crêpes. Le programme est :
Les sommets du polygone sont , , , et , où vaut 0, 24, 36, 32 et 24. La crêperie doit faire 6 douzaines de galettes et 4 douzaines de crêpes, pour 36 € : la plaque et le sarrasin sont saturés, il reste du lait pour 4 douzaines de crêpes.
En code
from scipy.optimize import linprog
# x1 : douzaines de galettes, x2 : douzaines de crêpesc = [-4, -3] # on minimise -z = -4 x1 - 3 x2A_ub = [[1, 1], # billig : 10 douzaines au total [1, 0], # sarrasin : 6 douzaines de galettes [0, 1]] # lait : 8 douzaines de crêpesb_ub = [10, 6, 8]res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=[(0, None), (0, None)])print(res.x, -res.fun) # [6. 4.] 36.0print(res.slack) # [0. 0. 4.] : billig et sarrasin saturés, lait restant 4# Contraintes A x <= b : billig, sarrasin, lait, puis positivitéA <- rbind(c(1, 1), c(1, 0), c(0, 1), c(-1, 0), c(0, -1))b <- c(10, 6, 8, 0, 0)sommets <- NULLfor (i in combn(nrow(A), 2, simplify = FALSE)) { if (abs(det(A[i, ])) < 1e-9) next x <- solve(A[i, ], b[i]) if (all(A %*% x <= b + 1e-9)) sommets <- rbind(sommets, x)}sommets <- unique(round(sommets, 9))z <- sommets %*% c(4, 3)cbind(sommets, z) # (6, 4) : 36 ; (2, 8) : 32 ; (6, 0) : 24 ; (0, 8) : 24 ; (0, 0) : 0sommets[which.max(z), ] # 6 4Exercice · Reconnaître les cas particuliers
difficileOn reprend les contraintes de la brasserie.
- Le prix de l’ambrée augmente : le bénéfice devient . Quelles sont les solutions optimales ?
- On revient à , mais le brasseur loue autant de cuves et achète autant de houblon qu’il veut : seule la contrainte du malt subsiste. Que se passe-t-il ?
Voir la solution
- La nouvelle ligne de niveau est parallèle à la contrainte des cuves . En chaque sommet : donne 9, donne 12, donne 12, donne 9. Les deux sommets et sont optimaux, ainsi que tout le segment qui les relie, où les cuves sont pleines : il y a une infinité de solutions, toutes de valeur 12.
- Il ne reste que et la positivité. Contrairement aux apparences, le problème reste borné : les coefficients de cette contrainte sont tous positifs, donc elle impose à elle seule et . L’ensemble réalisable est le triangle de sommets , et , où vaut 0, 27 et 6 : le brasseur ne produit plus que de la blonde, 9 hl, pour 2 700 €. Pour obtenir un problème non borné, il faudrait lever aussi la contrainte du malt : le point serait alors réalisable pour tout et rapporterait , qui tend vers l’infini. Ce n’est évidemment pas la réalité, c’est le modèle qui aurait perdu ses contraintes (aucune ressource n’est infinie).
En code
import numpy as npfrom scipy.optimize import linprog
A_ub, b_ub = [[1, 1], [1, 3], [1, 0]], [4, 9, 3]# 1. z = 3 x1 + 3 x2 : tout le segment entre (3, 1) et (1,5 ; 2,5) vaut 12for p in [(3, 0), (3, 1), (1.5, 2.5), (0, 3), (2, 2)]: print(p, 3 * p[0] + 3 * p[1]) # 9, 12, 12, 9, 12 : (2, 2) est aussi optimalres = linprog([-3, -3], A_ub=A_ub, b_ub=b_ub, bounds=[(0, None)] * 2)print(-res.fun) # 12.0 (le solveur renvoie un seul des optimums)
# 2. Seule la contrainte du malt : le problème reste borné !res = linprog([-3, -2], A_ub=[[1, 3]], b_ub=[9], bounds=[(0, None)] * 2)print(res.status, res.x, -res.fun) # 0 [9. 0.] 27.0# Sans aucune contrainte de ressource, en revanche, il devient non bornéres = linprog([-3, -2], bounds=[(0, None)] * 2)print(res.status) # 3 = non bornésommets_pl <- function(A, b) { # sommets du polygone A x <= b S <- NULL for (i in combn(nrow(A), 2, simplify = FALSE)) { if (abs(det(A[i, ])) < 1e-9) next x <- solve(A[i, ], b[i]) if (all(A %*% x <= b + 1e-9)) S <- rbind(S, x) } unique(round(S, 9))}positivite <- rbind(c(-1, 0), c(0, -1))# 1. Brasserie avec z = 3 x1 + 3 x2 : deux sommets à égalitéS <- sommets_pl(rbind(c(1, 1), c(1, 3), c(1, 0), positivite), c(4, 9, 3, 0, 0))cbind(S, S %*% c(3, 3)) # (1.5, 2.5) et (3, 1) valent 12 ; (0, 3) et (3, 0) valent 9# 2. Seul le malt subsiste : un triangle, donc un problème bornéS <- sommets_pl(rbind(c(1, 3), positivite), c(9, 0, 0))cbind(S, S %*% c(3, 2)) # (0, 3) : 6 ; (9, 0) : 27 ; (0, 0) : 0