ЛР-02: Учебные материалы и оборудование#
Worked example: civil 02#
Это полностью разобранный example. Он показывает образец постановки, решения и интерпретации транспортной модели.
1. Постановка кейса#
Здесь один маршрут сделан аварийно дорогим, чтобы увидеть реакцию оптимального плана.
Запасы#
Поставщик |
Объём |
|---|---|
Центр A |
25 |
Центр B |
45 |
Центр C |
35 |
Спрос#
Потребитель |
Объём |
|---|---|
Колледж 1 |
15 |
Колледж 2 |
30 |
Колледж 3 |
25 |
Колледж 4 |
35 |
Матрица затрат#
Откуда / Куда |
Колледж 1 |
Колледж 2 |
Колледж 3 |
Колледж 4 |
|---|---|---|---|---|
Центр A |
4 |
8 |
50 |
9 |
Центр B |
6 |
5 |
7 |
8 |
Центр C |
7 |
4 |
6 |
5 |
import numpy as np
import pandas as pd
from scipy.optimize import linprog
def balance_transport_problem(supplies, demands, costs, supplier_names, consumer_names):
supplies = supplies.astype(float).copy()
demands = demands.astype(float).copy()
costs = costs.astype(float).copy()
supplier_names = list(supplier_names)
consumer_names = list(consumer_names)
diff = supplies.sum() - demands.sum()
if diff > 0:
demands = np.append(demands, diff)
consumer_names.append('Фиктивный потребитель')
costs = np.column_stack([costs, np.zeros(len(supplies))])
elif diff < 0:
supplies = np.append(supplies, -diff)
supplier_names.append('Фиктивный поставщик')
costs = np.vstack([costs, np.zeros(len(demands))])
return supplies, demands, costs, supplier_names, consumer_names
def solve_transport_problem(supplies, demands, costs):
m, n = costs.shape
c = costs.flatten()
A_eq = []
b_eq = []
for i in range(m):
row = np.zeros(m * n)
row[i * n:(i + 1) * n] = 1
A_eq.append(row)
b_eq.append(supplies[i])
for j in range(n):
row = np.zeros(m * n)
row[j::n] = 1
A_eq.append(row)
b_eq.append(demands[j])
result = linprog(
c,
A_eq=np.array(A_eq),
b_eq=np.array(b_eq),
bounds=[(0, None)] * (m * n),
method='highs',
)
if not result.success:
raise RuntimeError(result.message)
return result, result.x.reshape(m, n)
supplier_names = ['Центр A', 'Центр B', 'Центр C']
consumer_names = ['Колледж 1', 'Колледж 2', 'Колледж 3', 'Колледж 4']
supplies = np.array([25, 45, 35], dtype=float)
demands = np.array([15, 30, 25, 35], dtype=float)
costs = np.array([[4, 8, 50, 9], [6, 5, 7, 8], [7, 4, 6, 5]], dtype=float)
balanced_supplies, balanced_demands, balanced_costs, supplier_names, consumer_names = balance_transport_problem(
supplies, demands, costs, supplier_names, consumer_names
)
result, plan = solve_transport_problem(balanced_supplies, balanced_demands, balanced_costs)
plan_df = pd.DataFrame(plan, index=supplier_names, columns=consumer_names)
cost_df = pd.DataFrame(balanced_costs, index=supplier_names, columns=consumer_names)
print('Оптимальная стоимость:', round(result.fun, 2))
print()
print('План перевозок:')
display(plan_df)
print('Матрица затрат:')
display(cost_df)
Оптимальная стоимость: 590.0
План перевозок:
| Колледж 1 | Колледж 2 | Колледж 3 | Колледж 4 | |
|---|---|---|---|---|
| Центр A | 15.0 | 10.0 | 0.0 | -0.0 |
| Центр B | 0.0 | 20.0 | 25.0 | 0.0 |
| Центр C | 0.0 | 0.0 | 0.0 | 35.0 |
Матрица затрат:
| Колледж 1 | Колледж 2 | Колледж 3 | Колледж 4 | |
|---|---|---|---|---|
| Центр A | 4.0 | 8.0 | 50.0 | 9.0 |
| Центр B | 6.0 | 5.0 | 7.0 | 8.0 |
| Центр C | 7.0 | 4.0 | 6.0 | 5.0 |
used_routes = []
for supplier in plan_df.index:
for consumer in plan_df.columns:
value = float(plan_df.loc[supplier, consumer])
if value > 1e-9:
used_routes.append({
'маршрут': f'{supplier} -> {consumer}',
'объём': round(value, 2),
'тариф': float(cost_df.loc[supplier, consumer]),
'затраты': round(value * float(cost_df.loc[supplier, consumer]), 2),
})
used_routes_df = pd.DataFrame(used_routes)
print('Использованные маршруты:')
display(used_routes_df)
print('Проверка баланса по поставщикам:')
display(pd.DataFrame({'план': plan_df.sum(axis=1), 'запас': balanced_supplies}, index=plan_df.index))
print('Проверка баланса по потребителям:')
display(pd.DataFrame({'план': plan_df.sum(axis=0), 'спрос': balanced_demands}, index=plan_df.columns))
Использованные маршруты:
| маршрут | объём | тариф | затраты | |
|---|---|---|---|---|
| 0 | Центр A -> Колледж 1 | 15.0 | 4.0 | 60.0 |
| 1 | Центр A -> Колледж 2 | 10.0 | 8.0 | 80.0 |
| 2 | Центр B -> Колледж 2 | 20.0 | 5.0 | 100.0 |
| 3 | Центр B -> Колледж 3 | 25.0 | 7.0 | 175.0 |
| 4 | Центр C -> Колледж 4 | 35.0 | 5.0 | 175.0 |
Проверка баланса по поставщикам:
| план | запас | |
|---|---|---|
| Центр A | 25.0 | 25.0 |
| Центр B | 45.0 | 45.0 |
| Центр C | 35.0 | 35.0 |
Проверка баланса по потребителям:
| план | спрос | |
|---|---|---|
| Колледж 1 | 15.0 | 15.0 |
| Колледж 2 | 30.0 | 30.0 |
| Колледж 3 | 25.0 | 25.0 |
| Колледж 4 | 35.0 | 35.0 |
2. Что важно проговорить в выводе#
если маршрут слишком дорогой, solver использует его только при необходимости;
важно интерпретировать, какой альтернативой этот маршрут был вытеснен;
для отчёта удобно перечислить только ненулевые перевозки.
Для отчёта обычно достаточно перечислить ненулевые маршруты, пояснить роль фиктивного узла, если он появился, и отдельно указать итоговую стоимость перевозок.