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

Переменные

Термин переменная в области математической оптимизации имеет много значений. Например, переменные оптимизации (также называемые переменными решения) — это неизвестные , которые находятся при решении задачи:

Ситуацию усложняет то, что в Julia слово переменная означает связь между именем и значением. Например, в следующем операторе:

julia> x = 1
1

x — это переменная, в которой хранится значение 1.

В JuMP термин переменная имеет свое значение — экземпляр структуры VariableRef. Таким образом, переменные в JuMP являются связующим звеном между Julia и переменными оптимизации в модели JuMP.

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

Создание переменной

Переменные создаются с помощью макроса @variable:

julia> model = Model();

julia> @variable(model, x)
x

julia> typeof(x)
VariableRef (alias for GenericVariableRef{Float64})

julia> num_variables(model)
1

Здесь x — это переменная Julia, привязанная к объекту VariableRef, и в модель добавлена одна переменная решения.

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

julia> model = Model();

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

но делать это, как правило, не нужно, так как за это отвечает макрос.

При создании переменной можно также указать ее границы:

julia> model = Model();

julia> @variable(model, x_free)
x_free

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

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

julia> @variable(model, 2 <= x_interval <= 3)
x_interval

julia> @variable(model, x_fixed == 4)
x_fixed

julia> print(model)
Feasibility
Subject to
 x_fixed = 4
 x_lower ≥ 0
 x_interval ≥ 2
 x_upper ≤ 1
 x_interval ≤ 3

При создании переменной только с нижней или только с верхней границей, значением которой не является числовой литерал (например, 1 или 1.0), имя переменной должно указываться слева. Указание имени справа является ошибкой. Например, переменную x можно создать так:

a = 1
@variable(model, x >= 1)      # ✓ Okay
@variable(model, 1.0 <= x)    # ✓ Okay
@variable(model, x >= a)      # ✓ Okay
@variable(model, a <= x)      # × Not okay
@variable(model, x >= 1 / 2)  # ✓ Okay
@variable(model, 1 / 2 <= x)  # × Not okay

Контейнеры переменных

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

Можно создавать массивы переменных JuMP:

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> x[1, 2]
x[1,2]

Множества индексов могут быть именованными, и границы могут зависеть от этих имен:

julia> model = Model();

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

julia> x[2]
x[2]

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

julia> model = Model();

julia> @variable(model, x[i = 2:3, j = 1:2:3, ["red", "blue"]] >= 0)
3-dimensional DenseAxisArray{VariableRef,3,...} with index sets:
    Dimension 1, 2:3
    Dimension 2, 1:2:3
    Dimension 3, ["red", "blue"]
And data, a 2×2×2 Array{VariableRef, 3}:
[:, :, "red"] =
 x[2,1,red]  x[2,3,red]
 x[3,1,red]  x[3,3,red]

[:, :, "blue"] =
 x[2,1,blue]  x[2,3,blue]
 x[3,1,blue]  x[3,3,blue]

julia> x[2, 1, "red"]
x[2,1,red]

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

julia> model = Model();

julia> @variable(model, u[i = 1:2, j = i:3])
JuMP.Containers.SparseAxisArray{VariableRef, 2, Tuple{Int64, Int64}} with 5 entries:
  [1, 1]  =  u[1,1]
  [1, 2]  =  u[1,2]
  [1, 3]  =  u[1,3]
  [2, 2]  =  u[2,2]
  [2, 3]  =  u[2,3]

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

julia> model = Model();

julia> @variable(model, v[i = 1:9; mod(i, 3) == 0])
JuMP.Containers.SparseAxisArray{VariableRef, 1, Tuple{Int64}} with 3 entries:
  [3]  =  v[3]
  [6]  =  v[6]
  [9]  =  v[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> model
A JuMP Model
├ solver: none
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 1
├ num_constraints: 0
└ Names registered in the model
  └ :x

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

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

julia> function set_objective(model::Model)
           @objective(model, Min, 2 * model[:my_x] + 1)
           return
       end
set_objective (generic function with 1 method)

julia> model = Model();

julia> @variable(model, my_x);

julia> set_objective(model)

julia> print(model)
Min 2 my_x + 1
Subject to

Анонимные переменные

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

julia> model = Model();

julia> @variable(model, x)
x

julia> @variable(model, x)
ERROR: An object of name x 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, :x)` 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[:x]`.
[...]

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

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

