Покажи
# %pip install plotly --quiet
from plotly import graph_objects as go# %pip install plotly --quiet
from plotly import graph_objects as goБутиково кафене предлага два вида еспресо: Супер еспресо и Делукс еспресо. За приготвянето на един килограм от първия вид еспресо са необходими по равни части арабика и робуста, а рецептата за Делукс предвижда смес от арабика и робуста в пропорция 1 към 3. Доставчиците са готови да осигурят 120 кг арабика и 160 кг. робуста. Заведението знае, че няма да може да продаде повече от 150 кг. Делукс еспресо. От всеки продаден килограм Супер еспресо заведението печели 40 EUR, докато печалбата от килограм Делукс възлиза на 50 EUR.
Колко от двата типа еспресо ще препоръчате на кафенето да произведе, за да максимизира печалбата си?
Целеви променливи:
\begin{align*} & x: \text{ Супер еспресо (кг.)}\\ & y: \text{ Делукс еспресо (кг.)} \end{align*}
\max z = 40 x + 50 y \text{ (целева функция)}
\begin{align*} 0.5 x + 0.25 y & \leq 120 \text{ (арабика)}\\ 0.5 x + 0.75 y & \leq 160 \text{ (робуста)} \\ 0 \cdot x + y & \leq 150 \text{ (търсене Делукс)}\\ x & \geq 0 \\ y & \geq 0 \end{align*}
Както и в предишната задача ще изобразим графично допустимото множество, като начертаем правите, към всяко от петте неравенства:
\begin{align} 0.5 x & + 0.25 y & = & 120 \\ 0.5 x & + 0.75 y & = & 160 \\ 0 \cdot x & + y & = & 150 \end{align}
Първо ще пресметнем пресечните точки на трите прави с осите x и y?
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import display
fig, ax = plt.subplots()
ax.plot([0, 240], [480, 0], label=r"$0.5 x + 0.25 y = 120$ (арабика)")
ax.plot([0, 320], [213.33, 0], label=r"$0.5 x + 0.75 y = 160$ (робуста)")
ax.plot([0, 400], [150, 150], label=r"$y = 150$ (търсене на Делукс)")
ax.set_xlim((0, 400))
ax.set_ylim((0, 500))
ax.set_xlabel(r'$x$ (Супер, кг.)')
ax.set_ylabel(r'$y$ (Делукс, кг.)')
ax.legend(loc=0)
vertices = [
[0, 0],
[0, 150],
[95, 150],
[200, 80],
[240, 0]
]
vertices_x = [v[0] for v in vertices]
vertices_y = [v[1] for v in vertices]
for v in vertices:
ax.annotate(
f"({v[0]}, {v[1]})",
(v[0], v[1]),
textcoords="offset points",
xytext=(0, 5)
)
ax.fill(vertices_x, vertices_y, color='grey', alpha=0.3)
display(fig)
За да определим оптималния план графично ще начертаем прави, съответстващи на различни нива на печалба.
Целевата функция зависи от две променливи, което означава, че за да я изобразим графично ще трябва да използваме трето измерение. Фигура 2.3 показва графиката на целевата функция в тримерно пространство, както и нейната проекция в двумерно пространство.
# Create a 3d scatter plot for the vertices of the feasible region with plotly
fig = go.Figure(data=[go.Scatter3d(
x=vertices_x,
y=vertices_y,
z=[0] * len(vertices_x),
mode='markers+text',
marker=dict(size=2, color='red'),
text=[f"({v[0]}, {v[1]}, 0)" for v in vertices],
textposition="top center"
)])
# Draw lines connecting the vertices
for i in range(len(vertices)):
x_line = [vertices[i][0], vertices[(i + 1) % len(vertices)][0]]
y_line = [vertices[i][1], vertices[(i + 1) % len(vertices)][1]]
z_line = [0, 0]
fig.add_trace(go.Scatter3d(
x=x_line,
y=y_line,
z=z_line,
mode='lines',
line=dict(color='blue', width=2)
))
fig.update_layout(
scene=dict(
xaxis_title=r"Super (kg)",
yaxis_title=r"Deluxe (kg)",
zaxis_title=r"Profit (Euro)",
zaxis=dict(range=[0, 12000]),
aspectmode='cube'
),
showlegend=False
)
# Draw the plane of the objective function
X1 = np.linspace(0, 250, 20)
X2 = np.linspace(0, 250, 20)
X1, X2 = np.meshgrid(X1, X2)
Z = 40 * X1 + 50 * X2
fig.add_trace(go.Surface(
x=X1,
y=X2,
z=Z,
opacity=0.5,
colorscale='Viridis'
))
# Draw a plane at level z = 6000
z_plane_1 = 6000
fig.add_trace(go.Surface(
x=X1,
y=X2,
z=np.full(X1.shape, z_plane_1),
opacity=0.2,
colorscale='Viridis',
showscale=False
))
# Draw a line at the intersection of the objective plane and the z = 10000 plane
intersection_x = np.linspace(0, 250, 20)
intersection_y_1 = (z_plane_1 - 40 * intersection_x) / 50
fig.add_trace(go.Scatter3d(
x=intersection_x,
y=intersection_y_1,
z=[z_plane_1] * len(intersection_x),
mode='lines',
opacity=0.4,
line=dict(color='red', width=3),
name='Intersection'
))
# Draw the intersection line on the xy plane
fig.add_trace(go.Scatter3d(
x=intersection_x,
y=intersection_y_1,
z=[0] * len(intersection_x),
mode='lines',
opacity=0.4,
line=dict(color='red', width=2),
name='Intersection'
))
fig.update_layout(
title="Profit Function, Feasible Region and Isolines at 6000 and 12000 Euro",
scene=dict(
xaxis_title=r"Super (kg)",
yaxis_title=r"Deluxe (kg)",
zaxis_title=r"Profit (Euro)",
zaxis=dict(range=[0, 13000]),
yaxis=dict(range=[0, 250]),
xaxis=dict(range=[0, 250]),
aspectmode='cube'
),
showlegend=False
)
# Draw a plane at the optimal solution z = 12000
z_plane_2 = 12000
fig.add_trace(go.Surface(
x=X1,
y=X2,
z=np.full(X1.shape, z_plane_2),
opacity=0.2,
colorscale='Viridis',
showscale=False
))
intersection_x = np.linspace(0, 250, 20)
intersection_y = (z_plane_2 - 40 * intersection_x) / 50
# Draw the intersection line on the xy plane
fig.add_trace(go.Scatter3d(
x=intersection_x,
y=intersection_y,
z=[z_plane_2] * len(intersection_x),
mode='lines',
opacity=0.4,
line=dict(color='red', width=3),
name='Intersection'
))
fig.add_trace(go.Scatter3d(
x=intersection_x,
y=intersection_y,
z=[0] * len(intersection_x),
mode='lines',
opacity=0.4,
line=dict(color='red', width=3),
name='Intersection'
))
fig.show()Нека да начертаем тези прави на графиката на допустимото множество. Фигура 2.4 показва графиката на допустимото множество и правите на печалбата от 3000 EUR, 50000 EUR и 12000 EUR
fig, ax = plt.subplots()
ax.fill(vertices_x, vertices_y, color='grey', alpha=0.3)
for v in vertices:
ax.annotate(
f"({v[0]}, {v[1]})",
(v[0], v[1]),
textcoords="offset points",
xytext=(0, 5)
)
ax.plot([0, 3000 / 40], [3000 / 50, 0], label=r'$40 x + 50 y = 3000$')
ax.plot([0, 5000 / 40], [5000 / 50, 0], label=r'$40 x + 50 y = 5000$')
ax.plot([0, 12000 / 40], [12000 / 50, 0], label=r'$40 x + 50 y = 12000$')
ax.quiver(0, 0, 40, 50, angles='xy', scale_units='xy', scale=1, color='grey', label='Нормален вектор')
ax.set_xlim((0, 260))
ax.set_ylim((0, 260))
ax.legend(loc=0)
plt.show()
Във Фигура 2.4 е показан векторът (40, 50)^T, който е перпендикулярен на всички прави на печалбата (които са успоредни една спрямо друга). Когато задачата се решава графично на ръка, този вектор е удобен, защото показва посоката на увеличение на печалбата (в нашия случай тя расте по-бързо в посока y, отколкото в посока x, защото коефициентът на y (50) е по-голям от коефициента на x (40)).
Защо този вектор е перпендикулярен на правите на печалба? Един лесен начин да видим това е да изразим правата в параметричен вид. За по-лесно нека фиксираме някакво ниво на печалба, например 5000 EUR. Всички комбинации от супер и делукс, които постигат точно това ниво на печалба лежат на права, дадена от следното уравнение:
40x + 50y = 5000
Това е т.н. координатно представяне на правата. Параметричното представяне използва една (произволна) начална точка и вектор, който показва посоката й. За да намерим началната точка, ще намерим пресечните точки на правата с двете оси. Тази права се пресича с оста x при x = 125 и с оста y при y = 100, т.е. две точки на правата са (125, 0) и (0, 100).
От тези две точки можем да изведем параметричното представяне на правата (за t \in \mathbb{R}) като вземем разликата между двете точки (вектори).
\begin{pmatrix} x \\ y \end{pmatrix} = \begin{pmatrix} 125 \\ 0 \end{pmatrix} + t \left[ \begin{pmatrix} 0 \\ 100 \end{pmatrix}- \begin{pmatrix}125 \\ 0 \end{pmatrix} \right] = \begin{pmatrix} 125 \\ 0 \end{pmatrix} + t \begin{pmatrix}-125 \\ 100 \end{pmatrix}
Два вектора са перпердикулярни (ортогонални), ако скаларното им произведение е равно на 0. Скаларното произведение на два вектора е равно на сумата от произведенията на съответните им координати. Така получаваме
\begin{pmatrix} -125 & 100 \end{pmatrix} \begin{pmatrix} 40 \\ 50 \end{pmatrix} = (-125) \cdot 40 + 100 \cdot 50 = -5000 + 5000 = 0
Упражнение 2.1 (Oще една задача от същия вид) Предприятие произвежда два вида боя: екстериорна (за външни повърхности) и интериорна (за вътрешни помещения). Производството двете бои изисква три суровини: разтворител, багрило и смола. За производство на един тон екстериорна боя са нужни 6 тона разтворител, един тон багрило и 2 тона смола. За производство на един тон интериорна боя са нужни 4 тона разтворител, 2 тона багрило и 7 тона смола. На разположение са следните количества суровини: 24 тона разтворител, 6 тона багрило и 4 тона смола. Колко тона от всяка боя да произведе предприятието, за да постигне максимална печалба? От продажбата на един тон екстериорна боя предприятието печели 5 000 EUR, а от продажбата на един тон интериорна боя - 4 000 EUR
| Екстериорна боя | Интериорна боя | Дневна наличност на ресурс | |
|---|---|---|---|
| Разтворител | 6 | 4 | 24 |
| Багрило | 1 | 2 | 6 |
| Смола | 2 | 7 | 4 |
| Печалба (1 000 EUR/тон) | 5 | 4 |
Съставете математически модел, който да намери оптималния план за производство на боите.
Целеви променливи: \begin{align*} & x: \text{ Екстериорна боя (тонове)}\\ & y: \text{ Интериорна боя (тонове)} \end{align*}
\max z = 5 x + 4 y \text{ (целева функция)}
Ограничения:
\begin{align*} 6x + 4y & \leq 24 \text{ (разтворител)}\\ x + 2y & \leq 6 \text{ (багрило)}\\ 2x + 7y & \leq 4 \text{ (смола)}\\ x & \geq 0 \\ y & \geq 0 \end{align*}
import gurobipy as gp
from gurobipy import GRB
# Create a new model
m = gp.Model("Paint")
m.Params.LogToConsole = 0
# Create variables
p_exterior = m.addVar(lb=0, name="exterior")
p_interior = m.addVar(lb=0, name="interior")
m.setObjective(5 * p_exterior + 4 * p_interior, GRB.MAXIMIZE)
m.addConstr(6 * p_exterior + 4 * p_interior <= 24, "Solvent")
m.addConstr(1 * p_exterior + 2 * p_interior <= 6, "Dye")
m.addConstr(2 * p_exterior + 7 * p_interior <= 4, "Resin")
m.optimize()
print(f"Optimal solution: p_exterior = {p_exterior.x}, p_interior = {p_interior.x}")Restricted license - for non-production use only - expires 2027-11-29
Set parameter LogToConsole to value 0
Optimal solution: p_exterior = 2.0, p_interior = 0.0