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

Ограничения

Язык JuMP основан на API MathOptInterface (MOI). По этой причине в JuMP используется следующая стандартная форма представления задач:

Каждое ограничение состоит из функции и множества. Например, называется не ограничением меньше или равно, а ограничением скалярная аффинная функция в «меньше чем», где функция принадлежит множеству меньше чем ]. Ограничения, состоящие из различных типов функций и множеств, сокращенно называются функцией во множестве.

На этой странице объясняется, как записывать различные типы ограничений в JuMP. Сведения о нелинейных ограничениях см. в разделе Nonlinear Modeling.

Добавление ограничения

Ограничение добавляется в модель JuMP с помощью макроса @constraint. Используемый синтаксис зависит от типа добавляемого ограничения.

Добавление линейного ограничения

Линейные ограничения создаются с помощью макроса @constraint:

julia> model = Model();

julia> @variable(model, x[1:3]);

julia> @constraint(model, c1, sum(x) <= 1)
c1 : x[1] + x[2] + x[3] ≤ 1

julia> @constraint(model, c2, x[1] + 2 * x[3] >= 2)
c2 : x[1] + 2 x[3] ≥ 2

julia> @constraint(model, c3, sum(i * x[i] for i in 1:3) == 3)
c3 : x[1] + 2 x[2] + 3 x[3] = 3

julia> @constraint(model, c4, 4 <= 2 * x[2] <= 5)
c4 : 2 x[2] ∈ [4, 5]

Нормализация

JuMP нормализует ограничения, перенося все члены, содержащие переменные, в левую часть, а все свободные члены — в правую часть. В результате получается следующее.

julia> model = Model();

julia> @variable(model, x);

julia> @constraint(model, c, 2x + 1 <= 4x + 4)
c : -2 x ≤ 3

Добавление квадратичного ограничения

Помимо аффинных функций, JuMP поддерживает ограничения с квадратичными членами. Например:

julia> model = Model();

julia> @variable(model, x[i=1:2])
2-element Vector{VariableRef}:
 x[1]
 x[2]

julia> @variable(model, t >= 0)
t

julia> @constraint(model, my_q, x[1]^2 + x[2]^2 <= t^2)
my_q : x[1]² + x[2]² - t² ≤ 0

Так как решатели могут учитывать то, что ограничение является квадратичным, добавлять квадратичные ограничения предпочтительнее с помощью макроса @constraint, а не @NLconstraint.

Векторизованные ограничения

Добавлять ограничения в JuMP можно также с использованием векторизованной линейной алгебры. Например:

julia> model = Model();

julia> @variable(model, x[i=1:2])
2-element Vector{VariableRef}:
 x[1]
 x[2]

julia> A = [1 2; 3 4]
2×2 Matrix{Int64}:
 1  2
 3  4

julia> b = [5, 6]
2-element Vector{Int64}:
 5
 6

julia> @constraint(model, con_vector, A * x == b)
con_vector : [x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ Zeros()

julia> @constraint(model, con_scalar, A * x .== b)
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}:
 con_scalar : x[1] + 2 x[2] = 5
 con_scalar : 3 x[1] + 4 x[2] = 6

Ограничения == и .== похожи, но между ними есть тонкое различие. В первом случае создается одно ограничение, а именно ограничение «MOI.VectorAffineFunction в MOI.Zeros». Во втором случае создается вектор ограничений «MOI.ScalarAffineFunction в MOI.EqualTo».

Выбор формулировки зависит от решателя и от того, что требуется сделать с объектом ограничения con_vector или con_scalar.

  • Если используется конический решатель, ожидается, что двойственностью для con_vector будет Vector{Float64}, и строка в ограничении не будет удаляться, выберите формулировку ==.

  • Если используется решатель, принимающий список скалярных ограничений, например HiGHS, или нужно удалить часть ограничения либо обратиться к одной строке ограничения, например dual(con_scalar[2]), используйте трансляцию .==.

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

В качестве операторов сравнения в ограничении можно также использовать <=, .<=, >= и .>=.

julia> @constraint(model, A * x <= b)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ Nonpositives()

julia> @constraint(model, A * x .<= b)
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 x[1] + 2 x[2] ≤ 5
 3 x[1] + 4 x[2] ≤ 6

julia> @constraint(model, A * x >= b)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ Nonnegatives()

julia> @constraint(model, A * x .>= b)
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.GreaterThan{Float64}}, ScalarShape}}:
 x[1] + 2 x[2] ≥ 5
 3 x[1] + 4 x[2] ≥ 6

Неравенства матриц

Неравенства матриц не поддерживаются из-за общей неоднозначности между поэлементными неравенствами и ограничением PSDCone.

julia> model = Model();

julia> @variable(model, x[1:2, 1:2], Symmetric);

julia> @variable(model, y[1:2, 1:2], Symmetric);

julia> @constraint(model, x >= y)
ERROR: At none:1: `@constraint(model, x >= y)`:
The syntax `x >= y` is ambiguous for matrices because we cannot tell if
you intend a positive semidefinite constraint or an elementwise
inequality.

To create a positive semidefinite constraint, pass `PSDCone()` or
`HermitianPSDCone()`:

```julia
@constraint(model, x >= y, PSDCone())
```

To create an element-wise inequality, pass `Nonnegatives()`, or use
broadcasting:

```julia
@constraint(model, x >= y, Nonnegatives())
# или
@constraint(model, x .>= y)
```
Stacktrace:
[...]

Вместо этого используйте Set inequality syntax, чтобы задать множество, например PSDCone или Nonnegatives:

julia> @constraint(model, x >= y, PSDCone())
[x[1,1] - y[1,1]  x[1,2] - y[1,2]
 ⋯                x[2,2] - y[2,2]] ∈ PSDCone()

julia> @constraint(model, x >= y, Nonnegatives())
[x[1,1] - y[1,1]  x[1,2] - y[1,2]
 ⋯                x[2,2] - y[2,2]] ∈ Nonnegatives()