julia> model = Model();

julia> x = @variable(model)
_[1]

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

Индекс переменной может не соответствовать столбцу переменной в решателе.

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

julia> model = Model();

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

Сокращения <= и >= нельзя использовать для установки границ анонимных переменных JuMP со скалярным значением. Вместо этого используйте именованные аргументы lower_bound и upper_bound:

julia> model = Model();

julia> x_lower = @variable(model, lower_bound = 1.0)
_[1]

julia> x_upper = @variable(model, upper_bound = 2.0)
_[2]

julia> x_interval = @variable(model, lower_bound = 3.0, upper_bound = 4.0)
_[3]

Имена переменных

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

Для получения и задания имени переменной используйте методы name и set_name:

julia> model = Model();

julia> @variable(model, x)
x

julia> name(x)
"x"

julia> set_name(x, "my_x_name")

julia> x
my_x_name

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

julia> model = Model();

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

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

julia> name(x[1])
"my_var[1]"

julia> set_name(x[1], "my_x")

julia> x
2-element Vector{VariableRef}:
 my_x
 my_var[2]

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

julia> model = Model();

julia> @variable(model, x, set_string_name = false)
_[1]

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

Получение переменной по имени

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

julia> variable_by_name(model, "my_x")
my_x

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

julia> variable_by_name(model, "bad_name")

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

julia> model = Model();

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

julia> variable_by_name(model, "my_var")

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

julia> model = Model();

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

julia> model[:x]
2-element Vector{VariableRef}:
 my_var[1]
 my_var[2]

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

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

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

  • Переменные JuMP создаются с помощью макроса @variable.

  • Переменные JuMP могут быть именованными или анонимными.

  • Именованные переменные JuMP имеют вид @variable(model, x). Для именованных переменных:

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

    • Создается переменная Julia x, которая привязывает x к переменной JuMP.

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

  • Анонимные переменные JuMP имеют вид x = @variable(model). Для анонимных переменных:

    • Именем типа String переменной является "". При выводе оно

заменяется на "_[i]", где i — это индекс переменной.

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

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

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

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

Вот пример, проясняющий эти особенности.

julia> model = Model();

julia> x_binding = @variable(model, base_name = "x")
x

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

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

julia> x_binding
x

julia> name(x_binding)
"x"

julia> model[:x_register] = x_binding
x

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

julia> model[:x_register]
x

julia> model[:x_register] === x_binding
true

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

Создание, удаление и изменение границ переменных

Запросить факт наличия границы у переменной можно с помощью методов has_lower_bound, has_upper_bound и is_fixed:

julia> has_lower_bound(x_free)
false

julia> has_upper_bound(x_upper)
true

julia> is_fixed(x_fixed)
true

Если у переменной есть определенная граница, запросить ее значение можно с помощью методов lower_bound, upper_bound и fix_value:

julia> lower_bound(x_interval)
2.0

julia> upper_bound(x_interval)
3.0

julia> fix_value(x_fixed)
4.0

При запросе значения несуществующей границы происходит ошибка.

Для удаления границ переменной используйте методы delete_lower_bound, delete_upper_bound и unfix:

julia> delete_lower_bound(x_lower)

julia> has_lower_bound(x_lower)
false

julia> delete_upper_bound(x_upper)

julia> has_upper_bound(x_upper)
false

julia> unfix(x_fixed)

julia> is_fixed(x_fixed)
false

Для задания или изменения границ переменной используйте методы set_lower_bound, set_upper_bound и fix:

julia> set_lower_bound(x_lower, 1.1)

