Variables

The term variable in mathematical optimization has many meanings. For example, optimization variables (also called decision variables) are the unknowns $x$ that we are solving for in the problem:

\[\begin{align} & \min_{x \in \mathbb{R}^n} & f_0(x) \\ & \;\;\text{s.t.} & f_i(x) & \in \mathcal{S}_i & i = 1 \ldots m \end{align}\]

To complicate things, Julia uses variable to mean a binding between a name and a value. For example, in the statement:

julia> x = 1
1

x is a variable that stores the value 1.

JuMP uses variable in a third way, to mean an instance of the VariableRef struct. JuMP variables are the link between Julia and the optimization variables inside a JuMP model.

This page explains how to create and manage JuMP variables in a variety of contexts.

Create a variable

Create variables using the @variable macro:

julia> model = Model();

julia> @variable(model, x)
x

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

julia> num_variables(model)
1

Here x is a Julia variable that is bound to a VariableRef object, and we have added 1 decision variable to our model.

To make the binding more explicit, we could have written:

julia> model = Model();

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

but there is no need to in general; the macro does it for us.

When creating a variable, you can also specify variable bounds:

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
Warning

When creating a variable with a single lower- or upper-bound, and the value of the bound is not a numeric literal (for example, 1 or 1.0), the name of the variable must appear on the left-hand side. Putting the name on the right-hand side is an error. For example, to create a variable 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

Containers of variables

The @variable macro also supports creating collections of JuMP variables. We'll cover some brief syntax here; read the Variable containers section for more details.

You can create arrays of JuMP variables:

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]

Index sets can be named, and bounds can depend on those names:

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]

Sets can be any Julia type that supports iteration:

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]

Sets can depend upon previous indices:

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]

and we can filter elements in the sets using the ; syntax:

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]

Registered variables

When you create variables, JuMP registers them inside the model using their corresponding symbol. Get a registered name using 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

Registered names are most useful when you start to write larger models and want to break up the model construction into functions:

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

Anonymous variables

