# | fig-cap: "Целева функция и допустимо множество"
# | code-fold: true
import numpy as np
import plotly.graph_objects as go
# Допустими върхове за задачата:
# x1 + 2x2 <= 10
# x1 + x2 <= 8
# x1, x2 >= 0
vertices = [(0, 0), (8, 0), (6, 2), (0, 5)]
vertices_x = [point[0] for point in vertices]
vertices_y = [point[1] for point in vertices]
vertices_z = [0] * len(vertices)
fig = go.Figure()
# Върхове на допустимото множество в равнината z = 0
fig.add_trace(
go.Scatter3d(
x=vertices_x,
y=vertices_y,
z=vertices_z,
mode="markers+text",
marker=dict(size=4, color="red"),
text=[f"({x}, {y}, 0)" for x, y in vertices],
textposition="top center",
name="Върхове",
)
)
# Ребра на допустимото множество
for index in range(len(vertices)):
next_index = (index + 1) % len(vertices)
fig.add_trace(
go.Scatter3d(
x=[vertices[index][0], vertices[next_index][0]],
y=[vertices[index][1], vertices[next_index][1]],
z=[0, 0],
mode="lines",
line=dict(color="blue", width=4),
showlegend=False,
)
)
# Нормален вектор на целевата функция z = x1 + 1.5x2.
# В x-y равнината това е градиентът (1, 1.5).
normal_start = np.array([0.8, 0.6, 0.0])
normal_vector = np.array([1.0, 1.5, 0.0])
normal_scale = 1.5
normal_end = normal_start + normal_scale * normal_vector
fig.add_trace(
go.Scatter3d(
x=[normal_start[0], normal_end[0]],
y=[normal_start[1], normal_end[1]],
z=[0, 0],
mode="lines+text",
line=dict(color="orange", width=8),
text=[None, "n = (1, 1.5)"],
textposition="top center",
name="Нормален вектор",
)
)
fig.add_trace(
go.Cone(
x=[normal_end[0]],
y=[normal_end[1]],
z=[0],
u=[normal_vector[0]],
v=[normal_vector[1]],
w=[0],
anchor="tip",
sizemode="absolute",
sizeref=0.45,
colorscale=[[0, "orange"], [1, "orange"]],
showscale=False,
name="Нормален вектор",
)
)
# Мрежа за целевата функция z = x1 + 1.5x2
x1 = np.linspace(0, 8, 40)
x2 = np.linspace(0, 8, 40)
X1, X2 = np.meshgrid(x1, x2)
Z = X1 + 1.5 * X2
fig.add_trace(
go.Surface(
x=X1,
y=X2,
z=Z,
opacity=0.55,
colorscale="Viridis",
name="z = x1 + 1.5x2",
showscale=False,
)
)
# Ниво под оптимума: z = 6
z_plane_1 = 6
intersection_x_1 = np.linspace(0, z_plane_1, 200)
intersection_y_1 = (z_plane_1 - intersection_x_1) / 1.5
mask_1 = (intersection_y_1 >= 0) & (intersection_y_1 <= 8)
intersection_x_1 = intersection_x_1[mask_1]
intersection_y_1 = intersection_y_1[mask_1]
fig.add_trace(
go.Surface(
x=X1,
y=X2,
z=np.full(X1.shape, z_plane_1),
opacity=0.18,
colorscale="Blues",
showscale=False,
)
)
fig.add_trace(
go.Scatter3d(
x=intersection_x_1,
y=intersection_y_1,
z=[z_plane_1] * len(intersection_x_1),
mode="lines",
line=dict(color="crimson", width=5),
name="z = 6",
)
)
fig.add_trace(
go.Scatter3d(
x=intersection_x_1,
y=intersection_y_1,
z=[0] * len(intersection_x_1),
mode="lines",
line=dict(color="crimson", width=3, dash="dash"),
showlegend=False,
)
)
# Оптимално ниво: z = 9, достигнато в точка (6, 2)
z_plane_2 = 9
intersection_x_2 = np.linspace(0, z_plane_2, 200)
intersection_y_2 = (z_plane_2 - intersection_x_2) / 1.5
mask_2 = (intersection_y_2 >= 0) & (intersection_y_2 <= 8)
intersection_x_2 = intersection_x_2[mask_2]
intersection_y_2 = intersection_y_2[mask_2]
fig.add_trace(
go.Surface(
x=X1,
y=X2,
z=np.full(X1.shape, z_plane_2),
opacity=0.18,
colorscale="Greens",
showscale=False,
)
)
fig.add_trace(
go.Scatter3d(
x=intersection_x_2,
y=intersection_y_2,
z=[z_plane_2] * len(intersection_x_2),
mode="lines",
line=dict(color="darkgreen", width=6),
name="z = 9",
)
)
fig.add_trace(
go.Scatter3d(
x=intersection_x_2,
y=intersection_y_2,
z=[0] * len(intersection_x_2),
mode="lines",
line=dict(color="darkgreen", width=3, dash="dash"),
showlegend=False,
)
)
# Оптимална точка
fig.add_trace(
go.Scatter3d(
x=[6],
y=[2],
z=[9],
mode="markers+text",
marker=dict(size=6, color="black"),
text=["Оптимум (6, 2, 9)"],
textposition="top center",
name="Оптимум",
)
)
fig.update_layout(
title="Целева функция и допустимо множество",
scene=dict(
xaxis_title="Козунак 1",
yaxis_title="Козунак 2",
zaxis_title="Печалба",
xaxis=dict(range=[0, 8]),
yaxis=dict(range=[0, 8]),
zaxis=dict(range=[0, 12]),
aspectmode="cube",
),
showlegend=False,
)
fig.show()