JuMP variable container with repeated index

I wonder how JuMP experts would create an SDP variable over some index matrix constrained during creation. From the docs, it is recommended to constraint variable upon creation if possible. Can the following be improved (if any)?

using JuMP
m = Model()
inds = [1 2 3; 2 4 5; 3 5 5]
@variable(m, x[unique(vec(inds))]) # create independent scalar variables
@constraint(m, x[inds]>=0, PSDCone()) # then add PSD constraint
@variable(m, y[inds]) # ERROR: Repeated index 2. Index sets must have unique elements.
@variable(m, z[1:3,1:3], PSD) # need to add further equality constraints
1 Like

This is a nice example. I see why you got confused! Let me try and talk you through what’s happening.

julia> using JuMP

julia> m = Model()
A JuMP Model
Feasibility problem with:
Variables: 0
Model mode: AUTOMATIC
CachingOptimizer state: NO_OPTIMIZER
Solver name: No optimizer attached.

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

julia> @variable(m, x[unique(vec(inds))]) # create independent scalar variables
1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:
    Dimension 1, [1, 2, 3, 4, 5]
And data, a 5-element Vector{VariableRef}:
 x[1]
 x[2]
 x[3]
 x[4]
 x[5]

So far so good. You created a vector of variables with 5 elements, and the keys are 1 through 5.

julia> @constraint(m, x[inds]>=0, PSDCone())
ERROR: KeyError: key [1 2 3; 2 4 5; 3 5 5] not found

Hmm. JuMP tried to use inds as a key, instead of creating a matrix using those indices.

You want to use:

julia> [x[i] for i in inds]
3×3 Matrix{VariableRef}:
 x[1]  x[2]  x[3]
 x[2]  x[4]  x[5]
 x[3]  x[5]  x[5]

So:

julia> @constraint(m, [x[i] for i in inds] >= 0, PSDCone())
[x[1]  x[2]  x[3];
 x[2]  x[4]  x[5]; ∈ PSDCone()
 x[3]  x[5]  x[5]]

What happened in the next error?

julia> @variable(m, y[inds])
ERROR: Repeated index 2. Index sets must have unique elements.

JuMP tried to create something like @variable(m, y[[1 2 3 2 4 5 3 5 5]]) by calling vec(inds). You either need to use unique again, or perhaps use y[1:3, 1:3] to create a 3x3 matrix.

I would write your model as:

inds = [1 2 3; 2 4 5; 3 5 5]
model = Model()
@variable(model, x[unique(inds)])
@constraint(model, [x[i] for i in inds] >= 0, PSDCone())

If you know that inds is going to contain the integers 1:N, then you can even write

inds = [1 2 3; 2 4 5; 3 5 5]
model = Model()
@variable(model, x[unique(inds)], container = Array)
@constraint(model, [x[i] for i in inds] >= 0, PSDCone())

and now x is a Vector not a DenseAxisArray:

julia> @variable(model, x[unique(inds)], container = Array)
5-element Vector{VariableRef}:
x[1]
x[2]
x[3]
x[4]
x[5]
2 Likes

Many thanks @odow for taking time and write such a detailed reply. I did not notice that @constraint(m, x[inds]>=0, PSDCone()) is wrong because I used M = x[inds]; @constraint(m, M>=0, PSDCone()) in my program. Your solution @constraint(m, [x[i] for i in inds] >= 0, PSDCone()) is nicer than me.

I also had a careful read of the documentation and I think the syntax above does not create variable constrained on creation Variables · JuMP

Is there any heuristic to follow in terms of performance when creating variables? I know this is not really a bottleneck for most usecases, unless one is implementing a very badly formulated optimization.