julia> set_upper_bound(x_upper, 2.1)

julia> fix(x_fixed, 4.1)

Фиксация переменной с существующими границами приводит к ошибке. Чтобы удалить границы перед фиксацией, используйте вызов fix(variable, value; force = true).

julia> model = Model();

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

julia> fix(x, 2)
ERROR: Unable to fix x to 2 because it has existing variable bounds. Consider calling `JuMP.fix(variable, value; force=true)` which will delete existing bounds before fixing the variable.

julia> fix(x, 2; force = true)


julia> fix_value(x)
2.0

Вместо вызова @constraint(model, x == 2) используйте метод fix. Он изменяет границы переменной, в то время как первой вызов добавляет в задачу новое линейное ограничение.

Двоичные переменные

Двоичные переменные ограничены множеством .

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

julia> model = Model();

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

Решатели используют допуски, чтобы решить, удовлетворяет ли переменная двоичному ограничению. Поэтому истинной допустимой областью является \cup [1 - \varepsilon, 1 + \varepsilon]], где зависит от решателя, но обычно равно 1e-6. В результате следует ожидать, что value(x) переменной Bin иногда будет принимать такое значение, как -0.0, 1e-8 или 0.999999.

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

julia> is_binary(x)
true

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

julia> unset_binary(x)

julia> is_binary(x)
false

Двоичные переменные также можно создавать, устанавливая именованный аргумент binary в значение true:

julia> model = Model();

julia> @variable(model, x, binary=true)
x

либо с помощью метода set_binary:

julia> model = Model();

julia> @variable(model, x)
x

julia> set_binary(x)

Целочисленные переменные

Целочисленные переменные ограничены множеством .

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

julia> model = Model();

julia> @variable(model, x, Int)
x

Решатели используют допуски, чтобы решить, удовлетворяет ли переменная целочисленному ограничению. Поэтому истинной допустимой областью является ], где зависит от решателя, но обычно равно 1e-6. В результате следует ожидать, что value(x) переменной Int иногда будет принимать такое значение, как 1e-8 или 2.999999.

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

julia> is_integer(x)
true

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

julia> unset_integer(x)

julia> is_integer(x)
false

Целочисленные переменные также можно создавать, устанавливая именованный аргумент integer в значение true:

julia> model = Model();

julia> @variable(model, x, integer=true)
x

либо с помощью метода set_integer:

julia> model = Model();

julia> @variable(model, x)
x

julia> set_integer(x)

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

Полуцелочисленные и полунепрерывные переменные

Полунепрерывные переменные ограничены множеством ].

Создать полунепрерывную переменную можно с помощью множества Semicontinuous:

julia> model = Model();

julia> @variable(model, x in Semicontinuous(1.5, 3.5))
x

Полуцелочисленные переменные ограничены множеством .

Создать полуцелочисленную переменную можно с помощью множества Semiinteger:

julia> model = Model();

julia> @variable(model, x in Semiinteger(1.0, 3.0))
x

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

Есть два способа предоставить прямое начальное решение (также называемое началом MIP или мягким началом) для каждой переменной:

  • с помощью именованного аргумента start в макросе @variable;

  • с помощью метода set_start_value.

Запросить начальное значение переменной можно с помощью метода start_value. Если начальное значение не задано, start_value возвращает nothing.

julia> model = Model();

julia> @variable(model, x)
x

julia> start_value(x)

julia> @variable(model, y, start = 1)
y

julia> start_value(y)
1.0

julia> set_start_value(y, 2)

julia> start_value(y)
2.0

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

julia> model = Model();

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

julia> start_value.(z)
2-element Vector{Float64}:
 1.0
 4.0

Некоторые решатели не поддерживают начальные значения. Если оптимизатор не поддерживает начальные значения, выдается ошибка MathOptInterface.UnsupportedAttribute{MathOptInterface.VariablePrimalStart}.

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

x = all_variables(model)
x_solution = value.(x)
set_start_value.(x, x_solution)