To reduce the likelihood of accidental bugs, and because JuMP registers variables inside a model, creating two variables with the same name is an error:

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]`.
[...]

A common reason for encountering this error is adding variables in a loop.

As a work-around, JuMP provides anonymous variables. Create a scalar valued anonymous variable by omitting the name argument:

julia> model = Model();

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

Anonymous variables get printed as an underscore followed by a unique index of the variable.

Warning

The index of the variable may not correspond to the column of the variable in the solver.

Create a container of anonymous JuMP variables by dropping the name in front of the [:

julia> model = Model();

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

The <= and >= short-hand cannot be used to set bounds on scalar-valued anonymous JuMP variables. Instead, use the lower_bound and upper_bound keywords:

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]

Variable names

In addition to the symbol that variables are registered with, JuMP variables have a String name that is used for printing and writing to file formats.

Get and set the name of a variable using name and 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

Override the default choice of name using the base_name keyword:

julia> model = Model();

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

Note that names apply to each element of the container, not to the container of variables:

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]
Tip

For some models, setting the string name of each variable can take a non-trivial portion of the total time required to build the model. Turn off String names by passing set_string_name = false to @variable:

julia> model = Model();

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

See Disable string names for more information.

Retrieve a variable by name

Retrieve a variable from a model using variable_by_name:

julia> variable_by_name(model, "my_x")
my_x

If the name is not present, nothing will be returned:

julia> variable_by_name(model, "bad_name")

You can only look up individual variables using variable_by_name. Something like this will not work:

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")

To look up a collection of variables, do not use variable_by_name. Instead, register them using the model[:key] = value syntax:

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]

String names, symbolic names, and bindings

It's common for new users to experience confusion relating to JuMP variables. Part of the problem is the overloaded use of "variable" in mathematical optimization, along with the difference between the name that a variable is registered under and the String name used for printing.

Here's a summary of the differences:

  • JuMP variables are created using @variable.
  • JuMP variables can be named or anonymous.
  • Named JuMP variables have the form @variable(model, x). For named variables:
    • The String name of the variable is set to "x".
    • A Julia variable x is created that binds x to the JuMP variable.
    • The name :x is registered as a key in the model with the value x.
  • Anonymous JuMP variables have the form x = @variable(model). For anonymous variables:
    • The String name of the variable is set to "". When printed, this is replaced with "_[i]" where i is the index of the variable.
    • You control the name of the Julia variable used as the binding.
    • No name is registered as a key in the model.
  • The base_name keyword can override the String name of the variable.
  • You can manually register names in the model via model[:key] = value

Here's an example that should make things clearer:

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

Create, delete, and modify variable bounds

Query whether a variable has a bound using has_lower_bound, has_upper_bound, and is_fixed:

julia> has_lower_bound(x_free)
false

julia> has_upper_bound(x_upper)
true

julia> is_fixed(x_fixed)
true

If a variable has a particular bound, query the value of it using lower_bound, upper_bound, and fix_value:

julia> lower_bound(x_interval)
2.0

julia> upper_bound(x_interval)
3.0

julia> fix_value(x_fixed)
4.0

Querying the value of a bound that does not exist will result in an error.

Delete variable bounds using delete_lower_bound, delete_upper_bound, and 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 or update variable bounds using set_lower_bound, set_upper_bound, and fix:

julia> set_lower_bound(x_lower, 1.1)

julia> set_upper_bound(x_upper, 2.1)

julia> fix(x_fixed, 4.1)

Fixing a variable with existing bounds will throw an error. To delete the bounds prior to fixing, use 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
Tip

Use fix instead of @constraint(model, x == 2). The former modifies variable bounds, while the latter adds a new linear constraint to the problem.

Binary variables

Binary variables are constrained to the set $x \in \{0, 1\}$.

Create a binary variable by passing Bin as an optional positional argument:

julia> model = Model();

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

Solvers use tolerances to decide whether a variable satisfies the binary constraint. Thus, the true feasible region is $[-\varepsilon, \varepsilon] \cup [1 - \varepsilon, 1 + \varepsilon]$, where $\varepsilon$ is solver-specific, but typically 1e-6. As a result, you should expect the value(x) of a Bin variable to sometimes take a value like -0.0, 1e-8, or 0.999999.

Check if a variable is binary using is_binary:

julia> is_binary(x)
true

Delete a binary constraint using unset_binary:

julia> unset_binary(x)

julia> is_binary(x)
false

Binary variables can also be created by setting the binary keyword to true:

julia> model = Model();

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

or by using set_binary:

julia> model = Model();

julia> @variable(model, x)
x

julia> set_binary(x)

Integer variables

Integer variables are constrained to the set $x \in \mathbb{Z}$.

Create an integer variable by passing Int as an optional positional argument:

julia> model = Model();

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

Solvers use tolerances to decide whether a variable satisfies the integer constraint. Thus, the true feasible region is $\cup_{z \in \mathbb{Z}}[z - \varepsilon, z + \varepsilon]$, where $\varepsilon$ is solver-specific, but typically 1e-6. As a result, you should expect the value(x) of an Int variable to sometimes take a value like 1e-8, or 2.999999.

Check if a variable is integer using is_integer:

julia> is_integer(x)
true

Delete an integer constraint using unset_integer.

julia> unset_integer(x)

julia> is_integer(x)
false

Integer variables can also be created by setting the integer keyword to true:

julia> model = Model();

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

or by using set_integer:

julia> model = Model();

julia> @variable(model, x)
x

julia> set_integer(x)
Tip

The relax_integrality function relaxes all integrality constraints in the model, returning a function that can be called to undo the operation later on.

Semi-integer and semi-continuous variables

Semi-continuous variables are constrained to the set $x \in \{0\} \cup [l, u]$.

Create a semi-continuous variable using the Semicontinuous set:

julia> model = Model();

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

Semi-integer variables are constrained to the set $x \in \{0\} \cup \{l, l+1, \dots, u\}$.

Create a semi-integer variable using the Semiinteger set:

julia> model = Model();

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

Start values

There are two ways to provide a primal starting solution (also called MIP-start or a warmstart) for each variable:

The starting value of a variable can be queried using start_value. If no start value has been set, start_value will return 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

The start keyword argument can depend on the indices of a variable container:

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
Warning

Some solvers do not support start values. If a solver does not support start values, an MathOptInterface.UnsupportedAttribute{MathOptInterface.VariablePrimalStart} error will be thrown.

Tip

To set the optimal solution from a previous solve as a new starting value, use all_variables to get a vector of all the variables in the model, then run:

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

Alternatively, use set_start_values.

Delete a variable

Use delete to delete a variable from a model. Use is_valid to check if a variable belongs to a model and has not been deleted.

julia> model = Model();

julia> @variable(model, x)
x

julia> is_valid(model, x)
true

julia> delete(model, x)

julia> is_valid(model, x)
false

Deleting a variable does not unregister the corresponding name from the model. Therefore, creating a new variable of the same name will throw an error:

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]`.
[...]

