Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider variable bounds clean #191

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions src/jump.jl
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ end
JuMP.optimize!(::T) where {T<:AbstractBilevelModel} =
error("Can't solve a model of type: $T ")
function JuMP.optimize!(model::BilevelModel;
lower_prob = "", upper_prob = "", bilevel_prob = "", solver_prob = "", file_format = MOI.FileFormats.FORMAT_AUTOMATIC)
lower_prob = "", upper_prob = "", bilevel_prob = "", solver_prob = "", file_format = MOI.FileFormats.FORMAT_AUTOMATIC, consider_constrained_variables = false,)

if model.mode === nothing
error("No solution mode selected, use `set_mode(model, mode)` or initialize with `BilevelModel(optimizer_constructor, mode = some_mode)`")
Expand Down Expand Up @@ -508,7 +508,7 @@ function JuMP.optimize!(model::BilevelModel;

single_blm, upper_to_sblm, lower_to_sblm, lower_primal_dual_map, lower_dual_to_sblm =
build_bilevel(upper, lower, moi_link, moi_upper, mode, moi_link2,
copy_names = model.copy_names, pass_start = model.pass_start)
copy_names = model.copy_names, pass_start = model.pass_start, consider_constrained_variables = consider_constrained_variables,)

# pass additional info (hints - not actual problem data)
# for lower level dual variables (start, upper hint, lower hint)
Expand Down
86 changes: 83 additions & 3 deletions src/moi.jl
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,52 @@ end
accept_vector_set(::ProductMode{T}, ::Complement) where T = nothing
accept_vector_set(::MixedMode{T}, ::Complement) where T = nothing

function get_variable_complement(primal_model, dual_model, primal_con, dual_con)
error("An internal error with variable complements occurred. Likely, your problem type does not yet support consideration of constrained variables.")
end

function get_variable_complement(primal_model, dual_model, primal_con::MOI.ConstraintIndex{Fp,Sp}, dual_con::MOI.ConstraintIndex{Fd,Sd}) where {Fp<:MOI.VariableIndex,Sp<:MOI.GreaterThan{T},Fd,Sd<:MOI.GreaterThan{T}} where T
Copy link
Owner

Choose a reason for hiding this comment

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

any reason for not supporting other sets like vector sets?
you can follow the code in get_canonical_complement, as you have been doing

Copy link
Author

Choose a reason for hiding this comment

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

left this out because I did not really feel certain.
Now added, can you check if it is correct?
Also not sure about the todo (see code for the new function)

primal_variable = MOI.get(primal_model, MOI.ConstraintFunction(), primal_con)
primal_set = MOI.get(primal_model, MOI.ConstraintSet(), primal_con)

@assert MOI.constant(primal_set) == 0 "Unexpected variable bound"

dual_func = MOI.get(dual_model, MOI.ConstraintFunction(), dual_con)
dual_set = MOI.get(dual_model, MOI.ConstraintSet(), dual_con)
if MOI.constant(dual_set) > 0
dual_func.constant = dual_func.constant - MOI.constant(dual_set)
elseif MOI.constant(dual_set) < 0
dual_func.constant = dual_func.constant + MOI.constant(dual_set)
end
return Complement(false, primal_con, dual_func, set_with_zero(dual_set), primal_variable)
end

function get_variable_complement(primal_model, dual_model, primal_con::MOI.ConstraintIndex{Fp,Sp}, dual_con::MOI.ConstraintIndex{Fd,Sd}) where {Fp<:MOI.VariableIndex,Sp<:MOI.LessThan{T},Fd,Sd<:MOI.LessThan{T}} where T
Copy link
Owner

Choose a reason for hiding this comment

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

these two functions look the same

Copy link
Author

Choose a reason for hiding this comment

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

Yes, this was an old relict as I wasn't sure about constants etc in the beginning. Now, one is deleted and the other is defined for union of lesser/greater. Equalities still throw an error.