Кроме того, можно воспользоваться методом set_start_values.

Удаление переменной

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

julia> model = Model();

julia> @variable(model, x)
x

julia> is_valid(model, x)
true

julia> delete(model, x)

julia> is_valid(model, x)
false

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

julia> @variable(model, x)
ERROR: An object of name x 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, :x)` 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[:x]`.
[...]

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

julia> unregister(model, :x)

julia> @variable(model, x)
x

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

Контейнеры переменных

JuMP предоставляет механизм для создания коллекций переменных в трех типах структур данных, которые называются контейнерами.

Это типы Array, DenseAxisArray и SparseAxisArray. Далее рассматривается каждый из них.

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

Массивы

Мы уже рассмотрели создание массива переменных JuMP с помощью синтаксиса x[1:2]. Его можно расширить для создания многомерных массивов переменных JuMP. Например:

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]

К массивам переменных JuMP можно обращаться по индексам и срезам следующим образом:

julia> x[1, 2]
x[1,2]

julia> x[2, :]
2-element Vector{VariableRef}:
 x[2,1]
 x[2,2]

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

julia> model = Model();

julia> @variable(model, x[i=1:2, j=1:2] >= 2i + j)
2×2 Matrix{VariableRef}:
 x[1,1]  x[1,2]
 x[2,1]  x[2,2]

julia> lower_bound.(x)
2×2 Matrix{Float64}:
 3.0  4.0
 5.0  6.0

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

DenseAxisArray

Зачастую требуется создавать массивы, индексы которых не являются целочисленными диапазонами, начинающимися с единицы. Например, может понадобиться создать переменную, индексируемую по названию продукта или местоположению. Синтаксис при этом такой же, как и выше, за исключением того, что в качестве индекса используется произвольный вектор, а не начинающийся с единицы диапазон. Главное отличие в том, что вместо возврата массива Array переменных JuMP возвращает массив DenseAxisArray. Например:

julia> model = Model();

julia> @variable(model, x[1:2, [:A,:B]])
2-dimensional DenseAxisArray{VariableRef,2,...} with index sets:
    Dimension 1, Base.OneTo(2)
    Dimension 2, [:A, :B]
And data, a 2×2 Matrix{VariableRef}:
 x[1,A]  x[1,B]
 x[2,A]  x[2,B]

К массивам DenseAxisArray можно обращаться по индексам и срезам следующим образом:

julia> x[1, :A]
x[1,A]

julia> x[2, :]
1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:
    Dimension 1, [:A, :B]
And data, a 2-element Vector{VariableRef}:
 x[2,A]
 x[2,B]

Границы могут зависеть от индексов:

julia> model = Model();

julia> @variable(model, x[i=2:3, j=1:2:3] >= 0.5i + j)
2-dimensional DenseAxisArray{VariableRef,2,...} with index sets:
    Dimension 1, 2:3
    Dimension 2, 1:2:3
And data, a 2×2 Matrix{VariableRef}:
 x[2,1]  x[2,3]
 x[3,1]  x[3,3]

julia> lower_bound.(x)
2-dimensional DenseAxisArray{Float64,2,...} with index sets:
    Dimension 1, 2:3
    Dimension 2, 1:2:3
And data, a 2×2 Matrix{Float64}:
 2.0  4.0
 2.5  4.5

SparseAxisArray

Третий тип контейнера, который изначально поддерживается JuMP, — это SparseAxisArray. Такие массивы создаются, когда индексы не образуют прямоугольное множество. Например, это верно в случае, когда индексы зависят от предыдущих индексов (так называемое треугольное индексирование). JuMP поддерживает такой вариант следующим образом:

julia> model = Model();

julia> @variable(model, x[i=1:2, j=i:2])
JuMP.Containers.SparseAxisArray{VariableRef, 2, Tuple{Int64, Int64}} with 3 entries:
  [1, 1]  =  x[1,1]
  [1, 2]  =  x[1,2]
  [2, 2]  =  x[2,2]

