ЛР-02: Лекарства в больницы#
Worked example: civil 01#
Это полностью разобранный example. Он показывает образец постановки, решения и интерпретации транспортной модели.
1. Постановка кейса#
Базовый гражданский пример на закрытой транспортной задаче.
Запасы#
Поставщик |
Объём |
|---|---|
Склад A |
35 |
Склад B |
50 |
Склад C |
40 |
Спрос#
Потребитель |
Объём |
|---|---|
Больница 1 |
20 |
Больница 2 |
30 |
Больница 3 |
25 |
Больница 4 |
50 |
Матрица затрат#
Откуда / Куда |
Больница 1 |
Больница 2 |
Больница 3 |
Больница 4 |
|---|---|---|---|---|
Склад A |
4 |
6 |
8 |
13 |
Склад B |
5 |
4 |
7 |
9 |
Склад C |
6 |
3 |
4 |
7 |
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([35, 50, 40], dtype=float)
demands = np.array([20, 30, 25, 50], dtype=float)
costs = np.array([[4, 6, 8, 13], [5, 4, 7, 9], [6, 3, 4, 7]], 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)
Оптимальная стоимость: 750.0
План перевозок:
| Больница 1 | Больница 2 | Больница 3 | Больница 4 | |
|---|---|---|---|---|
| Склад A | 20.0 | 15.0 | 0.0 | 0.0 |
| Склад B | 0.0 | 15.0 | 0.0 | 35.0 |
| Склад C | 0.0 | 0.0 | 25.0 | 15.0 |
Матрица затрат:
| Больница 1 | Больница 2 | Больница 3 | Больница 4 | |
|---|---|---|---|---|
| Склад A | 4.0 | 6.0 | 8.0 | 13.0 |
| Склад B | 5.0 | 4.0 | 7.0 | 9.0 |
| Склад C | 6.0 | 3.0 | 4.0 | 7.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 | 20.0 | 4.0 | 80.0 |
| 1 | Склад A -> Больница 2 | 15.0 | 6.0 | 90.0 |
| 2 | Склад B -> Больница 2 | 15.0 | 4.0 | 60.0 |
| 3 | Склад B -> Больница 4 | 35.0 | 9.0 | 315.0 |
| 4 | Склад C -> Больница 3 | 25.0 | 4.0 | 100.0 |
| 5 | Склад C -> Больница 4 | 15.0 | 7.0 | 105.0 |
Проверка баланса по поставщикам:
| план | запас | |
|---|---|---|
| Склад A | 35.0 | 35.0 |
| Склад B | 50.0 | 50.0 |
| Склад C | 40.0 | 40.0 |
Проверка баланса по потребителям:
| план | спрос | |
|---|---|---|
| Больница 1 | 20.0 | 20.0 |
| Больница 2 | 30.0 | 30.0 |
| Больница 3 | 25.0 | 25.0 |
| Больница 4 | 50.0 | 50.0 |
2. Что важно проговорить в выводе#
после решения полезно отдельно сверить суммы по строкам и столбцам;
нужно посмотреть, есть ли дорогие маршруты, которые solver избегает;
вывод должен объяснять не только стоимость, но и структуру плана.
Для отчёта обычно достаточно перечислить ненулевые маршруты, пояснить роль фиктивного узла, если он появился, и отдельно указать итоговую стоимость перевозок.