julia> @constraint(model, x >= y, Nonpositives())
[x[1,1] - y[1,1]  x[1,2] - y[1,2]
 ⋯                x[2,2] - y[2,2]] ∈ Nonpositives()

julia> @constraint(model, x >= y, Zeros())
[x[1,1] - y[1,1]  x[1,2] - y[1,2]
 ⋯                x[2,2] - y[2,2]] ∈ Zeros()

Особые случаи

Есть два исключения: если результат левой части минус правая часть представляет собой матрицу LinearAlgebra.Symmetric или матрицу LinearAlgebra.Hermitian, можно использовать синтаксис равенства без трансляции:

julia> using LinearAlgebra

julia> model = Model();

julia> @variable(model, X[1:2, 1:2], Symmetric)
2×2 Symmetric{VariableRef, Matrix{VariableRef}}:
 X[1,1]  X[1,2]
 X[1,2]  X[2,2]

julia> @constraint(model, X == LinearAlgebra.I)
[X[1,1] - 1  X[1,2]
 ⋯           X[2,2] - 1] ∈ Zeros()

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

julia> @constraint(model, X .== LinearAlgebra.I)
2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}:
 X[1,1] = 1  X[1,2] = 0
 X[1,2] = 0  X[2,2] = 1

То же самое верно для матриц LinearAlgebra.Hermitian:

julia> using LinearAlgebra

julia> model = Model();

julia> @variable(model, X[1:2, 1:2] in HermitianPSDCone())
2×2 Hermitian{GenericAffExpr{ComplexF64, VariableRef}, Matrix{GenericAffExpr{ComplexF64, VariableRef}}}:
 real(X[1,1])                    real(X[1,2]) + imag(X[1,2]) im
 real(X[1,2]) - imag(X[1,2]) im  real(X[2,2])

julia> @constraint(model, X == LinearAlgebra.I)
[real(X[1,1]) - 1                real(X[1,2]) + imag(X[1,2]) im
 real(X[1,2]) - imag(X[1,2]) im  real(X[2,2]) - 1] ∈ Zeros()

julia> @constraint(model, X .== LinearAlgebra.I)
2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{ComplexF64}, MathOptInterface.EqualTo{ComplexF64}}, ScalarShape}}:
 real(X[1,1]) = 1                    real(X[1,2]) + imag(X[1,2]) im = 0
 real(X[1,2]) - imag(X[1,2]) im = 0  real(X[2,2]) = 1

Контейнеры ограничений

Макрос @constraint](../api.md#JuMP.@constraint-Tuple) поддерживает создание коллекций ограничений. Здесь мы рассмотрим синтаксис лишь вкратце, а более подробные сведения можно получить в разделе [Constraint containers.

Создание массивов ограничений:

julia> model = Model();

julia> @variable(model, x[1:3]);

julia> @constraint(model, c[i=1:3], x[i] <= i^2)
3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 c[1] : x[1] ≤ 1
 c[2] : x[2] ≤ 4
 c[3] : x[3] ≤ 9

julia> c[2]
c[2] : x[2] ≤ 4

Множества могут быть любого типа Julia, поддерживающего итерацию:

julia> model = Model();

julia> @variable(model, x[1:3]);

julia> @constraint(model, c[i=2:3, ["red", "blue"]], x[i] <= i^2)
2-dimensional DenseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape},2,...} with index sets:
    Dimension 1, 2:3
    Dimension 2, ["red", "blue"]
And data, a 2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 c[2,red] : x[2] ≤ 4  c[2,blue] : x[2] ≤ 4
 c[3,red] : x[3] ≤ 9  c[3,blue] : x[3] ≤ 9

julia> c[2, "red"]
c[2,red] : x[2] ≤ 4

Множества могут зависеть от предыдущих индексов:

julia> model = Model();

julia> @variable(model, x[1:3]);

julia> @constraint(model, c[i=1:3, j=i:3], x[i] <= j)
JuMP.Containers.SparseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}, 2, Tuple{Int64, Int64}} with 6 entries:
  [1, 1]  =  c[1,1] : x[1] ≤ 1
  [1, 2]  =  c[1,2] : x[1] ≤ 2
  [1, 3]  =  c[1,3] : x[1] ≤ 3
  [2, 2]  =  c[2,2] : x[2] ≤ 2
  [2, 3]  =  c[2,3] : x[2] ≤ 3
  [3, 3]  =  c[3,3] : x[3] ≤ 3

Элементы множеств можно фильтровать, используя синтаксис ;:

julia> model = Model();

julia> @variable(model, x[1:9]);

julia> @constraint(model, c[i=1:9; mod(i, 3) == 0], x[i] <= i)
JuMP.Containers.SparseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}, 1, Tuple{Int64}} with 3 entries:
  [3]  =  c[3] : x[3] ≤ 3
  [6]  =  c[6] : x[6] ≤ 6
  [9]  =  c[9] : x[9] ≤ 9

Регистрация ограничений

При создании ограничений JuMP регистрирует их в модели, используя соответствующий символ. Получить зарегистрированное имя можно посредством model[:key]:

julia> model = Model()
A JuMP Model
├ solver: none
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none

julia> @variable(model, x)
x

julia> @constraint(model, my_c, 2x <= 1)
my_c : 2 x ≤ 1

julia> model
A JuMP Model
├ solver: none
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 1
├ num_constraints: 1
│ └ AffExpr in MOI.LessThan{Float64}: 1
└ Names registered in the model
  └ :my_c, :x

julia> model[:my_c] === my_c
true

Анонимные ограничения

Так как JuMP регистрирует ограничения внутри модели, создание двух ограничений с одинаковыми именами повышает вероятность случайных ошибок: #error:

julia> model = Model();

julia> @variable(model, x)
x

julia> @constraint(model, c, 2x <= 1)
c : 2 x ≤ 1

julia> @constraint(model, c, 2x <= 1)
ERROR: An object of name c is already attached to this model. If this
    is intended, consider using the anonymous construction syntax, for example,
    `x = @variable(model, [1:N], ...)` where the name of the object does
    not appear inside the macro.

    Alternatively, use `unregister(model, :c)` to first unregister
    the existing name from the model. Note that this will not delete the
    object; it will just remove the reference at `model[:c]`.
