import numpy as np
import scipy.optimize as so
import matplotlib.pyplot as plt
from rich import print
from rich.panel import Panel
import sympy as sym
import pandas as pd
from math import pi
1 Introduction au problème I
L’entreprise Car-Nivor souhaite se lancer dans la production de voitures et a déjà imaginé plusieurs modèles. Elle commence par analyser le marché avec les modèles Tiger (A) et Piranha (B).
Voici les informations qui ressortent de cette analyse :
- Le modèle A lui apporte un bénéfice de 3 000 € par unité vendue, tandis que le modèle B lui apporte un bénéfice de 2 000 € par unité vendue.
- La production d’une voiture de modèle A prend 2 heures tandis que celle d’une voiture de modèle B prend seulement 1 heure1.
- Pour des raisons d’absorption de marché, Car-Nivor ne peut pas vendre plus de 100 modèles A, 100 modèles B et 150 voitures (A+B) au total par mois.
- Enfin, l’usine de production n’est disponible que 200 heures par mois.
2 Modélisation du problème I
Avant de se lancer tête baissée dans l’utilisation d’un modèle de Boosting, réfléchissons un peu et posons-nous quelques questions.
- Quelles quantités peut-on faire varier ?
Les quantités de modèles A et B. - Que cherche-t-on à optimiser ?
Le profit de Car-Nivor. - Quelles sont les contraintes du problème ?
Les contraintes de production et les contraintes de marché
On voit en fait qu’on peut formuler cet énoncé sous la forme d’un problème d’optimisation linéaire sous contraintes !
Plus précisément, on considère les notations suivantes :
est la quantité vendue de modèle A, est la quantité vendue de modèle B, est le bénéfice en k€ par unité vendue de modèle A, est le bénéfice en k€ par unité vendue de modèle B, est le profit de Car-nivor, qu’on souhaite maximiser – on parle de fonction objectif, correspond à l’ensemble des contraintes du problème.
- La première contrainte indique qu’on ne peut pas produire de quantité négative de voiture,
- La seconde contrainte indique qu’on ne peut pas vendre plus de 100 modèles de type A,
- La troisième contrainte indique qu’on ne peut pas vendre plus de 100 modèles de type B,
- La quatrième contrainte porte sur le temps de disponibilité de l’usine (200h),
- La dernière contrainte indique qu’il est possible de produire au maximum 150 voitures de Type A ou B, et pas plus.
3 Résolution du problème I
3.1 Import des librairies et configuration
"figure.dpi"] = 600 plt.rcParams[
3.2 Représentation graphique du problème
On peut facilement représenter dans un graph en
l’ensemble des contraintes et proposer une solution graphique au problème puisqu’on dispose uniquement de 2 variables.
= np.linspace(0, 150, 500)
x = 200 - 2 * x
y2 = 150 - x
y3 = (350 - 3 * x) / 2 z
Code du graphique matplotlib
plt.hlines(=100, xmin=0, xmax=150, linewidth=2, color="r", label="$x_2 ≤ 100$", zorder=1
y
)
plt.vlines(=100, ymin=0, ymax=150, linewidth=2, color="m", label="$x_1 ≤ 100$", zorder=1
x
)="$2x_1 + x_2 \leq 200 $", zorder=1)
plt.plot(x, y2, label="$x_1 + x_2 \leq 150$", zorder=1)
plt.plot(x, y3, label="$z = 350k$", zorder=1)
plt.plot(x, z, label
50, 100, color="black", zorder=2)
plt.scatter(0, 49.9], [100, 100], [0, 0], hatch="//", color="blue", alpha=0.2)
plt.fill_between([
plt.fill_between(
x,min([y2, y3], axis=0),
np.=y2 < 100,
where="blue",
color=0.2,
alpha="//",
hatch=True,
interpolate="$admissible$",
label
)
"$x_1$", fontsize=15)
plt.xlabel("$x_2$", fontsize=15)
plt.ylabel(0, 125])
plt.ylim([0, 125])
plt.xlim([
"Ensemble des solutions admissibles")
plt.title(=(1.05, 1), loc=2, borderaxespad=0.0) plt.legend(bbox_to_anchor
3.3 Mise sous forme normale du problème
On a 4 équations dans le système des contraintes donc le rang est égal à 4.
Les variables positives sont appelées les variables de base :
L’idée est ensuite d’exprimer les variables de base en fonction des variables hors base (
3.4 Résolution avec scipy
Le submodule
scipy.optimize
fournit un ensemble de fonctions pour minimiser (ou maximiser) des fonctions objectif, possiblement sujettes à certaines contraintes.
Il faut d’abord écrire les matrices de travail avec numpy
.
= np.array([3, 2])
cost_1 = np.array([[1, 0], [0, 1], [2, 1], [1, 1]])
lhs_1 = np.array([100, 100, 200, 150]) rhs_1
cost_1
correspond à la matrice de coût, i.e. les coefficients et ,lhs_1
2 correspond au premier membre de la matrice des contraintes,rhs_1
3 correspond au second membre de la matrice des contraintes.
Prévisualisons-les avec sympy
.
display(sym.Matrix(cost_1))
display(sym.Matrix(lhs_1)) display(sym.Matrix(rhs_1))
Pour résoudre le problème, on peut utiliser la fonction linprog
du submodule scipy.optimize
, dont l’algorithme effectue par défaut une minimisation.
Comme nous souhaitons plutôt maximiser la fonction objectif, il faut inverser le signe de la fonction de coût.
= so.linprog(-cost_1, A_ub=lhs_1, b_ub=rhs_1, method="highs") result_1
Profitons-en aussi pour créer les fonctions :
total_profit
qui retourne le profit total optimal,details
qui affiche le profit ainsi que les quantités de voitures vendues.
Code des fonctions
def total_profit(result: so._optimize.OptimizeResult, cost: np.ndarray) -> np.float64:
"""Retourne le profit total en € lié à la vente de voitures."""
return np.sum(result["x"] * cost * 1000)
def details(result: so._optimize.OptimizeResult, cost: np.ndarray, types: list) -> str:
"""
Affiche un message contenant le profit optimal réalisé
ainsi que le nombre et le type de voiture vendues.
"""
= int(total_profit(result, cost))
profit = f"""
message Le profit optimal réalisé par [i]Car-Nivor[/i] est de [u]{profit}[/u] € par mois.
=> Pour :
"""
for voiture, type_voiture in zip(result["x"], types):
+= f"- {voiture.astype(int)} voitures vendues de [red][b]Type {type_voiture}[/b][/red] \n"
message return Panel(message)
Il suffit maintenant d’appeler la fonction details
pour obtenir les résultats.
"A", "B"]) details(result_1, cost_1, [
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ Le profit optimal réalisé par Car-Nivor est de 350000 € par mois. │ │ │ │ => Pour : │ │ │ │ - 50 voitures vendues de Type A │ │ - 100 voitures vendues de Type B │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
3.5 Effondrement de la demande en modèle B
A cause de constructeurs chinois, Car-Nivor ne peut plus écouler qu’un maximum de 50 voitures par mois.
3.5.1 Graphique de la situation
Code du graphique matplotlib
=50, xmin=0, xmax=150, linewidth=2, color="r", label="$x_2 ≤ 50$", zorder=1)
plt.hlines(y
plt.vlines(=100, ymin=0, ymax=150, linewidth=2, color="m", label="$x_1 ≤ 100$", zorder=1
x
)
= (325 - 3 * x) / 2
z
="$2x_1 + x_2 \leq 200 $", zorder=1)
plt.plot(x, y2, label="$x_1 + x_2 \leq 150$", zorder=1)
plt.plot(x, y3, label="$z = 325k$", zorder=1)
plt.plot(x, z, label
75, 50, color="black", zorder=2)
plt.scatter(
plt.fill_between(0, 75],
[49.9, 49.9],
[0, 0],
[="//",
hatch="blue",
color=0.2,
alpha=True,
interpolate="$admissible$",
label
)
plt.fill_between(="//", color="blue", where=y2 < 50, alpha=0.2, interpolate=True
x, y2, hatch
)
"$x_1$", fontsize=15)
plt.xlabel("$x_2$", fontsize=15)
plt.ylabel(0, 125])
plt.ylim([0, 125])
plt.xlim(["Ensemble des solutions admissibles")
plt.title(=(1.05, 1), loc=2, borderaxespad=0.0) plt.legend(bbox_to_anchor
Pour une résolution numérique , il suffit simplement de changer le second membre de la matrice des contraintes.
= np.array([100, 50, 200, 150]) rhs_2
= so.linprog(-cost_1, A_ub=lhs_1, b_ub=rhs_2, method="highs") result_2
"A", "B"]) details(result_2, cost_1, [
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ Le profit optimal réalisé par Car-Nivor est de 325000 € par mois. │ │ │ │ => Pour : │ │ │ │ - 75 voitures vendues de Type A │ │ - 50 voitures vendues de Type B │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
On voit qu’un effondrement de la demande en voiture de Type B entraîne une surproduction de voitures de Type A, mais que cette surproduction ne permet pas d’obtenir le profit optimal avant l’effondrement de la demande de Type B.
3.6 Analyse détaillée du problème
On veut étudier graphiquement l’évolution du profit en fonction du nombre maximal de voitures vendues de type B.
Pour faire ceci, je vais utiliser une boucle
for
et faire varier le second membre de la contrainte pour.
= [x for x in range(0, 101)]
list_x2 = np.array([3, 2])
cost_2 = np.array([[1, 0], [0, 1], [2, 1], [1, 1]])
lhs_2
= list()
x1_vente = list()
x2_vente = list()
profit
for x2_max in list_x2:
= np.array([100, x2_max, 200, 150])
rhs_2 = so.linprog(-cost_2, A_ub=lhs_2, b_ub=rhs_2, method="highs")
max_bagnole "x"][0])
x1_vente.append(max_bagnole["x"][1])
x2_vente.append(max_bagnole[-max_bagnole["fun"] * 1000) profit.append(
Code du graphique matplotlib
plt.plot(list_x2, profit)"Valeur max de $x_2$")
plt.xlabel("Profit en €")
plt.ylabel(=325000, xmin=0, xmax=50, linewidth=2, linestyles="--", color="m")
plt.hlines(y=50, ymin=300000, ymax=325000, linewidth=2, linestyles="--", color="m")
plt.vlines(x
=350000, xmin=0, xmax=100, linewidth=2, linestyles="--", color="m")
plt.hlines(y=100, ymin=300000, ymax=350000, linewidth=2, linestyles="--", color="m")
plt.vlines(x
50, 325000, color="red", zorder=2, label="π2 = 325k")
plt.scatter(100, 350000, color="green", zorder=2, label="π1 = 350k")
plt.scatter(
300000, 351000])
plt.ylim([0, 105])
plt.xlim([
plt.title(f"Profit optimal en fonction de la valeur de $x_2$ compris entre [{list_x2[0]}:{list_x2[-1]}] "
)
=(1.05, 1), loc=2, borderaxespad=0.0) plt.legend(bbox_to_anchor
On remarque que le profit est croissant en fonction de la valeur maximale de
4 Introduction au problème II
Un manager propose de dessiner deux nouveaux modèles de luxe pour se distinguer des constructeurs chinois : le Lion (C) et le Shark (D). Les bénéfices de ces deux modèles sont de respectivement 5 000 et 10 000 €.
Néanmoins, la demande de ces modèles de luxe est assez limitée : 30 unités au total.
- La demande mensuelle totale de voiture est toujours de 150.
- La disponibilité de l’usine est toujours de 200 heures.
- Le modèle C prend au total 5 heures de construction tandis que le modèle D en prend 8.
5 Modélisation du problème II
On considère les notations suivantes :
sont les quantités vendues de modèle A, B, C, D, est le bénéfice en k€ par unité vendue de modèle A, B, C, D, est le profit de Car-nivor, qu’on souhaite maximiser – on parle de fonction objectif, correspond à l’ensemble des contraintes du problème.
6 Résolution du problème II
Nous ne pouvons plus représenter graphiquement la situation, la résolution va donc uniquement se poursuivre en utilisant scipy
.
6.1 Résolution avec scipy
On procède aux mêmes étapes que dans la section précédente en écrivant les matrices de travail avec numpy
.
= np.array([3, 2, 5, 10])
cost_3 = np.array(
lhs_3 1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [2, 1, 5, 8], [1, 1, 1, 1]]
[[
)= np.array([100, 100, 30, 30, 200, 150]) rhs_3
= so.linprog(-cost_3, A_ub=lhs_3, b_ub=rhs_3, method="highs") result_3
"A", "B", "C", "D"]) details(result_3, cost_3, [
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ Le profit optimal réalisé par Car-Nivor est de 350000 € par mois. │ │ │ │ => Pour : │ │ │ │ - 50 voitures vendues de Type A │ │ - 100 voitures vendues de Type B │ │ - 0 voitures vendues de Type C │ │ - 0 voitures vendues de Type D │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
6.2 La disponibilité de l’usine double
Dans le cadre du Plan de relance pour l’industrie, des aides conséquentes permettent à Car-Nivor de doubler le temps de disponibilité de l’usine pour atteindre 400 heures.
Pour résoudre le problème il suffit simplement de changer le second membre de la matrice des contraintes en remplaçant 200 par 400.
= np.array([100, 100, 30, 30, 400, 150]) rhs_4
= so.linprog(-cost_3, A_ub=lhs_3, b_ub=rhs_4, method="highs") result_4
"A", "B", "C", "D"]) details(result_4, cost_3, [
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ Le profit optimal réalisé par Car-Nivor est de 580000 € par mois. │ │ │ │ => Pour : │ │ │ │ - 40 voitures vendues de Type A │ │ - 80 voitures vendues de Type B │ │ - 0 voitures vendues de Type C │ │ - 30 voitures vendues de Type D │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Un doublement de la disponibilité horaire entraîne la production de voitures de luxe de type D, ainsi qu’une augmentation substantielle du profit de Car-Nivor.
6.3 Impact du temps de disponibilité de l’usine sur la stratégie de l’entreprise.
Etudions l’impact du temps de disponibilité en utilisant des tranches de 100h de disponibilité mensuelle.
= [100 * hours for hours in range(1, 11)]
list_time = np.array([3, 2, 5, 10])
cost_4 = np.array(
lhs_4 1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], [2, 1, 5, 8], [1, 1, 1, 1]]
[[
)
= list()
x1_vente = list()
x2_vente = list()
x3_vente = list()
x4_vente = list()
profit
for hours in list_time:
= np.array([100, 100, 30, 30, hours, 150])
rhs_4 = so.linprog(-cost_4, A_ub=lhs_4, b_ub=rhs_4, method="highs")
max_bagnole "x"][0])
x1_vente.append(max_bagnole["x"][1])
x2_vente.append(max_bagnole["x"][2])
x3_vente.append(max_bagnole["x"][3])
x4_vente.append(max_bagnole[-max_bagnole["fun"] * 1000) profit.append(
Code du graphique matplotlib
plt.plot(list_time, profit)600, profit[5], color="green", zorder=2, label="π seuil = 720k")
plt.scatter(
"Disponibilité horaire")
plt.xlabel("Profit en €")
plt.ylabel(180000, 775000])
plt.ylim([90, 800])
plt.xlim([
plt.title(f"Profit optimal en fonction de la disponibilité horaire de l'usine \n compris entre [{list_time[0]}:{list_time[-1]}] heures"
)
plt.hlines(=profit[5], xmin=0, xmax=600, linewidth=2, linestyles="--", color="m", zorder=1
y
)
plt.vlines(=600, ymin=0, ymax=profit[5], linewidth=2, linestyles="--", color="m", zorder=1
x
)=(1.05, 1), loc=2, borderaxespad=0.0) plt.legend(bbox_to_anchor
Graphiquement, on remarque qu’il y n’y a plus intérêt à augmenter la disponibilité horaire à partir de 600 heures puisqu’on satisfait la contrainte de la demande de voiture
On peut, en plus, représenter le nombre de voitures vendues selon leur type en faisant varier la disponibilité horaire.
Code du graphique matplotlib
="Type A")
plt.plot(list_time, x1_vente, label="Type B")
plt.plot(list_time, x2_vente, label="Type C")
plt.plot(list_time, x3_vente, label="Type D")
plt.plot(list_time, x4_vente, label"Disponibilité horaire")
plt.xlabel("Nombre de voitures vendues")
plt.ylabel(=(1.05, 1), loc=2, borderaxespad=0.0) plt.legend(bbox_to_anchor
Si initialement les voitures de type B sont favorisées, on voit que Car-Nivor en produit de moins en moins pour focaliser sa production sur les type A, et dès qu’elle le peut, elle commence à produire des voitures de Type D. Pour finir, elle choisit de compléter sa production avec des voitures de Type C.
7 Introduction au problème III
L’usine de production fait remarquer à la direction que les flux de matières premières sont tendus et demande de les prendre en compte pour faire des modèles plus réalistes. Pour ce faire, elle crée le tableau suivant :
Type A | Type B | Type C | Type D | Limite des stocks mensuels | |
---|---|---|---|---|---|
Fer | 2.0 | 1.0 | 4.0 | 2.0 | 125 |
Cuivre | 0.2 | 0.2 | 0.5 | 1.0 | 55 |
Aluminium | 1.0 | 1.0 | 2.0 | 2.0 | 300 |
Acier | 2.0 | 2.0 | 1.0 | 2.0 | 100 |
Caoutchouc | 1.0 | 1.0 | 1.5 | 0.8 | 60 |
Terres rares | 0.0 | 0.0 | 2.0 | 2.0 | 50 |
Verre | 1.0 | 1.0 | 1.0 | 1.2 | 100 |
Tissu | 2.0 | 2.0 | 1.0 | 1.0 | 220 |
Cuir | 0.0 | 0.0 | 1.0 | 1.0 | 30 |
🐱🚀 Patience, ça arrive…