Посредством особого синтаксиса JuMP можно также создавать переменные условным образом. Этот синтаксис добавляет проверку сравнения, которая зависит от именованных индексов и отделяется от индексов точкой с запятой (;). Например:

julia> model = Model();

julia> @variable(model, x[i=1:4; mod(i, 2)==0])
JuMP.Containers.SparseAxisArray{VariableRef, 1, Tuple{Int64}} with 2 entries:
  [2]  =  x[2]
  [4]  =  x[4]

Замечания по производительности

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

Например:

julia> model = Model();

julia> N = 10
10

julia> S = [(1, 1, 1), (N, N, N)]
2-element Vector{Tuple{Int64, Int64, Int64}}:
 (1, 1, 1)
 (10, 10, 10)

julia> @time @variable(model, x1[i=1:N, j=1:N, k=1:N; (i, j, k) in S])
  0.203861 seconds (392.22 k allocations: 23.977 MiB, 99.10% compilation time)
JuMP.Containers.SparseAxisArray{VariableRef, 3, Tuple{Int64, Int64, Int64}} with 2 entries:
  [1, 1, 1   ]  =  x1[1,1,1]
  [10, 10, 10]  =  x1[10,10,10]

julia> @time @variable(model, x2[S])
  0.045407 seconds (65.24 k allocations: 3.771 MiB, 99.15% compilation time)
1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:
    Dimension 1, [(1, 1, 1), (10, 10, 10)]
And data, a 2-element Vector{VariableRef}:
 x2[(1, 1, 1)]
 x2[(10, 10, 10)]

Первый вариант выполняется медленнее, так как он равносилен следующему:

julia> model = Model();

julia> x1 = Dict{NTuple{3,Int},VariableRef}()
Dict{Tuple{Int64, Int64, Int64}, VariableRef}()

julia> for i in 1:N
           for j in 1:N
               for k in 1:N
                   if (i, j, k) in S
                       x1[i, j, k] = @variable(model, base_name = "x1[$i,$j,$k]")
                   end
               end
           end
       end

julia> x1
Dict{Tuple{Int64, Int64, Int64}, VariableRef} with 2 entries:
  (1, 1, 1)    => x1[1,1,1]
  (10, 10, 10) => x1[10,10,10]

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

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

При создании контейнера переменных JuMP пытается выбрать наиболее ограниченный тип контейнера, в котором могут храниться переменные JuMP. Таким образом, приоритет отдается сначала массиву Array, затем DenseAxisArray, а затем SparseAxisArray. Однако поскольку это происходит во время компиляции, JuMP не всегда делает лучший выбор. Чтобы проиллюстрировать это, рассмотрим следующий пример.

julia> model = Model();

julia> A = 1:2
1:2

julia> @variable(model, x[A])
1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:
    Dimension 1, 1:2
And data, a 2-element Vector{VariableRef}:
 x[1]
 x[2]

Так как значение (и тип) A неизвестны во время анализа, JuMP не может определить, что A представляет собой целочисленный диапазон, начинающийся с единицы. Поэтому JuMP создает массив DenseAxisArray, хотя эти две переменные могли бы храниться в обычном одномерном массиве Array.

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

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

Теперь JuMP создает вектор переменных JuMP вместо DenseAxisArray. При выборе недопустимого типа контейнера выдается ошибка.

Пользовательские контейнеры

Помимо встроенных типов контейнеров, можно создавать собственные коллекции переменных JuMP.

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

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

julia> model = Model();

julia> variables = Dict{Symbol,Array{VariableRef,2}}(
           key => @variable(model, [1:2, 1:2], Symmetric, base_name = "$(key)")
           for key in [:A, :B]
       )
Dict{Symbol, Matrix{VariableRef}} with 2 entries:
  :A => [A[1,1] A[1,2]; A[1,2] A[2,2]]
  :B => [B[1,1] B[1,2]; B[1,2] B[2,2]]

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

using JuMP
model = Model()
@variable(model, x[1:2] >= 0)
# Позднее нужно добавить
@variable(model, x[3:4] >= 0)