[...]

Частой причиной возникновения этой ошибки является добавление ограничений в цикле.

В качестве обходного пути JuMP предоставляет анонимные ограничения. Создадим анонимное ограничение, опустив аргумент имени:

julia> model = Model();

julia> @variable(model, x);

julia> c = @constraint(model, 2x <= 1)
2 x ≤ 1

Создадим контейнер анонимных ограничений, опустив имя перед [:

julia> model = Model();

julia> @variable(model, x[1:3]);

julia> c = @constraint(model, [i = 1:3], x[i] <= i)
3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 x[1] ≤ 1
 x[2] ≤ 2
 x[3] ≤ 3

Имена ограничений

Помимо символа, с которым регистрируются ограничения, у ограничений есть имя в виде строки String, которое используется при выводе данных и записи в файлы.

Для получения и задания имени ограничения используйте методы name(::JuMP.ConstraintRef) и set_name(::JuMP.ConstraintRef, ::String):

julia> model = Model(); @variable(model, x);

julia> @constraint(model, con, x <= 1)
con : x ≤ 1

julia> name(con)
"con"

julia> set_name(con, "my_con_name")

julia> con
my_con_name : x ≤ 1

Переопределить имя по умолчанию можно с помощью именованного аргумента base_name:

julia> model = Model(); @variable(model, x);

julia> con = @constraint(model, [i=1:2], x <= i, base_name = "my_con")
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 my_con[1] : x ≤ 1
 my_con[2] : x ≤ 2

Обратите внимание, что имена применяются к каждому элементу контейнера ограничений, а не к самому контейнеру:

julia> name(con[1])
"my_con[1]"

julia> set_name(con[1], "c")

julia> con
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 c : x ≤ 1
 my_con[2] : x ≤ 2

Для некоторых моделей задание строкового имени для каждого ограничения может занимать неоправданно большую долю общего времени, затрачиваемого на построение модели. Чтобы отключить имена типа String, передайте аргумент set_string_name = false в @constraint:

julia> model = Model();

julia> @variable(model, x);

julia> @constraint(model, con, x <= 2, set_string_name = false)
x ≤ 2

Дополнительные сведения см. в разделе Disable string names.

Получение ограничения по имени

Чтобы получить ограничение из модели, используйте метод constraint_by_name:

julia> constraint_by_name(model, "c")
c : x ≤ 1

Если такого имени нет, возвращается nothing:

julia> constraint_by_name(model, "bad_name")

С помощью constraint_by_name можно искать только отдельные ограничения. Код наподобие следующего не будет работать:

julia> model = Model(); @variable(model, x);

julia> con = @constraint(model, [i=1:2], x <= i, base_name = "my_con")
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 my_con[1] : x ≤ 1
 my_con[2] : x ≤ 2

julia> constraint_by_name(model, "my_con")

Для поиска коллекции ограничений не используйте метод constraint_by_name. Вместо этого зарегистрируйте их с помощью синтаксиса model[:key] = value:

julia> model = Model(); @variable(model, x);

julia> model[:con] = @constraint(model, [i=1:2], x <= i, base_name = "my_con")
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 my_con[1] : x ≤ 1
 my_con[2] : x ≤ 2

julia> model[:con]
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 my_con[1] : x ≤ 1
 my_con[2] : x ≤ 2

Строковые имена, символьные имена и привязки

У новых пользователей часто возникают затруднения с ограничениями. Частично проблема заключается в расхождении между именем, с которым зарегистрировано ограничение, и именем типа String, используемым для вывода.

Вот краткое описание различий.

  • Ограничения создаются с помощью макроса @constraint.

  • Ограничения могут быть именованными или анонимными.

  • Именованные ограничения имеют вид @constraint(model, c, expr). Для именованных ограничений:

    • Именем типа String ограничения является "c".

    • Создается переменная Julia c, которая привязывает c к ограничению JuMP.

    • Имя :c регистрируется в модели как ключ со значением c.

  • Анонимные ограничения имеют вид c = @constraint(model, expr). Для анонимных ограничений:

    • Именем типа String ограничения является "".

    • Вы управляете именем переменной Julia, используемой в качестве привязки.

    • Имя не регистрируется как ключ в модели.

  • Именованный аргумент base_name может переопределять имя типа String ограничения.

  • Имена можно регистрировать в модели вручную с помощью model[:key] = value.

Вот пример различий:

julia> model = Model();

julia> @variable(model, x)
x

julia> c_binding = @constraint(model, 2x <= 1, base_name = "c")
c : 2 x ≤ 1

julia> model
A JuMP Model
├ solver: none
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 1
├ num_constraints: 1
│ └ AffExpr in MOI.LessThan{Float64}: 1
└ Names registered in the model
  └ :x

julia> c
ERROR: UndefVarError: `c` not defined

julia> c_binding
c : 2 x ≤ 1

julia> name(c_binding)
"c"

julia> model[:c_register] = c_binding
c : 2 x ≤ 1

julia> model
A JuMP Model
├ solver: none
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 1
├ num_constraints: 1
│ └ AffExpr in MOI.LessThan{Float64}: 1
└ Names registered in the model
  └ :c_register, :x

julia> model[:c_register]
c : 2 x ≤ 1

julia> model[:c_register] === c_binding
true

julia> c
ERROR: UndefVarError: `c` not defined

Макрос @constraints

При большом количестве вызовов @constraint используйте макрос @constraints, чтобы улучшить удобочитаемость:

julia> model = Model();

julia> @variable(model, x);

julia> @constraints(model, begin
           2x <= 1
           c, x >= -1
       end)
(2 x ≤ 1, c : x ≥ -1)

julia> print(model)
Feasibility
Subject to
 c : x ≥ -1
 2 x ≤ 1

Макрос @constraints возвращает кортеж определенных ограничений.

Двойственность

В JuMP используется понятие конической двойственности из MathOptInterface. Для линейных программ допустимая двойственность для ограничения >= неотрицательна, а допустимая двойственность для ограничения <= неположительна. Для ограничения равенства двойственность зависит от направления привязки.

Определение двойственности в JuMP не зависит от целевого назначения. То есть знак допустимых двойственных решений, связанных с ограничением, зависит от направления ограничения, а не от того, является ли задача задачей максимизации или минимизации. Это соглашение отличается от определения двойственности линейного программирования в некоторых популярных учебниках. Если в линейной программе требуется определение из учебника, вероятно, лучше будет воспользоваться методами shadow_price и reduced_cost.

Доступ к двойственному значению, связанному с ограничением в последнем решении, можно получить с помощью функции dual. Чтобы проверить, есть ли у модели двойственное решение, доступное для запроса, используйте метод has_duals. Например:

julia> model = Model(HiGHS.Optimizer);

julia> set_silent(model)

julia> @variable(model, x)
x

julia> @constraint(model, con, x <= 1)
con : x ≤ 1

julia> @objective(model, Min, -2x)
-2 x

julia> has_duals(model)
false

julia> optimize!(model)

julia> has_duals(model)
true

julia> dual(con)
-2.0

julia> @objective(model, Max, 2x)
2 x

julia> optimize!(model)

julia> dual(con)
-2.0

В помощь пользователям, которые не так хорошо знакомы с понятием конической двойственности, JuMP предоставляет метод shadow_price. Он возвращает значение, которое можно интерпретировать как улучшение целевой функции при бесконечно малом ослаблении (в масштабе одной единицы) в правой части ограничения. shadow_price можно использовать только для линейных ограничений с оператором сравнения <=, >= или ==.

В приведенном выше примере dual(con) возвращает -2.0 независимо от назначения оптимизации. Однако во втором случае при назначении оптимизации Max метод shadow_price возвращает следующее:

julia> shadow_price(con)
2.0

Двойственность границ переменных

Чтобы запросить двойственные переменные, связанные с границей переменной, сначала получите ссылку на ограничение, используя один из методов UpperBoundRef, LowerBoundRef или FixRef, а затем вызовите dual для возвращенной ссылки на ограничение. Функция reduced_cost может упростить этот процесс, так как она возвращает теневую стоимость активной границы переменной (или ноль, если активной границы нет).

julia> model = Model(HiGHS.Optimizer);

julia> set_silent(model)

julia> @variable(model, x <= 1)
x

julia> @objective(model, Min, -2x)
-2 x

julia> optimize!(model)

julia> dual(UpperBoundRef(x))
-2.0

julia> reduced_cost(x)
-2.0

Изменение свободного члена

В этом разделе описывается, как изменить свободный член ограничения. Сделать это можно несколькими способами; мы рассмотрим три варианта.

Вариант 1. Изменение правой части

Для изменения правого (свободного) члена линейного или квадратичного ограничения используйте метод set_normalized_rhs. Для запроса члена в правой части используйте метод normalized_rhs.

julia> model = Model();

julia> @variable(model, x);

julia> @constraint(model, con, 2x <= 1)
con : 2 x ≤ 1

julia> set_normalized_rhs(con, 3)

julia> con
con : 2 x ≤ 3

julia> normalized_rhs(con)
3.0

set_normalized_rhs](../api.md#JuMP.set_normalized_rhs-Union{Tuple{F}, Tuple{S}, Tuple{T}, Tuple{AbstractVector{<:ConstraintRef{<:AbstractModel, MathOptInterface.ConstraintIndex{F, S}}}, AbstractVector{<:Number}}} where {T, S<:Union{MathOptInterface.EqualTo{T}, MathOptInterface.GreaterThan{T}, MathOptInterface.LessThan{T}}, F<:Union{MathOptInterface.ScalarAffineFunction{T}, MathOptInterface.ScalarQuadraticFunction{T}}}) задает правый член нормализованного ограничения. Дополнительные сведения см. в разделе [Normalization.

Вариант 2. Использование фиксированных переменных

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

На этот случай JuMP дает возможность фиксировать значения переменных с помощью функции fix. При фиксации переменной ее нижняя и верхняя границы устанавливаются в одно и то же значение. Таким образом, изменения свободного члена можно имитировать, добавив новую переменную и фиксируя ее с разными значениями. Вот пример:

julia> model = Model();

julia> @variable(model, x);

julia> @variable(model, const_term)
const_term

julia> @constraint(model, con, 2x <= const_term + 1)
con : 2 x - const_term ≤ 1

julia> fix(const_term, 1.0)

Теперь ограничение con эквивалентно 2x <= 2.

При передаче задачи в решатель фиксированные переменные не заменяются константами. Следовательно, даже если член const_term фиксирован, он все равно является переменной решения, и поэтому произведение const_term * x является билинейным.

Вариант 3. Изменение свободного члена функции

Третий вариант — использование метода add_to_function_constant. Указанная константа прибавляется к функции ограничения «func в set». В следующем примере прибавление 2 к функции приводит к удалению 2 из правой части:

julia> model = Model();

julia> @variable(model, x);

julia> @constraint(model, con, 2x <= 1)
con : 2 x ≤ 1

julia> add_to_function_constant(con, 2)

julia> con
con : 2 x ≤ -1

julia> normalized_rhs(con)
-1.0

В случае интервальных ограничений константа удаляется из каждой границы:

julia> model = Model();

julia> @variable(model, x);

julia> @constraint(model, con, 0 <= 2x + 1 <= 2)
con : 2 x ∈ [-1, 1]

julia> add_to_function_constant(con, 3)

julia> con
con : 2 x ∈ [-4, -2]

Изменение переменного коэффициента

Скалярные ограничения

Чтобы изменить коэффициенты линейного члена в ограничении, используйте метод set_normalized_coefficient. Чтобы запросить текущий коэффициент, используйте метод normalized_coefficient.

julia> model = Model();

julia> @variable(model, x[1:2]);

julia> @constraint(model, con, 2x[1] + x[2] <= 1)
con : 2 x[1] + x[2] ≤ 1

julia> set_normalized_coefficient(con, x[2], 0)

julia> con
con : 2 x[1] ≤ 1

julia> normalized_coefficient(con, x[2])
0.0

Для изменения квадратичных членов передайте две переменные:

julia> model = Model();

julia> @variable(model, x[1:2]);

julia> @constraint(model, con, x[1]^2 + x[1] * x[2] <= 1)
con : x[1]² + x[1]*x[2] ≤ 1

julia> set_normalized_coefficient(con, x[1], x[1], 2)

julia> set_normalized_coefficient(con, x[1], x[2], 3)

julia> con
con : 2 x[1]² + 3 x[1]*x[2] ≤ 1

julia> normalized_coefficient(con, x[1], x[1])
2.0

julia> normalized_coefficient(con, x[1], x[2])
3.0

set_normalized_coefficient](../api.md#JuMP.set_normalized_coefficient-Union{Tuple{F}, Tuple{T}, Tuple{AbstractVector{<:ConstraintRef{<:AbstractModel, <:MathOptInterface.ConstraintIndex{F}}}, AbstractVector{<:AbstractVariableRef}, AbstractVector{<:AbstractVariableRef}, AbstractVector{<:Number}}} where {T, F<:MathOptInterface.ScalarQuadraticFunction{T}}) задает коэффициент нормализованного ограничения. Дополнительные сведения см. в разделе [Normalization.

Векторные ограничения

Чтобы изменить коэффициенты векторнозначного ограничения, используйте метод set_normalized_coefficient.

julia> model = Model();

julia> @variable(model, x)
x

julia> @constraint(model, con, [2x + 3x, 4x] in MOI.Nonnegatives(2))
con : [5 x, 4 x] ∈ MathOptInterface.Nonnegatives(2)

julia> set_normalized_coefficient(con, x, [(1, 3.0)])

julia> con
con : [3 x, 4 x] ∈ MathOptInterface.Nonnegatives(2)

julia> set_normalized_coefficient(con, x, [(1, 2.0), (2, 5.0)])

julia> con
con : [2 x, 5 x] ∈ MathOptInterface.Nonnegatives(2)

Удаление ограничения

Чтобы удалить ограничение из модели, используйте метод delete. Чтобы проверить, принадлежит ли ограничение модели и не было ли оно удалено, используйте метод is_valid.

julia> model = Model();

julia> @variable(model, x);

julia> @constraint(model, con, 2x <= 1)
con : 2 x ≤ 1

julia> is_valid(model, con)
true

julia> delete(model, con)

julia> is_valid(model, con)
false

Удаление ограничения не отменяет регистрацию символьной ссылки в модели. Поэтому создание нового ограничения с тем же именем приведет к ошибке:

julia> @constraint(model, con, 2x <= 1)
ERROR: An object of name con is already attached to this model. If this
    is intended, consider using the anonymous construction syntax, for example,
    `x = @variable(model, [1:N], ...)` where the name of the object does
    not appear inside the macro.

    Alternatively, use `unregister(model, :con)` to first unregister
    the existing name from the model. Note that this will not delete the
    object; it will just remove the reference at `model[:con]`.
[...]

После вызова delete вызовите unregister, чтобы удалить символьную ссылку:

julia> unregister(model, :con)

julia> @constraint(model, con, 2x <= 1)
con : 2 x ≤ 1

Метод delete не вызывает unregister автоматически, так как имена, которые автоматически регистрируются макросами JuMP, и имена, которые вручную регистрируются пользователем путем задания значений в object_dictionary, не различаются. Кроме того, удаляя ограничение и затем добавляя новое ограничение с тем же именем, очень легко внести ошибки в код.

Начальные значения

Чтобы указать начальное значение (также называемое мягким началом) для прямого и двойственного решений ограничения, используйте методы set_start_value и set_dual_start_value.

Чтобы запросить начальное значение для прямого и двойственного решений ограничения, используйте методы start_value и dual_start_value. Если начальное значение не задано, эти методы возвращают nothing.

julia> model = Model();

julia> @variable(model, x)
x

julia> @constraint(model, con, x >= 10)
con : x ≥ 10

julia> start_value(con)

julia> set_start_value(con, 10.0)

julia> start_value(con)
10.0

julia> dual_start_value(con)

julia> set_dual_start_value(con, 2)

julia> dual_start_value(con)
2.0

Для векторнозначных ограничений требуется вектор:

julia> model = Model();

julia> @variable(model, x[1:3])
3-element Vector{VariableRef}:
 x[1]
 x[2]
 x[3]

julia> @constraint(model, con, x in SecondOrderCone())
con : [x[1], x[2], x[3]] in MathOptInterface.SecondOrderCone(3)

julia> dual_start_value(con)

julia> set_dual_start_value(con, [1.0, 2.0, 3.0])

julia> dual_start_value(con)
3-element Vector{Float64}:
 1.0
 2.0
 3.0

Упростить задание начальных значений для всех переменных и ограничений в модели может метод set_start_values](../api.md#JuMP.set_start_values-Union{Tuple{GenericModel{T}}, Tuple{T}} where T). В руководстве [Primal and dual warm-starts также подробно описывается, как можно выполнить перебор ограничений в модели для задания пользовательских начальных значений.

Контейнеры ограничений

Как и в случае с переменными (см. раздел Variable containers), JuMP предоставляет механизм для создания компактных групп ограничений. Ссылки на такие группы ограничений возвращаются в контейнерах. Поддерживаются три типа контейнеров ограничений: Array, DenseAxisArray и SparseAxisArray. Далее рассматривается каждый из них.

Дополнительные сведения о контейнерах см. в разделе Containers.

Массивы

Вот один из способов добавления компактной группы ограничений:

julia> model = Model();

julia> @variable(model, x);

julia> @constraint(model, con[i = 1:3], i * x <= i + 1)
3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 con[1] : x ≤ 2
 con[2] : 2 x ≤ 3
 con[3] : 3 x ≤ 4

JuMP возвращает ссылки на три ограничения в массиве Array, который привязан к переменной Julia con. К этому массиву можно обращаться и создавать срезы так же, как и к любому массиву Julia:

julia> con[1]
con[1] : x ≤ 2

julia> con[2:3]
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 con[2] : 2 x ≤ 3
 con[3] : 3 x ≤ 4

Также можно создавать анонимные контейнеры, опуская имя (например, con) перед квадратными скобками:

julia> con = @constraint(model, [i = 1:2], i * x <= i + 1)
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 x ≤ 2
 2 x ≤ 3

Как и в случае с @variable, JuMP формирует массив Array ограничений, если во время компиляции определяет, что индексы представляют собой целочисленные диапазоны, начинающиеся с единицы. Поэтому при con[1:b] создается массив Array, а при con[a:b] — нет. Особым случаем является con[Base.OneTo(n)], при котором создается Array. Если JuMP не удается определить, что индексы представляют собой начинающиеся с единицы целочисленные диапазоны (например, в случае con[a:b]), вместо этого создается массив DenseAxisArray.

DenseAxisArray

Синтаксис построения массива DenseAxisArray ограничений очень похож на синтаксис построения массива DenseAxisArray переменных.

julia> model = Model();

julia> @variable(model, x);

julia> @constraint(model, con[i = 1:2, j = 2:3], i * x <= j + 1)
2-dimensional DenseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape},2,...} with index sets:
    Dimension 1, Base.OneTo(2)
    Dimension 2, 2:3
