Skip to content

Commit

Permalink
Merge pull request #150 from LukasBarner/max_min_quad
Browse files Browse the repository at this point in the history
flips signs of quadslack for maximization, fixes 142
  • Loading branch information
joaquimg authored Oct 3, 2022
2 parents d9f9ee8 + 84bac98 commit e5f57b1
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 3 deletions.
8 changes: 5 additions & 3 deletions src/dual_equality_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function add_dual_equality_constraints(
scalar_affine_terms,
primal_dual_map.primal_var_dual_quad_slack,
primal_objective,
sense_change,
)

# terms from mixing variables and parameters
Expand Down Expand Up @@ -240,26 +241,27 @@ function add_scalar_affine_terms_from_quad_obj(
},
primal_var_dual_quad_slack::Dict{MOI.VariableIndex,MOI.VariableIndex},
primal_objective::PrimalObjective{T},
sense_change::T,
) where {T}
for term in primal_objective.obj.quadratic_terms
if term.variable_1 == term.variable_2
dual_vi = primal_var_dual_quad_slack[term.variable_1]
push_to_scalar_affine_terms!(
scalar_affine_terms[term.variable_1],
-MOI.coefficient(term),
-sense_change * MOI.coefficient(term),
dual_vi,
)
else
dual_vi_1 = primal_var_dual_quad_slack[term.variable_1]
push_to_scalar_affine_terms!(
scalar_affine_terms[term.variable_2],
-MOI.coefficient(term),
-sense_change * MOI.coefficient(term),
dual_vi_1,
)
dual_vi_2 = primal_var_dual_quad_slack[term.variable_2]
push_to_scalar_affine_terms!(
scalar_affine_terms[term.variable_1],
-MOI.coefficient(term),
-sense_change * MOI.coefficient(term),
dual_vi_2,
)
end
Expand Down
101 changes: 101 additions & 0 deletions test/Tests/test_max_min_dual_equal_feasibility_quadratic.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
function get_DualMinModel_no_bounds()
MinModel = Model()
@variable(MinModel, Q₁)
@variable(MinModel, Q₂)

@objective(MinModel, Min, (Q₁ + Q₂)^2)
@constraint(MinModel, C₁, Q₁ + 1 >= 0)
@constraint(MinModel, C₂, Q₂ + 1 >= 0)

DualMinModel = dualize(MinModel; dual_names = DualNames("dual", ""))
return DualMinModel
end

function get_DualMaxModel_no_bounds()
MaxModel = Model()
@variable(MaxModel, Q₁)
@variable(MaxModel, Q₂)
@objective(MaxModel, Max, -(Q₁ + Q₂)^2)
@constraint(MaxModel, C₁, Q₁ + 1 >= 0)
@constraint(MaxModel, C₂, Q₂ + 1 >= 0)

DualMaxModel = dualize(MaxModel; dual_names = DualNames("dual", ""))
return DualMaxModel
end

function get_DualMinModel_with_bounds()
MinModel = Model()
@variable(MinModel, Q₁ >= 0)
@variable(MinModel, Q₂ >= 0)

@objective(MinModel, Min, (Q₁ + Q₂)^2)
@constraint(MinModel, C₁, Q₁ + 1 >= 0)
@constraint(MinModel, C₂, Q₂ + 1 >= 0)

DualMinModel = dualize(MinModel; dual_names = DualNames("dual", ""))
return DualMinModel
end

function get_DualMaxModel_with_bounds()
MaxModel = Model()

@variable(MaxModel, Q₁ >= 0)
@variable(MaxModel, Q₂ >= 0)
@objective(MaxModel, Max, -(Q₁ + Q₂)^2)
@constraint(MaxModel, C₁, Q₁ + 1 >= 0)
@constraint(MaxModel, C₂, Q₂ + 1 >= 0)

DualMaxModel = dualize(MaxModel; dual_names = DualNames("dual", ""))
return DualMaxModel
end

function test_equivalence_max_min(DualMinModel, DualMaxModel)
for (F, S) in list_of_constraint_types(DualMinModel)
DualMinModel_eq_con_funs = [
MOI.get(backend(DualMinModel), MOI.ConstraintFunction(), ctr_idx) for
ctr_idx in JuMP.index.(all_constraints(DualMinModel, F, S))
]
DualMaxModel_eq_con_funs = [
MOI.get(backend(DualMaxModel), MOI.ConstraintFunction(), ctr_idx) for
ctr_idx in JuMP.index.(all_constraints(DualMaxModel, F, S))
]
@test length(DualMinModel_eq_con_funs) ==
length(DualMaxModel_eq_con_funs)
for i in eachindex(DualMinModel_eq_con_funs)
if typeof(DualMinModel_eq_con_funs[i]) != MOI.VariableIndex
@test MOI.coefficient.(DualMinModel_eq_con_funs[i].terms) ==
MOI.coefficient.(DualMaxModel_eq_con_funs[i].terms)
@test MOI.constant.(DualMinModel_eq_con_funs[i]) ==
MOI.constant.(DualMaxModel_eq_con_funs[i])
end
end
DualMinModel_eq_con_sets = [
MOI.get(backend(DualMinModel), MOI.ConstraintSet(), ctr_idx) for
ctr_idx in JuMP.index.(all_constraints(DualMinModel, F, S))
]
DualMaxModel_eq_con_sets = [
MOI.get(backend(DualMaxModel), MOI.ConstraintSet(), ctr_idx) for
ctr_idx in JuMP.index.(all_constraints(DualMaxModel, F, S))
]
@test length(DualMinModel_eq_con_sets) ==
length(DualMaxModel_eq_con_sets)
for i in eachindex(DualMinModel_eq_con_sets)
@test MOI.constant.(DualMinModel_eq_con_sets[i]) ==
MOI.constant.(DualMaxModel_eq_con_sets[i])
end
end
end

@testset "max min dual equal feasibility quadratic" begin
@testset "max min dual equal feasibility quadratic no variable bounds" begin
DualMinModel = get_DualMinModel_no_bounds()
DualMaxModel = get_DualMaxModel_no_bounds()
test_equivalence_max_min(DualMinModel, DualMaxModel)
end

@testset "max min dual equal feasibility quadratic with variable bounds" begin
DualMinModel = get_DualMinModel_with_bounds()
DualMaxModel = get_DualMaxModel_with_bounds()
test_equivalence_max_min(DualMinModel, DualMaxModel)
end
end
3 changes: 3 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,6 @@ include("Solvers/scs_test.jl")
include("Tests/test_JuMP_dualize.jl")
include("Tests/test_MOI_wrapper.jl")
include("Tests/test_modify.jl")

# Test that dual feasibility of quadratic min and max is equivalent (see issue #142)
include("Tests/test_max_min_dual_equal_feasibility_quadratic.jl")

0 comments on commit e5f57b1

Please sign in to comment.