Документация Engee

Программирование в ограничениях

JuMP поддерживает ряд ограничений для программирования в ограничениях посредством соответствующих множеств в MathOptInterface. Для большинства ограничений есть встроенные переформулировки, которые преобразуют ограничение программирования в ограничениях в эквивалентное ограничение частично целочисленного программирования.

Из-за такой переформулировки все переменные должны быть целочисленными и, как правило, иметь конечные границы. Если переформулировка требует конечности, а переменная имеет неконечные границы, выдается ошибка.

В этом руководстве используются следующие пакеты.

using JuMP
import HiGHS

AllDifferent

Множество MOI.AllDifferent гарантирует, что все элементы в списке принимают разные целочисленные значения.

model = Model(HiGHS.Optimizer)
set_silent(model)
@variable(model, 1 <= x[1:4] <= 4, Int)
@constraint(model, x in MOI.AllDifferent(4))
optimize!(model)
@assert is_solved_and_feasible(model)
value.(x)
4-element Vector{Float64}:
 1.0
 4.0
 3.0000000000000004
 1.9999999999999996

BinPacking

С помощью множества MOI.BinPacking можно разделить множество элементов на группы так, чтобы сумма их весов не превышала вместимости контейнера.

weights, capacity = Float64[1, 1, 2, 2, 3], 3.0;
number_of_bins = 3
model = Model(HiGHS.Optimizer)
set_silent(model)
@variable(model, 1 <= x[1:length(weights)] <= number_of_bins, Int)
@constraint(model, x in MOI.BinPacking(capacity, weights))
optimize!(model)
@assert is_solved_and_feasible(model)
value.(x)
5-element Vector{Float64}:
 2.0
 1.0
 2.0
 1.0
 3.0

Здесь значение x[i] — это контейнер, в который был помещен элемент i.

Circuit

Множество MOI.Circuit служит для построения маршрута обхода списка из N переменных. Каждой из них присваивается целочисленное значение от 1 до N, указывающее следующий элемент в списке:

model = Model(HiGHS.Optimizer)
set_silent(model)
@variable(model, x[1:4], Int)
@constraint(model, x in MOI.Circuit(4))
optimize!(model)
@assert is_solved_and_feasible(model)

Посмотрим, какой маршрут обхода был найден, начиная с узла номер 1:

y = round.(Int, value.(x))
tour = Int[1]
while length(tour) < length(y)
    push!(tour, y[tour[end]])
end
tour
4-element Vector{Int64}:
 1
 4
 3
 2

CountAtLeast

Множество MOI.CountAtLeast гарантирует, что по крайней мере n элементов во множестве переменных принадлежат множеству значений.

Например, вот модель с тремя переменными, ограниченными значениями в диапазоне от 0 до 5:

model = Model(HiGHS.Optimizer)
set_silent(model)
@variable(model, 0 <= x[1:3] <= 5, Int)
3-element Vector{VariableRef}:
 x[1]
 x[2]
 x[3]

Если нужно гарантировать, что хотя бы один элемент из каждого множества {x[1], x[2]} и {x[2], x[3]} принадлежит множеству {3}, следует создать список переменных, объединив множества:

variables = [x[1], x[2], x[2], x[3]]
4-element Vector{VariableRef}:
 x[1]
 x[2]
 x[2]
 x[3]

Затем необходимо получить список разделов, содержащий количество элементов в каждом множестве переменных:

partitions = [2, 2]
2-element Vector{Int64}:
 2
 2

Наконец, потребуется множество значений, которому должны принадлежать элементы:

values = Set([3])
Set{Int64} with 1 element:
  3

И количество элементов, которые должны принадлежать множеству values:

n = 1
1

Ограничение выглядит так:

@constraint(model, variables in MOI.CountAtLeast(n, partitions, values))
[x[1], x[2], x[2], x[3]] ∈ MathOptInterface.CountAtLeast(1, [2, 2], Set([3]))

Чтобы обеспечить уникальность решения, добавим ограничение, согласно которому x[2] должно быть <= 2. Это гарантирует, что единственным допустимым решением для x[1] и x[3] будет 3:

@constraint(model, x[2] <= 2)
x[2] ≤ 2

Проверим, найдено ли допустимое решение:

optimize!(model)
@assert is_solved_and_feasible(model)
value.(x)
3-element Vector{Float64}:
 3.0
 0.0
 3.0

CountBelongs

Множество MOI.CountBelongs подсчитывает количество элементов во множестве переменных, принадлежащих множеству значений.

Например, подсчитать количество элементов во множестве из 4 переменных, принадлежащих множеству {2, 3}, можно так:

model = Model(HiGHS.Optimizer)
set_silent(model)
@variable(model, 0 <= x[i = 1:4] <= i, Int)
@variable(model, n, Int)
@objective(model, Max, sum(x))
set = Set([2, 3])
@constraint(model, [n; x] in MOI.CountBelongs(1 + length(x), set))
optimize!(model)
@assert is_solved_and_feasible(model)
value(n), value.(x)
(2.0, [1.0, 2.0, 3.0, 4.0])

CountDistinct

Множество MOI.CountDistinct подсчитывает количество уникальных элементов во множестве переменных.

model = Model(HiGHS.Optimizer)
set_silent(model)
@variable(model, 0 <= x[i = 1:4] <= i, Int)
@variable(model, n, Int)
@objective(model, Max, sum(x))
@constraint(model, [n; x] in MOI.CountDistinct(1 + length(x)))
optimize!(model)
@assert is_solved_and_feasible(model)
value(n), value.(x)
(4.0, [1.0, 2.0, 3.0, 4.0])

CountGreaterThan

Множество MOI.CountGreaterThan служит для наложения строгого ограничения на максимальное количество уникальных элементов во множестве переменных со значением, равным другой переменной.

Например, подсчитать количество n появлений y в векторе x можно так:

model = Model(HiGHS.Optimizer)
set_silent(model)
@variable(model, 0 <= x[i = 1:4] <= i, Int)
@variable(model, n, Int)
@variable(model, 3 <= y <= 4, Int)
@objective(model, Max, sum(x))
@constraint(model, [n; y; x] in MOI.CountGreaterThan(1 + 1 + length(x)))
optimize!(model)
@assert is_solved_and_feasible(model)
value(n), value(y), value.(x)
(2.0, 3.0, [1.0, 2.0, 3.0, 4.0])

Здесь n строго больше количества, и нет никаких ограничений на величину n. Например, n = 100 также является допустимым решением. Единственное ограничение заключается в том, что n не может быть равно количеству появлений y или меньше его.

Таблица

Множество MOI.Table служит для выбора одной строки из матрицы значений.

Например, если дана матрица:

table = Float64[1 1 0; 0 1 1; 1 0 1; 1 1 1]
4×3 Matrix{Float64}:
 1.0  1.0  0.0
 0.0  1.0  1.0
 1.0  0.0  1.0
 1.0  1.0  1.0

мы можем ограничить трехэлементный вектор x так, чтобы он был равен одной из строк в table:

model = Model(HiGHS.Optimizer)
set_silent(model)
@variable(model, x[i = 1:3], Int)
@constraint(model, x in MOI.Table(table))
optimize!(model)
@assert is_solved_and_feasible(model)
value.(x)
3-element Vector{Float64}:
 1.0
 1.0
 0.0