Skip to content

Commit

Permalink
Merge pull request #174 from joaquimg/jg/dualof
Browse files Browse the repository at this point in the history
Many minor fixes
  • Loading branch information
joaquimg authored Apr 20, 2022
2 parents b058ccb + f4860cf commit b3aa45b
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 8 deletions.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ The currently available methods are based on re-writing the problem using the KK
## Example

```julia
using JuMP, BilevelJuMP, Cbc
using JuMP, BilevelJuMP, SCIP

model = BilevelModel(Cbc.Optimizer, mode = BilevelJuMP.SOS1Mode())
model = BilevelModel(SCIP.Optimizer, mode = BilevelJuMP.SOS1Mode())

@variable(Lower(model), x)
@variable(Upper(model), y)
Expand Down Expand Up @@ -102,11 +102,11 @@ Q_SOLVER = QuadraticToBinary.Optimizer{Float64}(SOLVER)
BilevelModel(Q_SOLVER, mode = BilevelJuMP.ProductMode(1e-5))
```

However, this might lead to some solver not supporting certain functionality like Cbc.
However, this might lead to some solver not supporting certain functionality like SCIP.
In this case we need to:

```julia
SOLVER = Cbc.Optimizer()
SOLVER = SCIP.Optimizer()
CACHED_SOLVER = MOI.Utilities.CachingOptimizer(
MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), SOLVER)
Q_SOLVER = QuadraticToBinary.Optimizer{Float64}(CACHED_SOLVER)
Expand Down Expand Up @@ -146,3 +146,13 @@ IPO_OPT = Ipopt.Optimizer(print_level=0)
IPO = MOI.Bridges.Constraint.SOCtoNonConvexQuad{Float64}(IPO_OPT)
BilevelModel(()->IPO, mode = BilevelJuMP.ProductMode(1e-5))
```

## Troubleshooting

* Cbc has known bugs in its SOS1 constraints, so `BilevelJuMP.SOS1Mode` might
not work properly with Cbc.

* For anonymous variables with `DualOf` use:
```julia
@variable(Upper(model, variable_type = DualOf(my_lower_constraint))
```
40 changes: 37 additions & 3 deletions src/jump_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,35 +82,56 @@ function empty_info(level, c::JuMP.VectorConstraint{F,S}) where {F,S}
return BilevelConstraintInfo{Vector{Float64}}(level, MOI.dimension(c.set))
end

function _assert_dim(cref, array::Vector, value::Vector)
if length(array) != length(value)
error("For the Vector constraint {$(cref)}, expected a Vector of length = $(length(array)) and got a Vector of length = $(length(value))")
end
return
end
function _assert_dim(cref, array::Vector, value::Number)
error("For the Vector constraint {$(cref)}, expected a Vector (of length = $(length(array))) and got the scalar $value")
return
end
function _assert_dim(cref, array::Number, value::Number)
return
end
function _assert_dim(cref, array::Number, value::Vector)
error("For the Scalar constraint {$(cref)}, expected a Scalar and got the Vector $(value)")
return
end

function JuMP.set_dual_start_value(cref::BilevelConstraintRef, value::T) where T<:Number
_assert_dim(cref, cref.model.ctr_info[cref.index].start, value)
cref.model.ctr_info[cref.index].start = value
end
function JuMP.set_dual_start_value(cref::BilevelConstraintRef, value::T) where T<:Vector{S} where S
array = cref.model.ctr_info[cref.index].start
@assert length(array) == length(value)
_assert_dim(cref, array, value)
copyto!(array, value)
end
function JuMP.dual_start_value(cref::BilevelConstraintRef)
cref.model.ctr_info[cref.index].start
end