Со встроенными типами контейнеров JuMP это невозможно. Однако вместо этого можно использовать обычные типы Julia:

julia> model = Model();

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

julia> append!(x, @variable(model, [1:2], lower_bound = 0, base_name = "y"));

julia> model[:x]
4-element Vector{VariableRef}:
 x[1]
 x[2]
 y[1]
 y[2]

Полуопределенные переменные

Чтобы объявить квадратную матрицу переменных JuMP положительно полуопределенной, передайте PSD дополнительным позиционным аргументом:

julia> model = Model();

julia> @variable(model, x[1:2, 1:2], PSD)
2×2 LinearAlgebra.Symmetric{VariableRef, Matrix{VariableRef}}:
 x[1,1]  x[1,2]
 x[1,2]  x[2,2]

Таким образом обеспечивается симметричность x и неотрицательность всех собственных значений.

x должно быть квадратным двумерным массивом Array переменных JuMP; это не может быть DenseAxisArray или SparseAxisArray.

Симметричные переменные

Чтобы объявить квадратную матрицу переменных JuMP симметричной (но не обязательно положительно полуопределенной), передайте Symmetric дополнительным позиционным аргументом:

julia> model = Model();

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

Макрос @variables

На случай, если имеется много вызовов @variable, JuMP предоставляет макрос @variables, который может улучшить удобочитаемость:

julia> model = Model();

julia> @variables(model, begin
           x
           y[i=1:2] >= i, (start = i, base_name = "Y_$i")
           z, Bin
       end)
(x, VariableRef[Y_1[1], Y_2[2]], z)

julia> print(model)
Feasibility
Subject to
 Y_1[1] ≥ 1
 Y_2[2] ≥ 2
 z binary

Макрос @variables возвращает кортеж определенных переменных.

Именованные аргументы должны быть заключены в скобки.

Ограничение переменных при создании

Все задокументированные на данный момент случаи использования макроса @variable преобразуются в отдельные вызовы для создания переменных и добавления ограничений границ или целочисленности.

Например, @variable(model, x >= 0, Int) равносильно следующему:

julia> model = Model();

julia> @variable(model, x)
x

julia> set_lower_bound(x, 0.0)

julia> set_integer(x)

Важно отметить, что ограничения границ и целочисленности добавляются после создания переменной.

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

Для доступа к специальному синтаксису ограничения переменных при создании используйте in внутри макроса @variable.

Например, следующий код создает вектор переменных, принадлежащих SecondOrderCone:

julia> model = Model();

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

Для сравнения, стандартный синтаксис выглядит следующим образом.

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

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

Альтернативой синтаксису x in Set является использование именованного аргумента set макроса @variable. Это особенно полезно при создании анонимных переменных:

julia> model = Model();

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

Удалить ограничение, связанное с ограниченной при создании переменной, нельзя.

Пример: положительно полуопределенные переменные

Альтернативой синтаксису в разделе Semidefinite variables является объявление матрицы переменных JuMP, положительно полуопределенной с помощью PSDCone:

julia> model = Model();

julia> @variable(model, x[1:2, 1:2] in PSDCone())
2×2 LinearAlgebra.Symmetric{VariableRef, Matrix{VariableRef}}:
 x[1,1]  x[1,2]
 x[1,2]  x[2,2]

Пример: симметричные переменные

Альтернативой синтаксису в разделе Symmetric variables является объявление матрицы переменных JuMP симметричной с помощью SymmetricMatrixSpace:

julia> model = Model();

julia> @variable(model, x[1:2, 1:2] in SymmetricMatrixSpace())
2×2 LinearAlgebra.Symmetric{VariableRef, Matrix{VariableRef}}:
 x[1,1]  x[1,2]
 x[1,2]  x[2,2]

Пример: кососимметричные переменные

Объявить матрицу переменных JuMP кососимметричной можно с помощью SkewSymmetricMatrixSpace:

julia> model = Model();

