I am trying to break a BilevelJuMP
model into smaller functions (similar to what is recommended for a JuMP model
when working with complex problems). My question is whether the models written below are all interpreted as the same by the BilevelJuMP
package:
Base formulation: (from BilevelJuMP GitHub page)
using JuMP, BilevelJuMP, Gurobi
model = BilevelModel(Gurobi.Optimizer, mode = BilevelJuMP.SOS1Mode())
@variable(Lower(model), x)
@variable(Upper(model), y)
@objective(Upper(model), Min, 3x + y)
@constraints(Upper(model), begin
x <= 5
y <= 8
y >= 0
end)
@objective(Lower(model), Min, -x)
@constraints(Lower(model), begin
x + y <= 8
4x + y >= 8
2x + y <= 13
2x - 7y <= 0
end)
optimize!(model)
objective_value(model) # = 3 * (3.5 * 8/15) + 8/15 # = 6.13...
value(x) # = 3.5 * 8/15 # = 1.86...
value(y) # = 8/15 # = 0.53...
Representation 1: (Breaking into different functions)
using JuMP, BilevelJuMP, Gurobi
function add_var(model)
@variable(Lower(model), x)
@variable(Upper(model), y)
return
end
function add_cons(model)
x = model[:x]
y = model[:y]
@constraints(Upper(model), begin
x <= 5
y <= 8
y >= 0
end)
@constraints(Lower(model), begin
x+y <= 8
4x+y >= 8
x+y <=13
2x-7y <= 0
end)
return
end
function add_objectives(model)
x = model[:x]
y = model[:y]
@objective(Upper(model), Min, 3x+y)
@objective(Lower(model), Min, -x)
return
end
function solve()
model = BilevelModel(Gurobi.Optimizer, mode=BilevelJuMP.SOS1Mode())
add_var(model)
add_cons(model)
add_objectives(model)
optimize!(model)
println(objective_value(model))
println(value(model[:x]))
println(value(model[:y]))
end
solve()
Are representation 1 and base formulation equivalent? I get the same solution from both; however, my main concerns are the lines x=model[:x]
and y=model[:y]
because variables and constraints here are attached to the lower- and upper-level model separately, I wanted to confirm if this is indeed a correct way to write the model.
If they are equivalent, can I further split the functions for lower and upper variables, constraints, and objectives? Example:
function add_var_lower(model)
@variable(Lower(model), x)
return
end
function add_var_upper(model)
@variable(Upper(model), y)
return
end
function add_cons_lower(model)
x = model[:x]
y = model[:y]
@constraints(Lower(model), begin
x+y <= 8
4x+y >= 8
x+y <=13
2x-7y <= 0
end)
return
end
# and so on...and then I will have solve function as:
function solve()
model = BilevelModel(Gurobi.Optimizer, mode=BilevelJuMP.SOS1Mode())
add_var_lower(model) #lower-level variables
add_var_upper(model) #upper-level variables
add_cons_lower(model) #lower-level constraints
add_cons_upper(model) #upper-level constraints
add_objective_lower(model) #lower-level objective
add_objective_upper(model) #upper-level objective
optimize!(model)
println(objective_value(model))
println(value(model[:x]))
println(value(model[:y]))
end
solve()
I tried this and it still gives me the same solution; however, again, I want to confirm whether it is safe to do so? Thanks.