Задача о перевозках
Это руководство было изначально предоставлено Луисом Луангкесорном (Louis Luangkesorn).
В этом руководстве рассматривается адаптированный вариант задачи о перевозках, описываемой в книге AMPL:[ ]https://ampl.com/resources/the-ampl-book/[A Modeling Language for Mathematical Programming], R. Fourer, D.M. Gay and B.W. Kernighan.
Цель данного руководства — продемонстрировать создание модели JuMP на основе текстового файла с особой структурой.
Формулировка
Предположим, имеется несколько заводов, производящих тренажеры «кузнечик», и несколько розничных магазинов, в которых они продаются. Каждый завод имеет максимальную производительность, а каждый магазин — свой спрос на тренажеры.
В задаче о перевозках нужно найти такое количество тренажеров, изготавливаемых на каждом заводе и доставляемых в каждый магазин, чтобы общие транспортные расходы были минимальными.
С математической точки зрения набор заводов представлен множеством исходных пунктов , а розничные магазины — множеством пунктов назначения . Максимальное предложение каждого завода составляет , а спрос в каждом магазине — . Расходы на транспортировку одного тренажера из в составляют .
Приложив немного усилий, мы можем смоделировать задачу о перевозках в виде следующей линейной программы:
Данные
Предполагается, что данные представлены в виде текстового файла, имеющего приведенный ниже формат. На практике этот текстовый файл с входными данными был бы получен от пользователя, но для целей этого руководства мы создадим его в Julia.
open(joinpath(@__DIR__, "transp.txt"), "w") do io
print(
io,
"""
. FRA DET LAN WIN STL FRE LAF SUPPLY
GARY 39 14 11 14 16 82 8 1400
CLEV 27 . 12 . 26 95 17 2600
PITT 24 14 17 13 28 99 20 2900
DEMAND 900 1200 600 400 1700 1100 1000 0
""",
)
return
end
Здесь строки — это исходные пункты, столбцы — пункты назначения, а значения — расходы на доставку одного тренажера из исходного пункта в пункт назначения. Если тренажер невозможно доставить из исходного пункта в пункт назначения, значение равно .
. Последние строка и столбец представляют соответственно спрос и предложение каждого пункта.
Мы не стали учитывать ребра, которых нет в формулировке, но можно внести небольшое изменение и зафиксировать значение при .
Сначала необходимо преобразовать этот текстовый формат в подходящую структуру данных Julia, с которой можно будет работать. Так как данные представляют собой таблицу с именованными строками и столбцами, одним из вариантов является объект JuMP Containers.DenseAxisArray
:
function read_data(filename::String)
data = DelimitedFiles.readdlm(filename)
rows, columns = data[2:end, 1], data[1, 2:end]
return Containers.DenseAxisArray(data[2:end, 2:end], rows, columns)
end
data = read_data(joinpath(@__DIR__, "transp.txt"))
2-dimensional DenseAxisArray{Any,2,...} with index sets:
Dimension 1, Any["GARY", "CLEV", "PITT", "DEMAND"]
Dimension 2, Any["FRA", "DET", "LAN", "WIN", "STL", "FRE", "LAF", "SUPPLY"]
And data, a 4×8 Matrix{Any}:
39 14 11 14 16 82 8 1400
27 "." 12 "." 26 95 17 2600
24 14 17 13 28 99 20 2900
900 1200 600 400 1700 1100 1000 0
Формулировка на языке JuMP
Согласно рекомендациям в разделе Design patterns for larger models, мы записываем нашу модель JuMP как функцию, которая принимает входные данные. В этом примере выходные данные выводятся в stdout
:
function solve_transportation_problem(data::Containers.DenseAxisArray)
# Получаем множество значений предложения и спроса
O, D = axes(data)
# Удаляем узлы SUPPLY и DEMAND из множеств
O, D = setdiff(O, ["DEMAND"]), setdiff(D, ["SUPPLY"])
model = Model(HiGHS.Optimizer)
set_silent(model)
@variable(model, x[o in O, d in D] >= 0)
# Удаляем ребра со стоимостью «.», присваивая им фиксированное значение 0,0.
for o in O, d in D
if data[o, d] == "."
fix(x[o, d], 0.0; force = true)
end
end
@objective(
model,
Min,
sum(data[o, d] * x[o, d] for o in O, d in D if data[o, d] != "."),
)
@constraint(model, [o in O], sum(x[o, :]) <= data[o, "SUPPLY"])
@constraint(model, [d in D], sum(x[:, d]) == data["DEMAND", d])
optimize!(model)
@assert is_solved_and_feasible(model)
# Выводим решение в структурированном формате входных данных
print(" ", join(lpad.(D, 7, ' ')))
for o in O
print("\n", o)
for d in D
if isapprox(value(x[o, d]), 0.0; atol = 1e-6)
print(" .")
else
print(" ", lpad(value(x[o, d]), 6, ' '))
end
end
end
return
end
solve_transportation_problem (generic function with 1 method)