primal_variable = MOI.get(primal_model, MOI.ConstraintFunction(), primal_con)
primal_set = MOI.get(primal_model, MOI.ConstraintSet(), primal_con)

@assert MOI.constant(primal_set) == 0 "Unexpected variable bound"

dual_func = MOI.get(dual_model, MOI.ConstraintFunction(), dual_con)
dual_set = MOI.get(dual_model, MOI.ConstraintSet(), dual_con)
if MOI.constant(dual_set) > 0
dual_func.constant = dual_func.constant - MOI.constant(dual_set)
elseif MOI.constant(dual_set) < 0
dual_func.constant = dual_func.constant + MOI.constant(dual_set)
end
return Complement(false, primal_con, dual_func, set_with_zero(dual_set), primal_variable)
end

function get_variable_complements(primal_model, dual_model, primal_dual_map)
map = primal_dual_map.primal_con_dual_var
out = Complement[]
for (primal_con, dual_con) in primal_dual_map.constrained_var_dual
con = get_variable_complement(primal_model, dual_model, primal_con, dual_con)
push!(out, con)
end
return out
end

function get_canonical_complements(primal_model, primal_dual_map)
map = primal_dual_map.primal_con_dual_var
out = Complement[]
Expand Down Expand Up @@ -311,7 +357,8 @@ function build_bilevel(
mode,
upper_var_to_lower_ctr::Dict{VI,CI} = Dict{VI,CI}();
copy_names::Bool = false,
pass_start::Bool = false
pass_start::Bool = false,
consider_constrained_variables::Bool = false,
)

# Start with an empty problem
Expand All @@ -326,7 +373,7 @@ function build_bilevel(
dual_names = DualNames("dual_","dual_"),
variable_parameters = upper_variables,
ignore_objective = ignore_dual_objective(mode),
consider_constrained_variables = false,
consider_constrained_variables = consider_constrained_variables,
)
# the model
lower_dual = dual_problem.dual_model
Expand Down Expand Up @@ -428,8 +475,32 @@ function build_bilevel(
# feasible equality constraints always satisfy complementarity
end
end

if consider_constrained_variables
# complementary slackness for variable bounds
variable_comps = get_variable_complements(lower, lower_dual, lower_primal_dual_map)
for comp in variable_comps
if !is_equality(comp.set_w_zero)
accept_vector_set(mode, comp)
add_complement(
mode,
m,
comp,
lower_dual_idxmap,
lower_idxmap,
copy_names,
pass_start,
)
end
end
end

else # strong duality
add_strong_duality(mode, m, lower_primal_obj, lower_dual_obj, lower_idxmap, lower_dual_idxmap)
if !consider_constrained_variables || contains_only_scalar_sets(lower)
add_strong_duality(mode, m, lower_primal_obj, lower_dual_obj, lower_idxmap, lower_dual_idxmap)
else
error("Only scalar sets in lower level allowed with consider_constrained_variables.")
end
Copy link
Owner

Choose a reason for hiding this comment

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

I dont think any of this is needed

Copy link
Author

Choose a reason for hiding this comment

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

true, this threw occasional errors earlier. Found out that this was related to missing dual variables for which start values could not be passed.
This is now resolved, since hints etc. are no longer passed if the dual to bounds does not exist in the first place...

end
add_aggregate_constraints(m, mode, copy_names)

Expand Down Expand Up @@ -1022,6 +1093,15 @@ function add_complement(mode::FortunyAmatMcCarlMode{T}, m, comp::Complement,
return c1
end

function contains_only_scalar_sets(model::MOI.ModelLike)
for (F,S) in MOI.get(model, MOI.ListOfConstraintTypesPresent())
if !(S <: SCALAR_SETS)
return false
end
end
return true
end

function to_vector_affine(f::MOI.VectorAffineFunction{T}) where T
return f
end
Expand Down
Loading