After calling delete, call unregister to remove the symbolic reference:

julia> unregister(model, :x)

julia> @variable(model, x)
x
Info

delete does not automatically unregister because we do not distinguish between names that are automatically registered by JuMP macros and names that are manually registered by the user by setting values in object_dictionary. In addition, deleting a variable and then adding a new variable of the same name is an easy way to introduce bugs into your code.

Variable containers

JuMP provides a mechanism for creating collections of variables in three types of data structures, which we refer to as containers.

The three types are Arrays, DenseAxisArrays, and SparseAxisArrays. We explain each of these in the following.

Tip

You can read more about containers in the Containers section.

Arrays

We have already seen the creation of an array of JuMP variables with the x[1:2] syntax. This can be extended to create multi-dimensional arrays of JuMP variables. For example:

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]

Arrays of JuMP variables can be indexed and sliced as follows:

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

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

Variable bounds can depend upon the indices:

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 will form an Array of JuMP variables when it can determine at compile time that the indices are one-based integer ranges. Therefore x[1:b] will create an Array of JuMP variables, but x[a:b] will not. If JuMP cannot determine that the indices are one-based integer ranges (for example, in the case of x[a:b]), JuMP will create a DenseAxisArray instead.

DenseAxisArrays

We often want to create arrays where the indices are not one-based integer ranges. For example, we may want to create a variable indexed by the name of a product or a location. The syntax is the same as that above, except with an arbitrary vector as an index as opposed to a one-based range. The biggest difference is that instead of returning an Array of JuMP variables, JuMP will return a DenseAxisArray. For example:

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]

DenseAxisArrays can be indexed and sliced as follows:

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]

Bounds can depend upon indices:

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

SparseAxisArrays

The third container type that JuMP natively supports is SparseAxisArray. These arrays are created when the indices do not form a rectangular set. For example, this applies when indices have a dependence upon previous indices (called triangular indexing). JuMP supports this as follows:

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]

We can also conditionally create variables via a JuMP-specific syntax. This syntax appends a comparison check that depends upon the named indices and is separated from the indices by a semi-colon (;). For example:

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]

Performance considerations

When using the semi-colon as a filter, JuMP iterates over all indices and evaluates the conditional for each combination. If there are many index dimensions and a large amount of sparsity, this can be inefficient.

For example:

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)]

The first option is slower because it is equivalent to:

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]

If performance is a concern, explicitly construct the set of indices instead of using the filtering syntax.

Forcing the container type

When creating a container of JuMP variables, JuMP will attempt to choose the tightest container type that can store the JuMP variables. Thus, it will prefer to create an Array before a DenseAxisArray and a DenseAxisArray before a SparseAxisArray. However, because this happens at compile time, JuMP does not always make the best choice. To illustrate this, consider the following example:

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]

Since the value (and type) of A is unknown at parsing time, JuMP is unable to infer that A is a one-based integer range. Therefore, JuMP creates a DenseAxisArray, even though it could store these two variables in a standard one-dimensional Array.

We can share our knowledge that it is possible to store these JuMP variables as an array by setting the container keyword:

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

JuMP now creates a vector of JuMP variables instead of a DenseAxisArray. Choosing an invalid container type will throw an error.

User-defined containers

In addition to the built-in container types, you can create your own collections of JuMP variables.

Tip

This is a point that users often overlook: you are not restricted to the built-in container types in JuMP.

For example, the following code creates a dictionary with symmetric matrices as the values:

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]]

Another common scenario is a request to add variables to existing containers, for example:

using JuMP
model = Model()
@variable(model, x[1:2] >= 0)
# Later I want to add
@variable(model, x[3:4] >= 0)

This is not possible with the built-in JuMP container types. However, you can use regular Julia types instead:

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]