And data, a 2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 con[1,2] : x ≤ 3    con[1,3] : x ≤ 4
 con[2,2] : 2 x ≤ 3  con[2,3] : 2 x ≤ 4

SparseAxisArray

Синтаксис построения массива SparseAxisArray ограничений очень похож на синтаксис построения массива SparseAxisArray переменных.

julia> model = Model();

julia> @variable(model, x);

julia> @constraint(model, con[i = 1:2, j = 1:2; i != j], i * x <= j + 1)
JuMP.Containers.SparseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}, 2, Tuple{Int64, Int64}} with 2 entries:
  [1, 2]  =  con[1,2] : x ≤ 3
  [2, 1]  =  con[2,1] : 2 x ≤ 2

При большом количестве измерений индексов и высокой степени разреженности см. раздел Performance considerations.

Принудительное задание типа контейнера

При создании контейнера ограничений JuMP пытается выбрать наиболее ограниченный тип контейнера, в котором могут храниться ограничения. Однако поскольку это происходит во время анализа, не всегда выбор будет оптимальным. Как и в случае с @variable, тип контейнера можно задать принудительно с помощью именованного аргумента container. Синтаксис и обоснование см. в документации по переменным.

Ограничения с одинаковыми индексами

Контейнеры часто применяются для создания ограничений на основе множества индексов. Однако зачастую индексы могут повторяться:

