Переменные
Термин переменная в области математической оптимизации имеет много значений. Например, переменные оптимизации (также называемые переменными решения) — это неизвестные , которые находятся при решении задачи:
Ситуацию усложняет то, что в 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
|
При создании переменной только с нижней или только с верхней границей, значением которой не является числовой литерал (например, |
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, которое используется при выводе данных и записи в файлы.
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]
|
Для некоторых моделей задание строкового имени для каждой переменной может занимать неоправданно большую долю общего времени, затрачиваемого на построение модели. Чтобы отключить имена типа
Дополнительные сведения см. в разделе 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
|
Вместо вызова |
Двоичные переменные
Двоичные переменные ограничены множеством .
Чтобы создать двоичную переменную, передайте Bin дополнительным позиционным аргументом:
julia> model = Model();
julia> @variable(model, x, Bin)
x
|
Решатели используют допуски, чтобы решить, удовлетворяет ли переменная двоичному ограничению. Поэтому истинной допустимой областью является \cup [1 - \varepsilon, 1 + \varepsilon]], где зависит от решателя, но обычно равно |
Чтобы проверить, является ли переменная двоичной, используйте метод 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
|
Решатели используют допуски, чтобы решить, удовлетворяет ли переменная целочисленному ограничению. Поэтому истинной допустимой областью является ], где зависит от решателя, но обычно равно |
Чтобы проверить, является ли переменная целочисленной, используйте метод 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)
|
Функция |
Полуцелочисленные и полунепрерывные переменные
Полунепрерывные переменные ограничены множеством ].
Создать полунепрерывную переменную можно с помощью множества 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
|
Некоторые решатели не поддерживают начальные значения. Если оптимизатор не поддерживает начальные значения, выдается ошибка |
|
Чтобы установить полученное ранее оптимальное решение в качестве нового начального значения, используйте |
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
|
Метод |
Контейнеры переменных
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 и неотрицательность всех собственных значений.
|
|
Симметричные переменные
Чтобы объявить квадратную матрицу переменных 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
|
Хотя |
Пример: эрмитовы положительно полуопределенные переменные
Объявить матрицу переменных 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 избежать повторения ряда шагов при обработке модели перед ее передачей решателю.