Semidefinite variables

Declare a square matrix of JuMP variables to be positive semidefinite by passing PSD as an optional positional argument:

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]

This will ensure that x is symmetric, and that all of its eigenvalues are nonnegative.

Note

x must be a square 2-dimensional Array of JuMP variables; it cannot be a DenseAxisArray or a SparseAxisArray.

Symmetric variables

Declare a square matrix of JuMP variables to be symmetric (but not necessarily positive semidefinite) by passing Symmetric as an optional positional argument:

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]

The @variables macro

If you have many @variable calls, JuMP provides the macro @variables that can improve readability:

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

The @variables macro returns a tuple of the variables that were defined.

Note

Keyword arguments must be contained within parentheses.

Variables constrained on creation

All uses of the @variable macro documented so far translate into separate calls for variable creation and the adding of any bound or integrality constraints.

For example, @variable(model, x >= 0, Int), is equivalent to:

julia> model = Model();

julia> @variable(model, x)
x

julia> set_lower_bound(x, 0.0)

julia> set_integer(x)

Importantly, the bound and integrality constraints are added after the variable has been created.

However, some solvers require a set specifying the variable domain to be given when the variable is first created. We say that these variables are constrained on creation.

Use in within @variable to access the special syntax for constraining variables on creation.

For example, the following creates a vector of variables that belong to the SecondOrderCone:

julia> model = Model();

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

For contrast, the standard syntax is as follows:

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)

An alternate syntax to x in Set is to use the set keyword of @variable. This is most useful when creating anonymous variables:

julia> model = Model();

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

You cannot delete the constraint associated with a variable constrained on creation.

Example: positive semidefinite variables

An alternative to the syntax in Semidefinite variables, declare a matrix of JuMP variables to be positive semidefinite using 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]

Example: symmetric variables

As an alternative to the syntax in Symmetric variables, declare a matrix of JuMP variables to be symmetric using 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]

Example: skew-symmetric variables

Declare a matrix of JuMP variables to be skew-symmetric using 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
Note

Even though x is a 2 by 2 matrix, only one decision variable is added to model; the remaining elements in x are linear transformations of the single variable.

Example: Hermitian positive semidefinite variables

Declare a matrix of JuMP variables to be Hermitian positive semidefinite using 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])

This adds 4 real variables in the 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)

Example: Hermitian variables

Declare a matrix of JuMP variables to be Hermitian using the Hermitian tag:

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])

This is equivalent to declaring the variable in 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])

Why use variables constrained on creation?

For most users, it does not matter if you use the constrained on creation syntax. Therefore, use whatever syntax you find most convenient.

However, if you use direct_model, you may be forced to use the constrained on creation syntax.

The technical difference between variables constrained on creation and the standard JuMP syntax is that variables constrained on creation calls MOI.add_constrained_variables, while the standard JuMP syntax calls MOI.add_variables and then MOI.add_constraint.

Consult the implementation of solver package you are using to see if your solver requires MOI.add_constrained_variables.

Parameters

Some solvers have explicit support for parameters, which are constants in the model that can be efficiently updated between solves.

JuMP implements parameters by a decision variable constrained on creation to the Parameter set.

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]

Create anonymous parameters using the set keyword:

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

Use parameter_value and set_parameter_value to query or update the value of a parameter.

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

Use is_parameter and ParameterRef to check if the variable is a parameter and to get the constraint that makes the variable a parameter.

julia> is_parameter(p[1])
true

julia> is_parameter(x)
false

julia> ParameterRef(p[2])
p[2] ∈ MathOptInterface.Parameter{Float64}(3.0)

Limitations

Parameters are implemented as decision variables belonging to the Parameter set. If the solver supports the MOI.Parameter set, it may decide to replace all instances of the parameter variable by the associated constant. If the solver does not support parameters, it will add the parameter as a decision variable with fixed bounds.

The most important implication of this design is that JuMP treats a parameter multiplied by a decision variable as a quadratic expression, even though it is equivalent to a linear expression.

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}})

When to use a parameter

Parameters are most useful when solving nonlinear models in a sequence:

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

Using parameters can be faster than creating a new model from scratch with updated data because JuMP is able to avoid repeating a number of steps in processing the model before handing it off to the solver.