julia> model = Model();

julia> @variable(model, x[1:2]);

julia> @variable(model, y[1:2]);

julia> @constraints(model, begin
           [i=1:2, j=1:2, k=1:2], i * x[j] <= k
           [i=1:2, j=1:2, k=1:2], i * y[j] <= k
       end);

Такой код трудно читать, и приходится часто выполнять копирование. Более удобным для восприятия является цикл for:

julia> for i=1:2, j=1:2, k=1:2
           @constraints(model, begin
               i * x[j] <= k
               i * y[j] <= k
           end)
       end

Доступ к ограничениям модели

Для запроса типов ограничений «функция во множестве» в модели используйте метод list_of_constraint_types:

julia> model = Model();

julia> @variable(model, x[i=1:2] >= i, Int);

julia> @constraint(model, x[1] + x[2] <= 1);

julia> list_of_constraint_types(model)
3-element Vector{Tuple{Type, Type}}:
 (AffExpr, MathOptInterface.LessThan{Float64})
 (VariableRef, MathOptInterface.GreaterThan{Float64})
 (VariableRef, MathOptInterface.Integer)

Для определенного сочетания типов функции и множества используйте num_constraints для получения количества ограничений и all_constraints для доступа к списку ссылок на них:

julia> num_constraints(model, VariableRef, MOI.Integer)
2

