Scope of variables in optimisation

Hello, folks! I am new to JuMP, so my apologies if it sounds a naive question.

I wrap an optimisation problem in a function and then run it by changing input data in a loop. I define the variables as global so that these can be called in a loop; however, I understand that global variables should be avoided. I have given a snapshot of the code.

I would like to pick your brains on:
a. Can the problem below be defined without use of global variables?
b. If I have to vary the range for which variable y <= Threshold is valid, I change these values in a loop as shown in the code, though I am not sure if it is a recommended way of doing it. Reading JuMP documentation, my understanding is that modifying right hand side of constraint is possible; however, here it concerns modifying limits on the variable. Do I need to remove the limits on the variable y and instead define it as a separate constraint? (see the way I have done it below).

I will appreciate any help.

function BuildModel(MXData,Threshold)
    model = Model(CPLEX.Optimizer)
    @variable(model,x[1:3])
    global x
    @variable(model,y <= Threshold)
    global y
    @objective(model,Max,sum(MXData[i]*x[i] for i=1:3)
    @contraint(model, x[1]+y<=6)
    return model
end
Threshold =3
# Run optimisation by updating data
for k = 1:6
    MXData = # read data from files 
    Generator = BuildModel(MXData,Threshold)
    optimize!(Generator)
    FirstVariable = JuMP.value.(x)
    SecondVariable = JuMP.value.(y)
end
# Vary the limit of variable y
MXData = # read data from the relevant file
for Threshold = -0.2:0.01:3
    Generator = BuildModel(MXData,Threshold)
    optimize!(Generator)
    FirstVariable = JuMP.value.(x)
    SecondVariable = JuMP.value.(y)
end

It is possible in JuMP to create a variable with a name, and then retrieve it from a model using that name. One way to do that is to index the model with a Symbol

julia> m = Model();

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

julia> m[:x]
3-element Array{VariableRef,1}:
 x[1]
 x[2]
 x[3]

As for updating the constraints, if you are not deleting the previous constraints, or if your new threshold is not less than or equal to your previous threshold, then you are not actually updating the problem in any way. So you can

  1. iterate through the thresholds from largest to smallest (so that y <= Threshold is always a tighter constraint than previously) or
  2. delete constraints of the form y <= Threshold each time (probably JuMP does this under the hood anyway if you follow the first case.). Or
  3. change the right hand side using set_normalized_rhs

You can name (and access by indexing) constraints just like variables in order to modify or delete them. If you didn’t want to do that, you could also return them at the end of the function and keep track of them manually (@constraint returns a ConstraintRef).

Also, just a small note on julia style that maybe you are already aware of: the widely adopted style convention is to give function names and variables lowercase names (with underscores between words if necessary) like build_model rather than BuildModel. TitleCase is reserved for types and modules, and global constants are often declared in all caps.

Hope this helps!

1 Like

See the documentation, Variables · JuMP.

In particular, it discusses set_upper_bound, and mentions the model[:x] trick.

You can also just return x from the function.

function foo()
    model = Model()
    @variable(model, x)
    return model, x
end

function bar()
    model, x = foo()
    println(x)
end
2 Likes

Thank you both @tomerarnon and @odow

Very helpful. There are few points that I would like to clarify, if you don’t mind:

a. The model[:x] trick would return variable x. I cannot see any example of how to access value of variables x and y using this trick.

Using this symbol trick how optimisation results i.eJuMP.value.(x) and JuMP.value.(y) can be accessed?

As, in my code,

Generator = BuildModel(MXData,Threshold)
Generator[:x] # This would return variable x rater than JuMP.value.(x)

I have figured out how to access the values by making the optimisation function to return variables x and y.
c. With regards to using set_upper_bound function, would the following be a correct way of applying it?

for Threshold = -0.2:0.01:3
set_upper_bound(y, Threshold)
Generator = BuildModel(MXData,Threshold)

Thanks again for helping out

function foo()
    model = Model()
    @variable(model, x)
    return model
end

function bar()
    model = foo()
    x = model[:x]
    value(x)
end

You need to call set_upper_bound on y after it has been built.

using JuMP, Clp
model = Model(Clp.Optimizer)
@variable(model, x >= 0)
@objective(model, Min)
for l = 1:4
    set_lower_bound(x, l)
    optimize!(model)
    @show value(x)
end
1 Like

Ok cool. One last thing, if I may check. Deleting variables is explained in the documentation but it does not seem to extend to objectives: Variables · JuMP

As in your code above, objective is to minimise and after running the for loop, say you want to call it again to optimise something but change the objective sense to Max. There is some description of low level functions which are not recommended: Objective · JuMP

The easy way to do this seems to be is to change the objective in the function to:
@objective(model,sense,sum(MXData[i]*x[i] for i=1:3)
In for loop before building the model and calling optimize! define:
sense = MOI.MIN_SENSE
After, this if one needs to re-optimise for something if the objective is to maximise, change sense to:
sense = MOI.MAX_SENSE and rebuild the model.

Is this a correct interpretation?

Just call @optimize multiple times:

model = Model()
@variable(model, 0 <= x <= 1)
@objective(model, Min, x)
optimize!(model)
@objective(model, Max, x)
optimize!(model)

If you’re only changing the sense

model = Model()
@variable(model, 0 <= x <= 1)
@objective(model, Min, x)
optimize!(model)
set_objective_sense(model, MOI.MAX_SENSE)
optimize!(model)

https://www.juliaopt.org/JuMP.jl/stable/objective/#JuMP.set_objective_sense

Thank you