julia> @variable(model, x[1:2, 1:2] in SkewSymmetricMatrixSpace())
2×2 Matrix{AffExpr}:
 0        x[1,2]
 -x[1,2]  0

Хотя x представляет собой матрицу 2 на 2, в model добавляется только одна переменная решения; остальные элементы в x являются линейными преобразованиями одной переменной.

Пример: эрмитовы положительно полуопределенные переменные

Объявить матрицу переменных JuMP эрмитовой положительно полуопределенной можно с помощью HermitianPSDCone:

julia> model = Model();

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

В результате добавляются четыре вещественные переменные в MOI.HermitianPositiveSemidefiniteConeTriangle:

julia> first(all_constraints(model, Vector{VariableRef}, MOI.HermitianPositiveSemidefiniteConeTriangle))
[real(H[1,1]), real(H[1,2]), real(H[2,2]), imag(H[1,2])] ∈ MathOptInterface.HermitianPositiveSemidefiniteConeTriangle(2)

Пример: эрмитовы переменные

Объявить матрицу переменных JuMP эрмитовой можно с помощью тега Hermitian:

julia> model = Model();

julia> @variable(model, x[1:2, 1:2], Hermitian)
2×2 LinearAlgebra.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])

Это равносильно объявлению переменной в HermitianMatrixSpace:

julia> model = Model();

julia> @variable(model, x[1:2, 1:2] in HermitianMatrixSpace())
2×2 LinearAlgebra.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])

Зачем нужно ограничивать переменные при создании?

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

Однако при использовании direct_model вам, возможно, придется использовать синтаксис ограничения при создании.

Технически переменные, ограниченные при создании, и стандартный синтаксис JuMP различаются тем, что в первом случае вызывается MOI.add_constrained_variables, а во втором — MOI.add_variables, а затем MOI.add_constraint.

Чтобы узнать, требуется ли решателю MOI.add_constrained_variables, проверьте реализацию используемого пакета решателя.

Параметры

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

Параметры реализуются в JuMP посредством переменной решения, ограниченной при создании множеством Parameter.

julia> model = Model();

julia> @variable(model, x);

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

Анонимные параметры создаются с помощью именованного аргумента set:

julia> anon_parameter = @variable(model, set = Parameter(1.0))
_[4]

Для запроса или изменения значения параметра используйте методы parameter_value и set_parameter_value.

julia> parameter_value.(p)
2-element Vector{Float64}:
 1.0
 2.0

julia> set_parameter_value(p[2], 3.0)

julia> parameter_value.(p)
2-element Vector{Float64}:
 1.0
 3.0

Ограничения

Параметры реализованы как переменные решения, принадлежащие множеству Parameter](../api.md#JuMP.Parameter). Если решатель поддерживает множество [MOI.Parameter, он может принять решение заменить все экземпляры переменной параметра связанной константой. Если решатель не поддерживает параметры, он добавляет параметр как переменную решения с фиксированными границами.

Важнейшим следствием такой схемы является то, что в JuMP параметр, умноженный на переменную решения, рассматривается как квадратичное выражение, даже если он эквивалентен линейному выражению.

julia> model = Model();

julia> @variable(model, x);

julia> @variable(model, p in Parameter(2));

julia> px = @expression(model, p * x)
p*x

julia> typeof(px)
QuadExpr (alias for GenericQuadExpr{Float64, GenericVariableRef{Float64}})

Когда следует использовать параметры

Параметры наиболее полезны при решении нелинейных моделей в следующем порядке.

julia> using JuMP, Ipopt


julia> model = Model(Ipopt.Optimizer);


julia> set_silent(model)


julia> @variable(model, x)
x

julia> @variable(model, p in Parameter(1.0))
p

julia> @objective(model, Min, (x - p)^2)
x² - 2 p*x + p²

julia> optimize!(model)


julia> value(x)
1.0

julia> set_parameter_value(p, 5.0)


julia> optimize!(model)


julia> value(x)
5.0

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