julia> cons = all_constraints(model, VariableRef, MOI.Integer)
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}, ScalarShape}}:
 x[1] integer
 x[2] integer

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

julia> num_constraints(model; count_variable_in_set_constraints = true)
5

julia> num_constraints(model; count_variable_in_set_constraints = false)
1

То же самое верно для all_constraints:

julia> all_constraints(model; include_variable_in_set_constraints = true)
5-element Vector{ConstraintRef}:
 x[1] + x[2] ≤ 1
 x[1] ≥ 1
 x[2] ≥ 2
 x[1] integer
 x[2] integer

julia> all_constraints(model; include_variable_in_set_constraints = false)
1-element Vector{ConstraintRef}:
 x[1] + x[2] ≤ 1

Если необходим более детальный контроль того, какие ограничения следует включить в подсчет, используйте такой вариант:

julia> sum(
           num_constraints(model, F, S) for
           (F, S) in list_of_constraint_types(model) if F != VariableRef
       )
1

Используйте constraint_object, чтобы получить экземпляр объекта AbstractConstraint, в котором хранятся данные ограничения:

julia> con = constraint_object(cons[1])
ScalarConstraint{VariableRef, MathOptInterface.Integer}(x[1], MathOptInterface.Integer())

julia> con.func
x[1]

julia> con.set
MathOptInterface.Integer()

Ограничения MathOptInterface

Так как язык JuMP основан на интерфейсе MathOptInterface, вы можете добавлять любые ограничения, поддерживаемые MathOptInterface, используя синтаксис «функция во множестве». Список поддерживаемых функций и множеств см. в разделе Standard form problem.

В качестве псевдонима модуля MathOptInterface используется MOI. Этот псевдоним определен при использовании директивы using JuMP. Его также можно определить в собственном коде следующим образом:

import MathOptInterface as MOI

Например, следующие два ограничения эквивалентны:

julia> model = Model();

julia> @variable(model, x[1:3]);

julia> @constraint(model, 2 * x[1] <= 1)
2 x[1] ≤ 1

julia> @constraint(model, 2 * x[1] in MOI.LessThan(1.0))
2 x[1] ≤ 1