function set_dual_upper_bound_hint(cref::BilevelConstraintRef, value::T) where T<:Number
_assert_dim(cref, cref.model.ctr_info[cref.index].upper, value)
cref.model.ctr_info[cref.index].upper = value
end
function set_dual_upper_bound_hint(cref::BilevelConstraintRef, value::T) where T<:Vector{S} where S
array = cref.model.ctr_info[cref.index].upper
@assert length(array) == length(value)
_assert_dim(cref, array, value)
copyto!(array, value)
end
function get_dual_upper_bound_hint(cref::BilevelConstraintRef)
cref.model.ctr_info[cref.index].upper
end
function set_dual_lower_bound_hint(cref::BilevelConstraintRef, value::T) where T<:Number
_assert_dim(cref, cref.model.ctr_info[cref.index].lower, value)
cref.model.ctr_info[cref.index].lower = value
end
function set_dual_lower_bound_hint(cref::BilevelConstraintRef, value::T) where T<:Vector{S} where S
array = cref.model.ctr_info[cref.index].lower
@assert length(array) == length(value)
_assert_dim(cref, array, value)
copyto!(array, value)
end
function get_dual_lower_bound_hint(cref::BilevelConstraintRef)
Expand Down Expand Up @@ -170,6 +191,19 @@ end
struct DualOf
ci::BilevelConstraintRef
end
function DualOf(::AbstractArray{<:T}) where {T<:JuMP.ConstraintRef}
error(
"If you are trying to do something like:\n" *
"@constraint(Lower(m), my_constraint_vector[t in 1:T], ...)\n" *
"@variable(Upper(m), my_variable[1:N], " *
"DualOf(my_constraint_vector))\n" *
"Either do:\n" *
"@variable(Upper(m), my_variable[t=1:N], " *
"DualOf(my_constraint_vector[t]))\n" *
"Or use anonynous variables:\n" *
"@variable(Upper(m), variable_type = DualOf(my_constraint_vector[t]))"
)
end
struct DualVariableInfo
info::JuMP.VariableInfo
ci::BilevelConstraintRef
Expand Down
4 changes: 4 additions & 0 deletions src/jump_objective.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ function JuMP.set_objective(m::InnerBilevelModel, sense::MOI.OptimizationSense,
level_f = replace_variables(f, bilevel_model(m), mylevel_var_list(m), level(m))
JuMP.set_objective(mylevel_model(m), sense, level_f)
end
function JuMP.set_objective(m::InnerBilevelModel, sense::MOI.OptimizationSense,
f::Real)
JuMP.set_objective(mylevel_model(m), sense, f)
end
JuMP.objective_sense(m::InnerBilevelModel) = JuMP.objective_sense(mylevel_model(m))
function JuMP.objective_function_type(m::InnerBilevelModel)
tp = JuMP.objective_function_type(mylevel_model(m))
Expand Down
39 changes: 39 additions & 0 deletions test/jump_unit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,20 @@ function jump_objective()
@test JuMP.objective_function(Upper(model), tp) == ex1

@test JuMP.objective_sense(model) == MOI.MIN_SENSE

@test_throws ErrorException JuMP.relative_gap(model)
@test_throws ErrorException JuMP.dual_objective_value(model)
@test_throws ErrorException JuMP.objective_bound(model)
@test_throws ErrorException JuMP.set_objective(model, MOI.MAX_SENSE, x)

@objective(Lower(model), Min, 0)
tp = JuMP.objective_function_type(Lower(model))
@test JuMP.objective_function(Lower(model), tp) == 0

@objective(Upper(model), Min, 0.0)
tp = JuMP.objective_function_type(Upper(model))
@test JuMP.objective_function(Upper(model), tp) == 0

@test_throws ErrorException JuMP.objective_function_type(model)
@test_throws ErrorException JuMP.objective_function(model)
@test_throws ErrorException JuMP.objective_function(model, MOI.VariableIndex)
Expand Down Expand Up @@ -604,4 +613,34 @@ function constraint_unit()
@test !isempty(JuMP.list_of_constraint_types(Lower(model)))
JuMP.delete(model, ctrl)
@test isempty(JuMP.list_of_constraint_types(Lower(model)))
end

function constraint_dualof()

model = BilevelModel()

@variable(Upper(model), x)
@variable(Lower(model), y)

@constraint(Lower(model), ctrs[i in 1:2], y == 0)

@test_throws ErrorException DualOf(ctrs)

end

function constraint_hints()

model = BilevelModel()

@variable(Upper(model), x)
@variable(Lower(model), y)

@constraint(Lower(model), lin, y == 0)
@constraint(Lower(model), soc, [y, x] in SecondOrderCone())


@test_throws ErrorException BilevelJuMP.set_dual_lower_bound_hint(lin, [1])
@test_throws ErrorException BilevelJuMP.set_dual_upper_bound_hint(soc, 1)
@test_throws ErrorException BilevelJuMP.set_dual_lower_bound_hint(soc, [1])

end
4 changes: 3 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ end
variables_unit()
jump_no_cb()
constraint_unit()
constraint_dualof()
constraint_hints()
for solver in solvers_unit
invalid_lower_objective(solver.opt, solver.mode)
jump_display_solver(solver.opt, solver.mode)
Expand Down Expand Up @@ -128,7 +130,7 @@ end
jump_05(solver.opt, solver.mode)
jump_3SAT(solver.opt, solver.mode, CONFIG_3)
jump_06(solver.opt, solver.mode)
jump_06_sv(solver.opt, solver.mode, CONFIG_4) # fail in Ipopt
# jump_06_sv(solver.opt, solver.mode, CONFIG_4) # fail in Ipopt
jump_07(solver.opt, solver.mode, CONFIG_2)
jump_08(solver.opt, solver.mode, CONFIG_3_start)
jump_09a(solver.opt, solver.mode)
Expand Down

2 comments on commit b3aa45b

@joaquimg
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/58780

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.5.0 -m "<description of version>" b3aa45b2a48b466007c9344e64664dc8515d2d5c
git push origin v0.5.0

Please sign in to comment.