La programmation linéaire
Ce que ce chapitre apporte 9 points
- Reconnaître un modèle linéaire, et ce qui n'en est pas un : un produit de variables, un quotient, un coût fixe.
- Écrire un programme linéaire sous forme canonique et sous forme standard, avec ses variables d'écart.
- Lire le domaine réalisable comme un polygone, et trouver l'optimum à l'un de ses sommets.
- Dérouler le simplexe à la main : variables de base et hors base, variable entrante, variable sortante au plus petit rapport, pivot, critère d'arrêt.
- Dire ce que fait une méthode de points intérieurs, et ce que vaut le simplexe dans le pire cas et en pratique.
- Écrire un modèle avec PuLP, et le même avec linprog, sous la forme de matrices qu'un solveur attend.
- Interpréter une solution : contraintes saturées, écarts, prix d'une ressource et jusqu'où il tient, problème impossible ou non borné.
- Modéliser et résoudre un problème de transport, et constater que sa solution est entière.
- Refuser d'arrondir une solution continue pour obtenir une solution entière.
Le modèle linéaire
Un programme linéaire décrit une décision par trois choses. Des variables de décision, qui prennent des valeurs réelles. Un objectif, à maximiser ou à minimiser, qui est une somme de termes proportionnels aux variables : . Des contraintes, qui comparent chacune une somme du même type à une constante, par « ≤ », « ≥ » ou « = ». Les nombres et les coefficients des contraintes sont des données ; seules les variables sont à choisir.
Valdrome fabrique aussi deux références en lots, sans rapport avec les dix commandes de la semaine : des poulies, qui rapportent 520 € le lot, et des bielles, 240 € le lot. Un lot de poulies demande 5 heures de tournage et 3 heures de fraisage ; un lot de bielles, 2 heures de tournage et 2 heures de fraisage. Le tournage dispose de 15 heures, le fraisage de 12. Les variables et sont les nombres de lots de poulies et de bielles.
Ce modèle suppose trois choses, qu'il vaut mieux dire. La proportionnalité : deux lots consomment deux fois les heures d'un lot, et rapportent deux fois sa marge. L'additivité : les heures des poulies et des bielles s'ajoutent, sans gêne ni économie de l'une à l'autre. La divisibilité : un lot et demi a un sens. Quand l'une des trois tombe, le modèle n'est plus linéaire, ou plus continu.
Ce qui n'est pas linéaire
- Un produit de variables, ou une puissance. Une remise qui grandit avec la quantité vendue fait de la marge d'un lot une fonction de , et la marge totale contient .
- Un quotient. « Au moins un tiers des lots sont des poulies » s'écrit d'abord . Le total est positif : multiplier les deux côtés par lui ne change pas le sens de l'inégalité, et la règle devient , soit , qui est linéaire. Cette linéarisation se fait à la main, aucun solveur ne la fait.
- Un coût fixe. Un réglage de 300 € payé dès qu'au moins un lot de bielles est lancé ne dépend pas du nombre de lots : le coût saute de 0 à 300 quand quitte 0. Ni ni aucune autre somme proportionnelle ne l'écrit.
- Un maximum, une valeur absolue, une condition. « Le plus long des deux temps », « l'écart à la cible, quel que soit son sens », « si l'on fait des poulies, alors on fait aussi des bielles ».
Exiger des valeurs entières n'est pas une non-linéarité, mais cela sort aussi du cadre : c'est la programmation linéaire en nombres entiers, bien plus dure, qui occupe le chapitre 4. Le chapitre 5 traite des modèles qui sortent du cadre linéaire.
1.Laquelle de ces contraintes est linéaire ?
2.Le réglage de 300 €, payé dès qu'un lot de bielles est lancé, peut-il entrer dans l'objectif sous la forme ?
Forme canonique, forme standard
Un même problème s'écrit de plusieurs façons, et chaque méthode de résolution en attend une précise. Deux formes reviennent partout.
La forme canonique maximise, sous des contraintes toutes en « ≤ », avec des variables positives ou nulles. Les lots y sont déjà.
La forme standard n'a que des égalités, et des variables positives ou nulles. Chaque contrainte « ≤ » reçoit une variable d'écart, positive, qui est ce qui reste de la ressource :
Au plan de 1 lot de poulies et 2 lots de bielles, il reste heures de tournage et heures de fraisage. Une contrainte est saturée quand son écart vaut 0 : la ressource est épuisée. Un écart ne peut pas être négatif, ce serait consommer plus qu'il n'y a. Une contrainte « ≥ » reçoit, elle, une variable d'excédent, retranchée : « au moins 2 lots en tout », , devient avec , ce qui dépasse le minimum.
Tout programme linéaire se ramène à l'une ou l'autre forme, par des conversions qui ne changent pas l'ensemble des plans possibles, seulement leur écriture.
| Écrit | Se ramène à |
|---|---|
| minimiser | maximiser : le même plan, la valeur au signe près |
| deux inégalités, « ≤ » et « ≥ » (en forme canonique) | |
| une variable de signe quelconque | la différence de deux variables positives, |
| une borne, | une contrainte de plus, ou une borne que le solveur traite à part |
Pourquoi un solveur n'accepte souvent qu'une forme. Son code ne travaille que sur l'écriture de son algorithme. Le simplexe, plus bas, raisonne sur la forme standard. La fonction linprog de scipy reçoit un problème à minimiser, des matrices de « ≤ » et d'« = », et des bornes : un problème à maximiser, ou une contrainte « ≥ », doit d'abord y être converti par celui qui l'écrit. Ces conversions sont faciles et propices aux erreurs de signe. Une bibliothèque de modélisation comme PuLP les fait elle-même, et c'est l'une de ses raisons d'être : la section Écrire un modèle avec PuLP montre les deux écritures du même modèle.
Les écarts d'un plan
- 1.
Au plan de 2 lots de poulies et 2 lots de bielles, combien d'heures de tournage restent-elles ?
- 2.
Et combien d'heures de fraisage ?
- 3.
Au plan de 3 lots de poulies et un demi-lot de bielles, que vaudrait l'écart de tournage ?
La résolution graphique
À deux variables, un programme linéaire se dessine. Le détail de la méthode graphique est traité dans le chapitre optimiser sous contraintes du parcours Mathématiques appliquées ; on ne le refait pas, on s'en sert.
Chaque contrainte est un demi-plan, et leur intersection est un polygone, le domaine réalisable. La marge est constante sur une droite, et cette droite glisse parallèlement à elle-même quand la marge change. La figure dessine le polygone et cette droite. Les lots entiers réalisables sont les points pleins de la grille. Avant toute explication, chercher à la main le lot le plus rentable : glisser la droite vers les grandes marges, choisir un point plein, puis demander à la figure si c'est le meilleur.
- Droite
- 520 x + 240 y = 800 €
- Point retenu
- aucun
Glisser la droite orange, ou la régler au curseur. Zone grisée : interdite par une contrainte. Points pleins : points entiers réalisables. Au clavier : Tab jusqu'au tracé, flèches pour parcourir la grille, Entrée pour retenir un point, Page haut ou bas pour avancer la droite.
L'optimum est atteint au dernier point du polygone que la droite touche en glissant vers les marges croissantes. C'est un cas d'une propriété générale, qui fonde tout ce chapitre.
Si un programme linéaire a un optimum, l'un au moins de ses points optimaux est un sommet du domaine réalisable, un point où se croisent autant de bords que de variables : deux droites, à deux variables.
La raison tient à la linéarité de l'objectif. Le long d'un segment, il varie en ligne droite : un point du milieu ne fait jamais mieux que le meilleur des deux bouts. Deux nuances. Quand la droite de l'objectif est parallèle à un côté du polygone, tout ce côté est optimal, ses deux sommets compris. Et un programme linéaire peut n'avoir aucun optimum, s'il est impossible ou non borné : la section Interpréter une solution y revient.
Il suffit donc d'examiner les sommets. Le bloc suivant les trouve comme on le ferait à la main : il croise les bords deux à deux, contraintes et axes, garde les points qui respectent toutes les contraintes, et calcule la marge de chacun. Il énumère aussi les plans en lots entiers, peu nombreux ici.
Six paires de bords, quatre sommets : (0 ; 0), (3 ; 0), (1,5 ; 3,75) et (0 ; 6), de marges 0, 1 560, 1 680 et 1 440 €. Les deux autres croisements, où le tournage coupe l'axe des bielles et le fraisage celui des poulies, sortent du polygone. Cette méthode ne passe pas à l'échelle. À variables et contraintes, un sommet est le croisement de bords parmi , et il y a choix à essayer : avec 20 variables et 20 contraintes, , plus de cent milliards de systèmes à résoudre. Il faut une méthode qui n'essaie pas tout : c'est le simplexe, à la section suivante.
Le meilleur sommet est à lot de poulies et lots de bielles, pour 1 680 €. Un lot et demi, trois lots trois quarts : ce n'est pas une décision d'atelier. Si les lots sont indivisibles, l'optimum tombe à 1 560 €, avec 3 lots de poulies et aucun de bielles, le meilleur des 16 plans en lots entiers. Et le point entier obtenu en arrondissant le sommet continu vers le bas, 1 lot de poulies et 3 de bielles, ne rapporte que 1 240 € : arrondir n'est pas résoudre, l'optimum entier est ici dans un autre coin du polygone.
Deux leçons pour la suite. La première : le continu donne une borne (1 680 €), et le problème en nombres entiers est plus dur que son relâché continu. On sait résoudre le second pour des milliers de variables ; le premier reste difficile en général. La seconde : un solveur en nombres entiers traite directement le cas où l'on impose l'entier. PuLP le fait plus bas en un mot, et le chapitre 4 explique comment il s'y prend.
La solution de la relaxation continue d'un problème de maximisation vaut 1 680, et la meilleure solution en nombres entiers vaut 1 560. Laquelle de ces phrases est fausse ?
Le simplexe, de sommet en sommet
Le simplexe, mis au point par George Dantzig en 1947, part d'un sommet, ici l'origine, et passe à un sommet voisin, relié au premier par un côté du polygone, à condition que la marge y soit meilleure. Il recommence jusqu'à un sommet dont aucun voisin ne fait mieux. Ce sommet est l'optimum, parce que le polygone n'a pas de creux : il est convexe, le segment qui joint deux de ses points y reste tout entier, si bien qu'un sommet qu'aucun voisin n'améliore est le meilleur de tous. C'est ce qui sépare un programme linéaire des problèmes de la recherche locale, où un optimum local n'est que local.
La figure déroule le simplexe sur les lots. Chaque clic sur « Pivot suivant » fait un pas, et le tableau sous le dessin est celui que la suite de cette section apprend à lire.
- Sommet courant
- (0 ; 0), après 0 pivot
- Marge
- 0 €
Zone grisée : interdite par une contrainte. Point plein : le sommet courant ; flèches : les pivots déjà faits ; tirets orange : la droite de la marge du sommet courant. Chaque pivot fait entrer une variable de coefficient positif dans la ligne de la marge, et sortir la variable de base du plus petit rapport.
| base | x | y | e₁ | e₂ | valeur |
|---|---|---|---|---|---|
| e₁ | 5 | 2 | 1 | 0 | 15 |
| e₂ | 3 | 2 | 0 | 1 | 12 |
| marge | 520 | 240 | 0 | 0 | 0 |
Écarts : e₁ Tournage, e₂ Fraisage. Hors base, une variable vaut 0. Dans la ligne de la marge, un coefficient positif (en couleur) est ce que rapporte une unité de cette variable.
Le tableau : variables de base et hors base
Le simplexe travaille sur la forme standard des lots, quatre variables et deux égalités :
Deux équations fixent deux inconnues : si deux des quatre variables sont mises à 0, les deux autres se calculent. À chaque sommet, les variables se partagent ainsi en deux groupes. Les variables hors base, autant que de variables de décision, valent 0. Les variables de base, une par contrainte, se calculent par les égalités. Choisir les variables hors base, c'est choisir les deux bords qui se croisent au sommet : et à l'origine ; , le tournage épuisé, et au sommet (3 ; 0).
À l'origine, et sont hors base, et chaque écart vaut toutes les heures de son atelier. Écrire chaque variable de base en fonction des variables hors base donne un dictionnaire, et la marge s'y écrit de même, notée :
Le tableau range les mêmes nombres : une ligne par égalité, une ligne pour la marge, une colonne par variable, et la valeur de chaque variable de base à droite.
| base | valeur | ||||
|---|---|---|---|---|---|
| 5 | 2 | 1 | 0 | 15 | |
| 3 | 2 | 0 | 1 | 12 | |
| marge | 520 | 240 | 0 | 0 | 0 |
La ligne de la marge porte, sous chaque variable, ce que rapporterait une unité de cette variable à partir du sommet courant. On l'appelle aussi la ligne des coûts réduits.
Un pivot, pas à pas
- La variable entrante. Une variable hors base dont le coefficient dans la ligne de la marge est positif : la faire monter améliore la marge. La règle de Dantzig prend le plus grand coefficient.
- La variable sortante. Faire monter l'entrante fait baisser des variables de base. Chacune s'annule à un rapport : sa valeur, divisée par son coefficient dans la colonne de l'entrante, quand ce coefficient est positif. La sortante est celle du plus petit rapport, la première à atteindre 0 : monter plus haut la rendrait négative, et le plan sortirait du polygone. Une ligne où l'entrante a un coefficient nul ou négatif ne limite rien.
- Le pivot. L'entrante prend la place de la sortante dans la base : sa ligne se résout en elle, puis on la remplace partout ailleurs, ligne de la marge comprise. Le plan est passé au sommet voisin.
- Le critère d'arrêt. Quand plus aucun coefficient de la ligne de la marge n'est positif, le sommet courant est l'optimum.
Premier pivot. À l'origine, entre, avec le plus grand coefficient, 520 € par lot. reste à 0, et les écarts valent et . Le premier s'annule à , le second à : les rapports sont 3 et 4, et sort, au plus petit. La ligne du tournage se résout en , , et remplace dans les deux autres :
Le plan est en (3 ; 0), pour 1 560 €. Le coefficient de n'est plus 240, mais 32 : au sommet (3 ; 0), le tournage est épuisé, et un lot de bielles de plus prend 2 heures de tournage qu'il faut retirer aux poulies, soit deux cinquièmes de lot. Il rapporte €. C'est encore positif : un pivot de plus s'impose.
Second pivot. entre, seul candidat. reste à 0, si bien que s'annulerait à , et à : sort. Après réécriture :
| base | valeur | ||||
|---|---|---|---|---|---|
| 1 | 0 | 1,5 | |||
| 0 | 1 | 3,75 | |||
| marge | 0 | 0 | −80 | −40 | 1 680 |
Plus aucun coefficient positif : faire monter ou , c'est laisser des heures inutilisées, et la marge ne peut que baisser. Le simplexe s'arrête en (1,5 ; 3,75), pour 1 680 €, après deux pivots et trois sommets visités ; le sommet (0 ; 6) ne l'a jamais été. Les deux nombres −80 et −40 resservent plus bas : ce sont, au signe près, ce que vaut une heure de plus de chaque atelier.
La règle d'entrée choisit le chemin, pas l'arrivée. Faire entrer d'abord, comme la figure le permet, mène par (0 ; 6), pour 1 440 €, puis au même sommet (1,5 ; 3,75). Quand trois bords ou plus passent par un même sommet, un rapport peut être nul : le pivot change la base sans déplacer le plan. Un tel sommet est dit dégénéré ; l'atelier en rencontre un dès son premier pivot. Les solveurs ajoutent des règles qui empêchent alors de tourner en rond.
Le bloc suivant fait les pivots en Python, sans bibliothèque, en fractions exactes : s'y affiche 2/5. Il prend le problème sous forme canonique, des marges c, une matrice A et des seconds membres b, et construit lui-même la forme standard. Il affiche chaque tableau, l'entrante, les rapports, la sortante, et le sommet atteint.
Le bloc retrouve les deux pivots et les trois tableaux de la section. Il ne dépend pas du nombre de variables. Lui donner un troisième produit, à 660 € le lot pour 5 heures de tournage et 8 de fraisage, demande une colonne de plus : c=[520, 240, 660], A=[[5, 2, 5], [3, 2, 8]] et noms=["x", "y", "w"]. Le simplexe le fait entrer d'abord, avec le plus grand coefficient, passe par deux sommets où il est fabriqué, puis l'abandonne au troisième pivot, et finit au même plan, pour la même marge. Le dernier tableau porte −60 sous lui : aux prix de 80 € l'heure de tournage et 40 € l'heure de fraisage, ses 5 et 8 heures valent €, plus que les 660 € qu'il rapporte.
1.Au sommet (3 ; 0), le coefficient de dans la ligne de la marge vaut 32, et non plus 240. Pourquoi ?
2.Au second pivot, entre, et les rapports valent 7,5 pour et 3,75 pour . Que se passerait-il si montait jusqu'à 7,5 ?
3.Au dernier tableau, la ligne de la marge vaut 0, 0, −80 et −40. Que conclure ?
La ligne de la marge qui ne bouge pas
Une seule étape est fausse. Désigner laquelle.
Un camarade mène le simplexe sur les lots, depuis l'origine, avec la règle du plus grand coefficient.
Les points intérieurs
Le simplexe longe le bord du polygone. Une méthode de points intérieurs prend l'autre chemin : elle part d'un point à l'intérieur du domaine, et avance vers l'optimum à travers lui, en se tenant d'abord à distance des bords, puis en s'en approchant à mesure qu'elle relâche cette distance. Elle ne visite aucun sommet en route. Son nombre d'étapes croît au plus comme un polynôme de la taille du problème, depuis les travaux de Narendra Karmarkar, en 1984. Elle finit tout près du sommet optimal, et une dernière étape l'y amène exactement.
Dans la page, PuLP confie le calcul à HiGHS, un solveur libre, par la fonction linprog de scipy et sa méthode "highs". D'après la documentation de scipy, cette méthode choisit d'elle-même entre deux algorithmes de HiGHS : "highs-ds", un simplexe dual révisé, une variante du simplexe de cette section, et "highs-ipm", une méthode de points intérieurs, munie de cette étape finale qui la rend aussi précise qu'un simplexe. Le plus rapide des deux dépend du problème.
Le pire cas, et la pratique
Le simplexe est rapide sur les problèmes de la pratique, et pourtant son pire cas est exponentiel. En 1972, Victor Klee et George Minty ont construit des problèmes à variables, des cubes déformés, sur lesquels la règle du plus grand coefficient visite tous les sommets, , en pivots. Des exemples du même genre ont été construits depuis pour les autres règles d'entrée courantes, et l'on ne connaît aucune règle dont on ait prouvé qu'elle reste polynomiale dans tous les cas. Les problèmes de la pratique se résolvent pourtant en un nombre de pivots modeste, qui croît à peu près comme leur nombre de contraintes. Le bloc suivant compte les pivots sur les deux sortes de problèmes ; il ne démontre rien, il montre.
Sur le cube de Klee et Minty, chaque variable de plus double le nombre de pivots : 3 à deux variables, 1 023 à dix, 4 095 à douze. Sur vingt problèmes tirés au hasard à chaque taille (graine 1), le plus long n'en demande jamais plus de 7. Le pire cas existe, mais il ne se rencontre guère hors des exemples construits pour lui.
La programmation linéaire est pourtant un problème facile, au sens du chapitre 2 : sa version de décision est dans P, et l'on sait la résoudre en un temps polynomial depuis 1979, par la méthode de l'ellipsoïde de Leonid Khachiyan, lente en pratique, puis par les points intérieurs. Sa version en nombres entiers, elle, est NP-difficile. Le simplexe illustre la différence entre la complexité dans le pire cas et le comportement observé : un algorithme exponentiel dans le pire cas peut rester le plus employé.
Écrire un modèle avec PuLP
Un solveur résout ; une bibliothèque de modélisation écrit. PuLP, une bibliothèque Python, laisse écrire le modèle presque comme sur le papier, maximiser, « ≥ », une expression de chaque côté, et le traduit dans la forme que le solveur attend.
| Besoin | Écriture |
|---|---|
| Un problème à maximiser (ou à minimiser) | prob = LpProblem("lots", LpMaximize) (ou LpMinimize, le défaut) |
| Une variable positive, continue | x = LpVariable("poulies", lowBound=0) |
| Une variable bornée, ou entière | LpVariable("nuit", lowBound=5, upBound=30), cat="Integer" |
| L'objectif | prob += 520 * x + 240 * y : une expression seule |
| Une contrainte nommée | prob += 5 * x + 2 * y <= 15, "tournage" : une comparaison, <=, >= ou == |
| Une somme sur un ensemble | lpSum(cout[i] * q[i] for i in routes) |
| Résoudre | prob.solve() |
| Le statut de la résolution | LpStatus[prob.status] : "Optimal", "Infeasible", "Unbounded" |
| La valeur d'une variable, de l'objectif | x.varValue, value(prob.objective) |
| L'écart, le prix d'une contrainte | prob.constraints["tournage"].slack, .pi |
Le bloc suivant écrit les lots. Le premier bloc de la page qui importe pulp met environ deux secondes à le charger ; les suivants vont vite.
Le solveur rend le sommet (1,5 ; 3,75) et ses 1 680 €, le statut "Optimal", et pour chaque contrainte son écart et son prix, que la section suivante interprète. Avec ENTIERS = True, le même modèle exige des lots entiers, et rend (3 ; 0), pour 1 560 € : le meilleur point entier de la figure. Les prix, eux, n'ont plus de sens en nombres entiers, et se lisent sur le problème continu.
La forme brute. La fonction linprog de scipy reçoit le même problème sous la forme qu'attend le solveur : des matrices. Il faut alors faire soi-même les conversions de la forme canonique : linprog minimise, on lui passe la marge changée de signe, et l'on change le signe de ce qu'il rend.
linprog n'accepte qu'une forme : minimiser sous , , et des bornes. Une contrainte « ≥ » s'y écrit en changeant tous ses signes, et une égalité passe par A_eq et b_eq. C'est exactement ce que PuLP fabrique à partir du modèle écrit tel qu'on le dit : une colonne par variable, une ligne par contrainte, les signes remis dans le bon sens. Les deux écritures rendent le même plan, les mêmes écarts et les mêmes prix ; la seconde montre ce que voit le solveur, la première se relit et se modifie sans erreur de signe.
Interpréter une solution
Le plan, (1,5 ; 3,75) pour 1 680 €, n'est que la moitié de la réponse. La sortie d'un solveur dit aussi ce qui le fixe, ce qui reste, et ce que vaudrait une unité de plus de chaque ressource.
Contraintes saturées, et ce qui reste
Au plan (1,5 ; 3,75), le tournage consomme heures et le fraisage : les deux contraintes sont saturées, et leurs écarts, que PuLP donne par slack, sont nuls. Ce sont elles qui fixent le plan : un sommet, c'est un choix de contraintes saturées, celles dont l'écart a quitté la base au fil des pivots. Une contrainte non saturée laisse un reste, et ne limite rien. PuLP écrit l'écart comme le second membre moins la valeur de l'expression : il est positif pour une « ≤ » non saturée, et négatif pour une « ≥ » non saturée. Avec une commande d'au moins 5 lots en tout, , le plan (1,5 ; 3,75) en fait 5,25, et PuLP donne à cette contrainte un écart de −0,25.
Ce que vaut une unité de plus
Le prix d'une ressource, ou prix dual, est ce que gagnerait l'objectif optimal si la ressource augmentait d'une unité, le plan étant recalculé. Une ressource qui n'est pas saturée a un prix nul : il en reste déjà, une unité de plus ne sert à rien.
Trois lectures donnent le même nombre : refaire le calcul avec une unité de plus ; lire contrainte.pi dans PuLP ; lire le dernier tableau du simplexe, où il figure au signe près, dans la ligne de la marge, sous l'écart de la contrainte.
Avec 16 heures de tournage, le tournage et le fraisage restent saturés, et le sommet glisse le long de la droite du fraisage, jusqu'en (2 ; 3), pour 1 760 € : 80 € de plus. C'est le −80 du dernier tableau, et le pi du tournage que PuLP affiche. Une heure de fraisage vaut de même 40 €. Si un sous-traitant propose une heure de tournage à 60 €, l'acheter laisse 20 € de gain ; à 100 €, elle coûte plus qu'elle ne rapporte. Le prix d'une ressource est le prix au-delà duquel une unité de plus ne vaut plus la peine d'être achetée.
Au signe près, le prix dit aussi ce que coûte une unité de moins. Le dernier dictionnaire le montre : , et une heure de tournage laissée libre, , coûte 80 €.
Jusqu'où le prix tient
Le prix ne vaut que tant que les mêmes contraintes restent saturées au sommet optimal. Avec heures de tournage, ce sommet est le croisement des droites du tournage et du fraisage : et . Il reste dans le polygone tant que et , c'est-à-dire pour entre 12 et 20 heures. Au-delà de 20 heures, plus aucune bielle n'est fabriquée : quatre lots de poulies épuisent le fraisage, il reste du tournage, et une heure de plus ne rapporte plus rien. C'est l'idée de l'analyse de sensibilité : chaque prix a sa plage de validité, et l'étudier dit jusqu'où une décision prise sur un prix reste bonne. Beaucoup de solveurs calculent ces plages ; à deux variables, elles se trouvent à la main, comme ici.
Le prix d'une heure de fraisage
- 1.
Avec 13 heures de fraisage au lieu de 12, le tournage et le fraisage restent saturés. Combien de lots de poulies le nouveau sommet compte-t-il ?
- 2.
Et combien de lots de bielles ?
- 3.
Quelle marge ce sommet rapporte-t-il, en euros ?
- 4.
Que rapporte donc la treizième heure de fraisage, en euros ?
- 5.
Jusqu'à combien d'heures de fraisage ce prix tient-il ? Chercher où le sommet quitte le polygone en atteignant l'axe des bielles.
Les cas sans réponse
Un programme linéaire n'a pas toujours d'optimum, et le solveur le dit par son statut.
- Impossible. Aucun plan ne respecte toutes les contraintes : elles se contredisent. Un client qui exige au moins 7 lots en tout, , demande l'impossible : le polygone ne contient aucun plan de plus de 6 lots, au sommet (0 ; 6). PuLP répond
"Infeasible", etvarValuevautNone. C'est une réponse, pas une panne : la demande, telle qu'elle est écrite, ne peut pas être tenue, et c'est au métier de décider quelle contrainte desserrer. - Non borné. L'objectif peut croître sans fin. Dans un problème réel, c'est presque toujours une contrainte oubliée, ou écrite à l'envers : les deux contraintes des lots écrites en « ≥ » laissent produire autant de lots qu'on veut. PuLP répond
"Unbounded". Le simplexe le voit quand une entrante n'a aucun rapport : aucune ligne ne l'arrête, et le bloc du simplexe l'écrit.
Le statut se lit avant la solution : sur un problème impossible ou non borné, il n'y a pas de plan à lire, et un programme qui affiche x.varValue sans regarder le statut affiche None, ou plante sur un calcul qui en dépend.
Le problème de transport
Trois carrières, au nord, au centre et au sud d'un département, livrent du gravier à quatre chantiers : un pont, une gare, un lycée et un stade. Chaque carrière peut fournir au plus son tonnage de la semaine ; chaque chantier doit recevoir exactement le sien ; chaque tonne transportée coûte selon la route. Où prendre le gravier de chaque chantier pour payer le moins de transport ?
| Coût (€ par tonne) | Pont | Gare | Lycée | Stade | Offre (t) |
|---|---|---|---|---|---|
| Nord | 4 | 6 | 9 | 7 | 120 |
| Centre | 5 | 3 | 4 | 6 | 110 |
| Sud | 8 | 5 | 3 | 4 | 90 |
| Demande (t) | 70 | 90 | 60 | 80 |
Une variable par route : , les tonnes livrées de la carrière au chantier , douze variables positives. Le modèle s'écrit
où est le coût d'une tonne sur la route, l'offre de la carrière et la demande du chantier. Les offres totalisent 320 tonnes pour 300 demandées : une carrière au moins gardera du gravier, et la contrainte d'offre est une « ≤ ». Si l'offre totale était inférieure à la demande, le problème serait impossible.
Le modèle a la même forme quelle que soit la taille : une contrainte par carrière, une par chantier, écrites en parcourant les dictionnaires, et lpSum pour les sommes. Le bloc le résout, puis le résout encore sur trente problèmes tirés au hasard (graine 1), et compte ceux dont le plan est entier.
Le plan coûte 1 160 € et emprunte six routes : le Nord livre le pont (70 t) et une partie du stade (30 t), le Centre la gare (90 t) et une partie du lycée (20 t), le Sud le reste du lycée (40 t) et du stade (50 t). Le Nord garde 20 tonnes : son offre n'est pas saturée, et son prix est nul. Les prix se lisent comme ceux des lots. Une tonne de plus livrée au stade coûterait 7 € : elle viendrait du Nord, la seule carrière qui ait du reste, par la route à 7 €. Une tonne de plus à la carrière du Centre ferait économiser 2 € : elle irait au lycée à la place d'une tonne du Sud, qui irait au stade à la place d'une tonne du Nord, soit €.
Une solution entière, sans l'avoir exigé. Les variables sont continues, et le plan est pourtant fait de tonnes entières ; sur les trente problèmes tirés au hasard, les trente plans le sont aussi. Ce n'est pas un hasard : quand les offres et les demandes sont entières, tous les sommets du polygone d'un problème de transport ont des coordonnées entières. La raison tient à la forme des contraintes, où chaque variable figure exactement deux fois, avec le coefficient 1, une fois dans l'offre de sa carrière et une fois dans la demande de son chantier ; la démonstration sort du cadre de ce chapitre. Le simplexe, qui va de sommet en sommet, rend donc un plan entier. Les lots de poulies et de bielles n'ont pas cette forme, et leur sommet optimal est (1,5 ; 3,75).
La propriété vaut aussi pour l'affectation, un transport où chaque offre et chaque demande vaut 1 : affecter des opérateurs à des postes se résout sans exiger l'entier, et le chapitre 4 le constate sur les opérateurs de Valdrome. Elle ne vaut plus dès qu'une contrainte d'une autre forme s'ajoute au modèle, une capacité de camion par exemple : il faut alors exiger l'entier.
Prendre en main le solveur
Six exercices courts, dans l'ordre : chacun ajoute une écriture de PuLP à la précédente. Les vérifications disent quel cas échoue, ce qui était attendu, et l'erreur probable quand elle est connue. Le premier exercice vérifié charge PuLP, en deux secondes environ ; les suivants vont vite.
Un premier modèle à maximiser
Écrire marge_optimale(marge_poulie, marge_bielle), qui renvoie, arrondie au centime, la meilleure marge des lots quand un lot de poulies rapporte marge_poulie euros et un lot de bielles marge_bielle euros. Les heures ne changent pas : 5 de tournage et 3 de fraisage par lot de poulies, 2 et 2 par lot de bielles, 15 heures de tournage et 12 de fraisage en tout.
Le problème à maximiser est créé ; il reste les deux variables, positives, l'objectif et les deux contraintes.
Écrire la fonction, puis « Vérifier » : 4 vérifications sur des cas qu'on sait faire de tête.
Une contrainte « au moins »
Deux machines fabriquent deux pièces. Une heure de la machine M1 coûte 50 € et donne 3 pièces A et 1 pièce B ; une heure de la machine M2 coûte 40 € et donne 1 pièce A et 2 pièces B. Les heures se fractionnent.
Écrire cout_minimal(pieces_a, pieces_b), qui renvoie, arrondi au centime, le coût le plus bas pour obtenir au moins pieces_a pièces A et au moins pieces_b pièces B.
Écrire la fonction, puis « Vérifier » : 4 vérifications sur des cas qu'on sait faire de tête.
Une égalité et des bornes
Trois équipes se partagent exactement heures heures de presse dans la semaine. Une heure coûte 40 € avec l'équipe du matin, 30 € avec celle de l'après-midi, 50 € avec celle de nuit. Le matin dispose d'au plus 20 heures, l'après-midi d'au plus 15 ; l'équipe de nuit, qui doit garder la main, en fait au moins 5, et au plus 30.
Écrire cout_de_la_semaine(heures), qui renvoie, arrondi au centime, le coût le plus bas. Les bornes d'une variable se donnent à sa création : LpVariable("nuit", lowBound=5, upBound=30).
Écrire la fonction, puis « Vérifier » : 4 vérifications sur des cas qu'on sait faire de tête.
Lire le statut
Écrire statut_des_lots(contraintes), qui maximise la marge des lots, 520 x + 240 y avec x et y positifs, sous les contraintes données, et renvoie le statut de la résolution en clair : "Optimal", "Infeasible" (impossible) ou "Unbounded" (non borné).
Chaque contrainte est une liste [a, b, sens, c], qui se lit a x + b y ≤ c quand sens vaut "<=", et a x + b y ≥ c quand il vaut ">=".
Écrire la fonction, puis « Vérifier » : 5 vérifications sur des cas qu'on sait faire de tête.
Lire le prix d'une ressource
Écrire prix_des_heures(tournage, fraisage), qui résout les lots (520 € et 240 € de marge) avec tournage heures de tournage et fraisage heures de fraisage, et renvoie la liste [prix du tournage, prix du fraisage] : ce que rapporterait une heure de plus de chaque atelier, arrondi au centime.
Une contrainte nommée se retrouve, après la résolution, dans prob.constraints["tournage"], et son prix dans .pi.
Écrire la fonction, puis « Vérifier » : 4 vérifications sur des cas qu'on sait faire de tête.
Le transport
Écrire cout_du_transport(offres, demandes, couts), qui renvoie, arrondi au centime, le coût minimal d'un plan de transport : la carrière i livre au plus offres[i] tonnes, le chantier j reçoit exactement demandes[j] tonnes, et une tonne de i à j coûte couts[i][j] euros.
Une variable par route, dans un dictionnaire q[i, j] construit en compréhension, pour i dans carrieres et j dans chantiers.
Écrire la fonction, puis « Vérifier » : 4 vérifications sur des cas qu'on sait faire de tête.
Les erreurs d'un débutant
Exercices type
Écrire sous forme standard le problème : minimiser sous , , . Puis l'écrire pour linprog.
La forme standard veut des égalités. La contrainte « ≥ » reçoit une variable d'excédent, retranchée : ; la contrainte « ≤ » une variable d'écart, ajoutée : ; toutes les variables sont positives, . Pour la forme canonique, qui maximise, l'objectif devient .
linprog minimise déjà, mais n'accepte que des « ≤ » : la première contrainte s'y écrit en changeant tous ses signes, . D'où c = [3, 2], A_ub = [[-1, -1], [1, -1]] et b_ub = [-4, 2]. L'optimum est en (0 ; 4), pour 8.
Le simplexe a fait entrer d'abord, et il est au sommet (0 ; 6), pour 1 440 €. La ligne de la marge vaut 160 sous et −120 sous . Que fait-il ensuite, et où arrive-t-il ?
Le coefficient 160 sous est positif : entre. Au sommet (0 ; 6), le fraisage est épuisé : un lot de poulies prend 3 heures de fraisage, qu'il faut retirer aux bielles, un lot et demi, et rapporte €. Les deux variables de base baissent quand monte : s'annule à , et le reste de tournage, , à . Le plus petit rapport est 1,5 : sort, et le plan arrive en (1,5 ; 3,75), pour 1 680 €, le même sommet que par l'autre chemin. Plus aucun coefficient n'y est positif.
Un fournisseur propose 6 heures de fraisage de plus, à 35 € l'heure. Que répondre ?
Une heure de fraisage vaut 40 €, mais seulement de 9 à 15 heures. Au-delà de 15, le plan ne fabrique plus que des bielles, 7,5 lots, que le tournage arrête ; il reste du fraisage, et une heure de plus ne rapporte rien. Les 3 premières heures font monter la marge de 120 €, pour 105 € payés : 15 € de gain. Les 3 suivantes coûtent 105 € et ne rapportent rien. Acheter les 6 heures ferait perdre 90 € ; en acheter 3 en fait gagner 15. Le prix dual dit à quel tarif acheter, et sa plage dit combien.
Pourquoi le plan de transport est-il fait de tonnes entières, sans qu'on l'ait exigé, alors que les lots s'arrêtent à 3,75 lots de bielles ?
Parce que le simplexe rend un sommet, et que les sommets d'un problème de transport sont entiers quand les offres et les demandes le sont. C'est une propriété de la forme de ses contraintes : chaque variable y figure deux fois, avec le coefficient 1, dans l'offre de sa carrière et dans la demande de son chantier. Les contraintes des lots, avec leurs coefficients 5, 2, 3 et 2, n'ont pas cette forme, et leur sommet optimal a une coordonnée fractionnaire. La propriété se perd dès qu'une contrainte d'une autre forme s'ajoute au transport : il faut alors exiger l'entier.
Le simplexe doit-il visiter tous les sommets pour prouver que le sien est optimal ?
Non. Il s'arrête quand aucun coefficient de la ligne de la marge n'est positif, c'est-à-dire quand aucun sommet voisin ne fait mieux, et la convexité du polygone garantit qu'alors aucun sommet, voisin ou non, ne fait mieux. Sur les lots, il prouve l'optimum de (1,5 ; 3,75) sans avoir visité (0 ; 6). Le pire cas, celui de Klee et Minty, l'oblige à les visiter tous, mais par le chemin qu'il emprunte, pas pour vérifier.
La méthode
- Nommer les variables de décision, et dire ce que chacune compte, avec son unité.
- Écrire l'objectif et chaque contrainte comme des sommes de termes proportionnels aux variables : une ressource limitée en « ≤ », un besoin en « ≥ », une quantité imposée en « = ». Linéariser un quotient en multipliant par le total, s'il est positif ; refuser un produit de variables.
- Donner au solveur la forme qu'il attend : la forme standard, avec une variable d'écart par contrainte, pour le simplexe à la main ; des matrices à minimiser pour
linprog; le modèle tel qu'on le dit pour PuLP, qui fait les conversions. - À deux variables, chercher l'optimum parmi les sommets du polygone ; au-delà, confier le calcul au solveur, qui va de sommet en sommet ou traverse le polygone par l'intérieur.
- Lire le statut avant la solution : « impossible » dit que la demande se contredit, « non borné » trahit presque toujours une contrainte oubliée ou écrite à l'envers.
- Lire la solution : les contraintes saturées fixent le plan, une ressource non saturée a un prix nul, une unité de plus d'une ressource saturée vaut son prix, dans une plage qu'il faut connaître avant d'acheter.
- Si les variables doivent être entières, ne pas arrondir : résoudre en nombres entiers, avec
cat="Integer", et garder l'optimum continu comme borne. Un problème de transport aux données entières fait exception : son optimum continu est déjà entier.
Synthèse
- Un programme linéaire maximise ou minimise une somme de termes proportionnels aux variables, sous des contraintes du même type, avec des variables réelles. Un produit de variables, un quotient, un coût fixe ou une condition n'en sont pas ; un quotient d'un total positif se linéarise.
- La forme canonique maximise sous des « ≤ » ; la forme standard n'a que des égalités, avec une variable d'écart par contrainte, ce qui reste de la ressource. Une contrainte est saturée quand son écart est nul. Les conversions ne changent que l'écriture :
linprogen exige une, PuLP les fait seul. - Le domaine réalisable est un polygone, convexe, et l'optimum, s'il existe, est atteint en un sommet. Énumérer les sommets marche à deux variables, et devient impossible quand le nombre de variables grandit.
- Le simplexe va de sommet en sommet : l'entrante a un coefficient positif dans la ligne de la marge, la sortante le plus petit rapport, le pivot réécrit le tableau, et l'arrêt vient quand plus aucun coefficient n'est positif. Sur les lots : (0 ; 0), (3 ; 0), puis (1,5 ; 3,75), pour 1 680 €. La règle d'entrée change le chemin, pas l'arrivée.
- Les points intérieurs traversent le polygone et sont polynomiaux ; HiGHS choisit entre eux et un simplexe selon le problème. Le simplexe est exponentiel dans le pire cas, pivots sur le cube de Klee et Minty, et rapide en pratique.
- Interpréter une solution, c'est lire le statut, les contraintes saturées, les écarts, et les prix duaux : 80 € l'heure de tournage, valable de 12 à 20 heures, 40 € l'heure de fraisage, de 9 à 15 heures. Un problème peut être impossible ou non borné, et le solveur le dit.
- Le problème de transport se modélise par une variable par route, une contrainte par offre et une par demande ; aux données entières, son optimum continu est entier : 1 160 € pour les carrières, en tonnes entières.
- L'optimum continu est une borne de l'optimum entier : 1 680 € pour les lots, contre 1 560 € en lots entiers. Arrondir le sommet continu ne donne pas l'optimum entier.
Et ensuite
Le chapitre suivant fait un outil de la borne que donne le continu : elle dit ce qu'une solution entière peut encore espérer, et un solveur en nombres entiers s'en sert pour résoudre exactement les petits cas, par séparation et évaluation. Le chapitre 5 sort du cadre linéaire, pour les contraintes et les coûts qui ne s'y écrivent pas.
L'atelier de programmation linéaire met ce chapitre en pratique chez Da Lambda, une pizzeria : un modèle à poser depuis une demande écrite, une proportion à linéariser, un simplexe qui commence par un sommet dégénéré, un mélange à composer, des commandes en tout ou rien. Le thème d'exercices La programmation linéaire reprend la lecture d'une solution, le calcul d'un prix, un pivot à la main, un transport et un modèle à corriger, sur d'autres situations. La méthode graphique est détaillée dans le chapitre optimiser sous contraintes du parcours Mathématiques appliquées.
Chapitre révisé en octobre 2026 · Une erreur dans ce chapitre ?
Mettre en pratique
Lire la sortie d'un solveur, retrouver le prix d'une ressource, dérouler un pivot du simplexe, livrer au moindre coût malgré des routes coupées, et corriger un modèle qui ne produit rien.
- Lire la sortie du solveurNiveau 2
- Retrouver le prix d'une ressourceNiveau 3
- Un pivot à la mainNiveau 3
- Livrer au moindre coût, malgré les routes coupéesNiveau 3
- Débogage : le plan qui ne produit rienNiveau 3