Можно также использовать любое множество, определенное в MathOptInterface:

julia> @constraint(model, x - [1; 2; 3] in MOI.Nonnegatives(3))
[x[1] - 1, x[2] - 2, x[3] - 3] ∈ MathOptInterface.Nonnegatives(3)

julia> @constraint(model, x in MOI.ExponentialCone())
[x[1], x[2], x[3]] ∈ MathOptInterface.ExponentialCone()

В остальных разделах на этой странице описываются функции и синтаксис, повышающие удобство в стандартных ситуациях моделирования, подобные определенному в JuMP синтаксису <= и >= для удобного указания ограничений MOI.LessThan и MOI.GreaterThan.

Синтаксис неравенства множеств

Для удобства моделирования доступен синтаксис @constraint(model, x >= y, Set()), являющийся сокращением для @constraint(model, x - y in Set()).

Таким образом, следующие вызовы эквивалентны:

julia> model = Model();

julia> @variable(model, x[1:2]);

julia> y = [0.5, 0.75];

julia> @constraint(model, x >= y, MOI.Nonnegatives(2))
[x[1] - 0.5, x[2] - 0.75] ∈ MathOptInterface.Nonnegatives(2)

julia> @constraint(model, x - y in MOI.Nonnegatives(2))
[x[1] - 0.5, x[2] - 0.75] ∈ MathOptInterface.Nonnegatives(2)

Ненулевые константы в этом синтаксисе не поддерживаются:

julia> @constraint(model, x >= 1, MOI.Nonnegatives(2))
ERROR: Operation `sub_mul` between `Vector{VariableRef}` and `Int64` is not allowed. This most often happens when you write a constraint like `x >= y` where `x` is an array and `y` is a constant. Use the broadcast syntax `x .- y >= 0` instead.
Stacktrace:
[...]

Используйте вместо этого такой вызов:

julia> @constraint(model, x .- 1 >= 0, MOI.Nonnegatives(2))
[x[1] - 1, x[2] - 1] ∈ MathOptInterface.Nonnegatives(2)

Синтаксис @constraint(model, y <= x, Set()) поддерживается, но не рекомендуется, так как значение прямого и двойственного решений, связанных с ограничением, может быть отрицательным вопреки ожиданиям.

Конусные ограничения второго порядка

SecondOrderCone ограничивает переменные t и x следующим множеством:

и . Добавить его можно следующим образом:

julia> model = Model();

julia> @variable(model, t)
t

julia> @variable(model, x[1:2])
2-element Vector{VariableRef}:
 x[1]
 x[2]

julia> @constraint(model, [t; x] in SecondOrderCone())
[t, x[1], x[2]] ∈ MathOptInterface.SecondOrderCone(3)

Повернутые конусные ограничения второго порядка

RotatedSecondOrderCone Ограничивает переменные t, u и x следующим множеством:

и . Добавить его можно следующим образом:

julia> model = Model();

julia> @variable(model, t)
t

julia> @variable(model, u)
u

julia> @variable(model, x[1:2])
2-element Vector{VariableRef}:
 x[1]
 x[2]

julia> @constraint(model, [t; u; x] in RotatedSecondOrderCone())
[t, u, x[1], x[2]] ∈ MathOptInterface.RotatedSecondOrderCone(4)

Особые упорядоченные множества типа 1

В особом упорядоченном множестве типа 1 (часто обозначаемом как SOS-I или SOS1) максимум один элемент может принимать ненулевое значение.

Ограничения SOS-I создаются с помощью множества SOS1:

julia> model = Model();

julia> @variable(model, x[1:3])
3-element Vector{VariableRef}:
 x[1]
 x[2]
 x[3]

julia> @constraint(model, x in SOS1())
[x[1], x[2], x[3]] in MathOptInterface.SOS1{Float64}([1.0, 2.0, 3.0])

Хотя упорядочение переменных и не является обязательным условием для допустимости, оно может быть полезно для решателей (например, переменные представляют собой различные фабрики, которые необходимо построить, может быть построена максимум одна фабрика, а фабрики можно упорядочить по стоимости). Для упорядочения можно указать вектор весов, по которым будут упорядочены переменные.

Например, в следующем ограничении:

julia> @constraint(model, x in SOS1([3.1, 1.2, 2.3]))
[x[1], x[2], x[3]] in MathOptInterface.SOS1{Float64}([3.1, 1.2, 2.3])

переменные x имеют приоритет x[2], x[3], x[1].

Особые упорядоченные множества типа 2

В особом упорядоченном множестве типа 2 (SOS-II) не более двух элементов могут быть ненулевыми, и если есть два ненулевых элемента, они должны быть последовательными в порядке, обуславливаемом вектором весов.

Ограничения SOS-II создаются с помощью множества SOS2:

julia> @constraint(model, x in SOS2([3.0, 1.0, 2.0]))
[x[1], x[2], x[3]] in MathOptInterface.SOS2{Float64}([3.0, 1.0, 2.0])

Возможные ненулевые пары — (x[1], x[3]) и (x[2], x[3]):

Если вектор весов не указан, JuMP осуществляет упорядочение согласно 1:length(x):

julia> @constraint(model, x in SOS2())
[x[1], x[2], x[3]] in MathOptInterface.SOS2{Float64}([1.0, 2.0, 3.0])

Индикаторные ограничения

Индикаторные ограничения состоят из двоичной переменной и линейного ограничения. Ограничение действует, когда двоичная переменная принимает значение 1. Ограничение может действовать либо не действовать, когда двоичная переменная принимает значение 0.

Применить ограничение x + y <= 1, когда двоичная переменная a равна 1, можно так:

julia> model = Model();

julia> @variable(model, x)
x

julia> @variable(model, y)
y

julia> @variable(model, a, Bin)
a

julia> @constraint(model, a --> {x + y <= 1})
a --> {x + y ≤ 1}

Если ограничение должно действовать, когда a равно нулю, добавьте ! или ¬ перед двоичной переменной.

julia> @constraint(model, !a --> {x + y <= 1})
!a --> {x + y ≤ 1}

