From 37b95b4e86069eb9b694f971b265e80c0955b34e Mon Sep 17 00:00:00 2001 From: joaquim Date: Wed, 20 Apr 2022 01:09:07 -0300 Subject: [PATCH 1/5] Protect DualOf usage --- src/jump_constraints.jl | 13 +++++++++++++ test/jump_unit.jl | 13 +++++++++++++ test/runtests.jl | 1 + 3 files changed, 27 insertions(+) diff --git a/src/jump_constraints.jl b/src/jump_constraints.jl index e73b62c1..5464066d 100644 --- a/src/jump_constraints.jl +++ b/src/jump_constraints.jl @@ -170,6 +170,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 diff --git a/test/jump_unit.jl b/test/jump_unit.jl index fe96d153..4eaf5730 100644 --- a/test/jump_unit.jl +++ b/test/jump_unit.jl @@ -604,4 +604,17 @@ 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 DualOf(ctrs) + end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 1473f799..8bca0a4e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -100,6 +100,7 @@ end variables_unit() jump_no_cb() constraint_unit() + constraint_dualof() for solver in solvers_unit invalid_lower_objective(solver.opt, solver.mode) jump_display_solver(solver.opt, solver.mode) From e703e49c8bb8d3d127d862bf1b6ab1233efd4366 Mon Sep 17 00:00:00 2001 From: joaquim Date: Wed, 20 Apr 2022 01:11:36 -0300 Subject: [PATCH 2/5] fix --- test/jump_unit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jump_unit.jl b/test/jump_unit.jl index 4eaf5730..212df289 100644 --- a/test/jump_unit.jl +++ b/test/jump_unit.jl @@ -615,6 +615,6 @@ function constraint_dualof() @constraint(Lower(model), ctrs[i in 1:2], y == 0) - @test_throws DualOf(ctrs) + @test_throws ErrorException DualOf(ctrs) end \ No newline at end of file From 4c7185b6ef8d4d65efda031a748457ee605eddec Mon Sep 17 00:00:00 2001 From: joaquim Date: Wed, 20 Apr 2022 01:26:32 -0300 Subject: [PATCH 3/5] improve readme and allow constant obj --- README.md | 18 ++++++++++++++---- src/jump_objective.jl | 4 ++++ test/jump_unit.jl | 9 +++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0ab11712..c7f5a1b5 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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) @@ -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)) +``` diff --git a/src/jump_objective.jl b/src/jump_objective.jl index 7d0a6de4..cf6569f4 100644 --- a/src/jump_objective.jl +++ b/src/jump_objective.jl @@ -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)) diff --git a/test/jump_unit.jl b/test/jump_unit.jl index 212df289..0ab44cd2 100644 --- a/test/jump_unit.jl +++ b/test/jump_unit.jl @@ -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) From 52b1a074ea50598a164e0b02b175aaea7ecc5d2b Mon Sep 17 00:00:00 2001 From: joaquim Date: Wed, 20 Apr 2022 02:03:52 -0300 Subject: [PATCH 4/5] add errors to bound hints --- src/jump_constraints.jl | 27 ++++++++++++++++++++++++--- test/jump_unit.jl | 17 +++++++++++++++++ test/runtests.jl | 1 + 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/jump_constraints.jl b/src/jump_constraints.jl index 5464066d..01f836fb 100644 --- a/src/jump_constraints.jl +++ b/src/jump_constraints.jl @@ -82,12 +82,31 @@ 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) @@ -95,22 +114,24 @@ function JuMP.dual_start_value(cref::BilevelConstraintRef) 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) diff --git a/test/jump_unit.jl b/test/jump_unit.jl index 0ab44cd2..84cbf616 100644 --- a/test/jump_unit.jl +++ b/test/jump_unit.jl @@ -626,4 +626,21 @@ function constraint_dualof() @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 \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 8bca0a4e..d99bed5d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -101,6 +101,7 @@ end 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) From f4860cfbcd57bf03d097714e2c1ca049d23001f1 Mon Sep 17 00:00:00 2001 From: joaquim Date: Wed, 20 Apr 2022 02:07:05 -0300 Subject: [PATCH 5/5] disable unstabel test test --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index d99bed5d..e14cc47c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -130,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)