В левой части индикаторного ограничения нельзя использовать выражение.

Полуопределенные ограничения

Чтобы ограничить матрицу положительно полуопределенной формой (PSD), используйте PSDCone.

julia> model = Model();

julia> @variable(model, X[1:2, 1:2])
2×2 Matrix{VariableRef}:
 X[1,1]  X[1,2]
 X[2,1]  X[2,2]

julia> @constraint(model, X >= 0, PSDCone())
[X[1,1]  X[1,2]
 X[2,1]  X[2,2]] ∈ PSDCone()

По возможности лучше создавать матрицу Semidefinite variables с помощью макроса @variable, а не добавляя ограничение типа @constraint(model, X >= 0, PSDCone()). В некоторых решателях добавление ограничения посредством макроса @constraint менее эффективно и может приводить к добавлению в модель дополнительных промежуточных переменных и ограничений.

Неравенство X >= Y двух квадратных матриц X и Y интерпретируется как ограничение X - Y положительно полуопределенной формой.

julia> Y = [1 2; 2 1]
2×2 Matrix{Int64}:
 1  2
 2  1

julia> @constraint(model, X >= Y, PSDCone())
[X[1,1] - 1  X[1,2] - 2
 X[2,1] - 2  X[2,2] - 1] ∈ PSDCone()

Синтаксис @constraint(model, Y <= X, PSDCone()) поддерживается, но не рекомендуется, так как значение прямого и двойственного решений, связанных с ограничением, может быть отрицательным вопреки ожиданиям.

Симметрия

Решатели, поддерживающие ограничения PSD, обычно ожидают передачи символьно симметричной матрицы, имеющей одно и то же выражение в соответствующих недиагональных элементах. В нашем примере выражения элементов (1, 2) и (2, 1) — это соответственно X[1,2] - 2 и X[2,1] - 2, то есть различаются.

Чтобы привести моделируемое ограничение к требуемому виду, решатели могут добавлять ограничение равенства X[1,2] - 2 == X[2,1] - 2, обеспечивающее симметрию. Явно сообщить решателю, что матрица симметрична, можно с помощью LinearAlgebra.Symmetric:

julia> import LinearAlgebra

julia> Z = [X[1, 1] X[1, 2]; X[1, 2] X[2, 2]]
2×2 Matrix{VariableRef}:
 X[1,1]  X[1,2]
 X[1,2]  X[2,2]

julia> @constraint(model, LinearAlgebra.Symmetric(Z) >= 0, PSDCone())
[X[1,1]  X[1,2]
 ⋯       X[2,2]] ∈ PSDCone()

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

julia> @constraint(model, LinearAlgebra.Symmetric(X) >= 0, PSDCone())
[X[1,1]  X[1,2]
 ⋯       X[2,2]] ∈ PSDCone()

(Обратите внимание, что ошибка не возникает несмотря на то, что матрица X не является симметричной.)

Ограничения комплементарности

Смешанное ограничение комплементарности F(x) ⟂ x заключается в нахождении такого x в интервале [lb, ub], что верно следующее:

  • F(x) == 0, если lb < x < ub

  • F(x) >= 0, если lb == x

  • F(x) <= 0, если x == ub

В JuMP смешанные ограничения комплементарности поддерживаются посредством complements(F(x), x) или F(x) ⟂ x в макросе @constraint. Интервальное множество [lb, ub] получается из границ переменной x.

Например, определить задачу 2x - 1 ⟂ x с x ∈ [0, ∞) можно так:

julia> model = Model();

julia> @variable(model, x >= 0)
x

julia> @constraint(model, 2x - 1 ⟂ x)
[2 x - 1, x] ∈ MathOptInterface.Complements(2)

У этой задачи есть уникальное решение в точке x = 0.5.

Оператор перпендикулярности можно ввести в большинстве редакторов (и в REPL Julia), набрав \perp<tab>.

Есть и альтернативный подход, не требующий символа , — используйте функцию complements следующим образом:

julia> @constraint(model, complements(2x - 1, x))
[2 x - 1, x] ∈ MathOptInterface.Complements(2)

В обоих случаях сопоставление F(x) передается в первом аргументе, а соответствующая переменная x — во втором.

Также поддерживаются векторнозначные ограничения комплементарности:

julia> @variable(model, -2 <= y[1:2] <= 2)
2-element Vector{VariableRef}:
 y[1]
 y[2]

julia> M = [1 2; 3 4]
2×2 Matrix{Int64}:
 1  2
 3  4

julia> q = [5, 6]
2-element Vector{Int64}:
 5
 6

julia> @constraint(model, M * y + q ⟂ y)
[y[1] + 2 y[2] + 5, 3 y[1] + 4 y[2] + 6, y[1], y[2]] ∈ MathOptInterface.Complements(4)

Логические ограничения

Чтобы добавить логическое ограничение (множество MOI.EqualTo{Bool}), используйте оператор := с правым членом типа Bool:

julia> model = GenericModel{Bool}();

julia> @variable(model, x[1:2]);

julia> @constraint(model, x[1] || x[2] := true)
x[1] || x[2] = true

julia> @constraint(model, x[1] && x[2] := false)
x[1] && x[2] = false

julia> model
A JuMP Model
├ value_type: Bool
├ solver: none
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 2
├ num_constraints: 2
│ └ GenericNonlinearExpr{GenericVariableRef{Bool}} in MOI.EqualTo{Bool}: 2
└ Names registered in the model
  └ :x

Логические ограничения не следует добавлять с помощью оператора ==, так как JuMP изменит форму записи на lhs - rhs = 0, а также по той причине, что ограничения вида a == b == c требуют скобок для различения вариантов (a == b) == c и a == (b == c). В свою очередь, a == b := c эквивалентно (a == b) := c:

julia> model = Model();

julia> @variable(model, x[1:2]);

julia> rhs = false
false

julia> @constraint(model, (x[1] == x[2]) == rhs)
(x[1] == x[2]) - 0.0 = 0

julia> @constraint(model, x[1] == x[2] := rhs)
x[1] == x[2] = false